v2.2.18: Dynamic Office Auto-Layout & Legacy Cleanup
This commit is contained in:
@@ -3,15 +3,15 @@
|
|||||||
<!-- ASTRA:AUTO-START -->
|
<!-- ASTRA:AUTO-START -->
|
||||||
|
|
||||||
## Snapshot
|
## Snapshot
|
||||||
- **Workspace**: `ConnectAI` `v2.2.16` _(absolute path varies by environment; resolved from the active VS Code workspace)_
|
- **Workspace**: `ConnectAI` `v2.2.17` _(absolute path varies by environment; resolved from the active VS Code workspace)_
|
||||||
- **Description**: The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.
|
- **Description**: The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.
|
||||||
- **Stack**: TypeScript, Node.js, VS Code Extension, LM Studio SDK, Test runner
|
- **Stack**: TypeScript, Node.js, VS Code Extension, LM Studio SDK, Test runner
|
||||||
- **Stats**: 250 source files, ~50,659 lines across 5 top-level modules.
|
- **Stats**: 250 source files, ~51,189 lines across 5 top-level modules.
|
||||||
|
|
||||||
## Last Refresh
|
## Last Refresh
|
||||||
- **Time**: 2026-05-16T13:21:15.124Z
|
- **Time**: 2026-05-16T13:51:21.324Z
|
||||||
- **Files newly analysed**: 5
|
- **Files newly analysed**: 8
|
||||||
- **Files reused from cache**: 245
|
- **Files reused from cache**: 242
|
||||||
|
|
||||||
## Directory Map
|
## Directory Map
|
||||||
```mermaid
|
```mermaid
|
||||||
@@ -64,7 +64,7 @@ flowchart LR
|
|||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
### `src/` — 127 files, ~34,442 lines
|
### `src/` — 127 files, ~34,824 lines
|
||||||
|
|
||||||
**Sub-directories**
|
**Sub-directories**
|
||||||
- `src/features/` (54) — Astra Office — public API. 다음 세션에서 추가될 OfficeSnapshot presenter / schema 도 같은 entry 로 노출 예정. 현재 노출: full webview panel H
|
- `src/features/` (54) — Astra Office — public API. 다음 세션에서 추가될 OfficeSnapshot presenter / schema 도 같은 entry 로 노출 예정. 현재 노출: full webview panel H
|
||||||
@@ -97,25 +97,25 @@ flowchart LR
|
|||||||
- `src/features/company/dispatcher.ts` (1435 lines) — Sequential dispatcher for 1인 기업 모드. Drives one company "turn": user prompt → CEO planner (JSON {brief, tasks}) → for each task in plan: dispatch one specialist (sequentially) - build specialist prompt
|
- `src/features/company/dispatcher.ts` (1435 lines) — Sequential dispatcher for 1인 기업 모드. Drives one company "turn": user prompt → CEO planner (JSON {brief, tasks}) → for each task in plan: dispatch one specialist (sequentially) - build specialist prompt
|
||||||
- `src/features/approval/approvalQueue.ts` (129 lines)
|
- `src/features/approval/approvalQueue.ts` (129 lines)
|
||||||
- `src/integrations/telegram/telegramClient.ts` (154 lines)
|
- `src/integrations/telegram/telegramClient.ts` (154 lines)
|
||||||
- `src/features/astraOffice/view/runtime.ts` (1350 lines) — 자동 분리: src/sidebarProvider.ts 4002-5116 (IIFE 본문) 에서 추출. 동작 동등. ${assets.derivedBase} placeholder 는 panelHtml 에서 .replace() 로 실제 값 주입. 다음 세션에서 OfficeSnapshot 기반으로 단계적으로 잘라낼 예정.
|
- `src/features/astraOffice/view/runtime.ts` (1564 lines) — 자동 분리: src/sidebarProvider.ts 4002-5116 (IIFE 본문) 에서 추출. 동작 동등. ${assets.derivedBase} placeholder 는 panelHtml 에서 .replace() 로 실제 값 주입. 다음 세션에서 OfficeSnapshot 기반으로 단계적으로 잘라낼 예정.
|
||||||
- `src/features/company/agents.ts` (211 lines) — 기본 에이전트 로스터 — 1인 기업 모드의 출고 디폴트. 설계 의도: 소프트웨어/게임 개발 IT 회사의 1인 기업 운영을 가정. 한 사람이 기획 → 디자인 → 개발 → QA → 출시 → 운영/마케팅을 모두 책임질 때 필요한 직군을 빠짐없이 커버하되 역할이 겹치지 않게 분리한다. 직군 구분 (혼동 방지): - 기획자(business) : 무엇을 만들지 정의
|
- `src/features/company/agents.ts` (211 lines) — 기본 에이전트 로스터 — 1인 기업 모드의 출고 디폴트. 설계 의도: 소프트웨어/게임 개발 IT 회사의 1인 기업 운영을 가정. 한 사람이 기획 → 디자인 → 개발 → QA → 출시 → 운영/마케팅을 모두 책임질 때 필요한 직군을 빠짐없이 커버하되 역할이 겹치지 않게 분리한다. 직군 구분 (혼동 방지): - 기획자(business) : 무엇을 만들지 정의
|
||||||
- `src/features/company/pixelOfficeState.ts` (286 lines) — Pixel Office — Agent Work Pipeline 상태를 시각화하는 UI Layer 전용 모듈. ─────────────────── 설계 원칙 ─────────────────── 1. Agent 핵심 판단 로직을 절대 바꾸지 않는다. Pipeline 진행, contract 합의, 검수 cycle, 승인 게이트 — 모두 기존 dispatcher
|
- `src/features/company/pixelOfficeState.ts` (286 lines) — Pixel Office — Agent Work Pipeline 상태를 시각화하는 UI Layer 전용 모듈. ─────────────────── 설계 원칙 ─────────────────── 1. Agent 핵심 판단 로직을 절대 바꾸지 않는다. Pipeline 진행, contract 합의, 검수 cycle, 승인 게이트 — 모두 기존 dispatcher
|
||||||
- `src/features/company/sessionStore.ts` (231 lines) — Disk persistence for company-mode session artefacts. Each company turn produces a timestamped directory: <workspaceRoot>/.astra/company/sessions/2026-05-13T21-29/ ├─ brief.md ← CEO's task decompositio
|
- `src/features/company/sessionStore.ts` (231 lines) — Disk persistence for company-mode session artefacts. Each company turn produces a timestamped directory: <workspaceRoot>/.astra/company/sessions/2026-05-13T21-29/ ├─ brief.md ← CEO's task decompositio
|
||||||
- `src/features/projectArchitecture/scanner.ts` (644 lines) — Deep static analyser for the Project Architecture Context generator. Walks the project tree (skipping the usual nodemodules / out / dist noise), pulls the role of each interesting file from its leadin
|
- `src/features/projectArchitecture/scanner.ts` (644 lines) — Deep static analyser for the Project Architecture Context generator. Walks the project tree (skipping the usual nodemodules / out / dist noise), pulls the role of each interesting file from its leadin
|
||||||
- `src/lib/contextManager.ts` (275 lines) — Context Manager (컨텍스트 한계 관리) "context length = 132k" 는 "답변을 132k 토큰까지 생성해도 된다" 가 아닙니다. 시스템 프롬프트 + 대화 기록 + 입력 문서 + 생성될 답변 + 여유분 ≤ context length 이 모듈은 요청을 보내기 전에 입력 토큰을 추정하고, - 동적으로 출력 상한(maxTokens)을 계
|
- `src/lib/contextManager.ts` (275 lines) — Context Manager (컨텍스트 한계 관리) "context length = 132k" 는 "답변을 132k 토큰까지 생성해도 된다" 가 아닙니다. 시스템 프롬프트 + 대화 기록 + 입력 문서 + 생성될 답변 + 여유분 ≤ context length 이 모듈은 요청을 보내기 전에 입력 토큰을 추정하고, - 동적으로 출력 상한(maxTokens)을 계
|
||||||
- `src/extension.ts` (1201 lines)
|
- `src/extension.ts` (1202 lines)
|
||||||
- `src/features/company/resumeStore.ts` (134 lines) — Disk persistence for company-turn resume state. 각 turn의 sessionDir 안에 resume.json을 두고, dispatcher가 매 의미 있는 시점(plan 확정 / 각 stage 직후 / abort 시점)에 현재 상태를 덮어쓴다. 재개 시점에는 이 파일을 읽어 nextIndex 부터 dispatch 재개.
|
- `src/features/company/resumeStore.ts` (134 lines) — Disk persistence for company-turn resume state. 각 turn의 sessionDir 안에 resume.json을 두고, dispatcher가 매 의미 있는 시점(plan 확정 / 각 stage 직후 / abort 시점)에 현재 상태를 덮어쓴다. 재개 시점에는 이 파일을 읽어 nextIndex 부터 dispatch 재개.
|
||||||
- `src/core/astraPath.ts` (50 lines) — Astra Path Resolver (경로 해결기) Astra의 모든 데이터 파일(.astra 디렉토리)의 경로를 중앙에서 관리합니다. 확장 프로그램의 설치 경로(extensionUri) 기반으로 .astra 디렉토리를 해결하여, 사용자 프로젝트 루트가 아닌 ConnectAI 패키지 내부에 데이터를 저장합니다. 이 모듈은 AAL(Astra Autonomou
|
- `src/core/astraPath.ts` (50 lines) — Astra Path Resolver (경로 해결기) Astra의 모든 데이터 파일(.astra 디렉토리)의 경로를 중앙에서 관리합니다. 확장 프로그램의 설치 경로(extensionUri) 기반으로 .astra 디렉토리를 해결하여, 사용자 프로젝트 루트가 아닌 ConnectAI 패키지 내부에 데이터를 저장합니다. 이 모듈은 AAL(Astra Autonomou
|
||||||
|
|
||||||
### `media/` — 6 files, ~6,863 lines
|
### `media/` — 6 files, ~7,011 lines
|
||||||
|
|
||||||
**Key files**
|
**Key files**
|
||||||
- `media/sidebar.css` (2016 lines) — Stylesheet
|
- `media/sidebar.css` (2016 lines) — Stylesheet
|
||||||
- `media/sidebar.js` (3657 lines)
|
- `media/sidebar.js` (3657 lines)
|
||||||
- `media/sidebar.html` (546 lines) — Astra
|
- `media/sidebar.html` (546 lines) — Astra
|
||||||
- `media/settings-panel.css` (210 lines) — Stylesheet
|
- `media/settings-panel.css` (210 lines) — Stylesheet
|
||||||
- `media/settings-panel.html` (164 lines) — Astra Settings
|
- `media/settings-panel.html` (244 lines) — Astra Settings
|
||||||
- `media/settings-panel.js` (270 lines)
|
- `media/settings-panel.js` (338 lines)
|
||||||
|
|
||||||
### `tests/` — 33 files, ~5,811 lines
|
### `tests/` — 33 files, ~5,811 lines
|
||||||
*Depends on*: `src/`
|
*Depends on*: `src/`
|
||||||
@@ -328,7 +328,7 @@ Astra는 대표님의 명시적인 승인 하에 로컬 시스템의 강력한
|
|||||||
**Designed for High-Performance Decision Making.**
|
**Designed for High-Performance Decision Making.**
|
||||||
Copyright (C) **g1nation**. All rights reserved.
|
Copyright (C) **g1nation**. All rights reserved.
|
||||||
|
|
||||||
_Last auto-scan: 2026-05-16T13:21:15.124Z · signature `f2379ce`_
|
_Last auto-scan: 2026-05-16T13:51:21.324Z · signature `ebf5ecaf`_
|
||||||
<!-- ASTRA:AUTO-END -->
|
<!-- ASTRA:AUTO-END -->
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"generatedAt": "2026-05-16T13:21:15.133Z",
|
"generatedAt": "2026-05-16T13:51:21.336Z",
|
||||||
"files": {
|
"files": {
|
||||||
"src/agent.ts": {
|
"src/agent.ts": {
|
||||||
"mtimeMs": 1778936503000,
|
"mtimeMs": 1778936503000,
|
||||||
@@ -259,9 +259,9 @@
|
|||||||
"imports": []
|
"imports": []
|
||||||
},
|
},
|
||||||
"src/extension.ts": {
|
"src/extension.ts": {
|
||||||
"mtimeMs": 1778937452000,
|
"mtimeMs": 1778938013000,
|
||||||
"size": 62380,
|
"size": 62397,
|
||||||
"lines": 1201,
|
"lines": 1202,
|
||||||
"role": "",
|
"role": "",
|
||||||
"imports": [
|
"imports": [
|
||||||
"src/utils",
|
"src/utils",
|
||||||
@@ -362,16 +362,16 @@
|
|||||||
"imports": []
|
"imports": []
|
||||||
},
|
},
|
||||||
"src/features/astraOffice/view/officeBody.ts": {
|
"src/features/astraOffice/view/officeBody.ts": {
|
||||||
"mtimeMs": 1778937436000,
|
"mtimeMs": 1778938908000,
|
||||||
"size": 3986,
|
"size": 4112,
|
||||||
"lines": 102,
|
"lines": 103,
|
||||||
"role": "",
|
"role": "",
|
||||||
"imports": []
|
"imports": []
|
||||||
},
|
},
|
||||||
"src/features/astraOffice/view/officeStyles.ts": {
|
"src/features/astraOffice/view/officeStyles.ts": {
|
||||||
"mtimeMs": 1778937430000,
|
"mtimeMs": 1778938908000,
|
||||||
"size": 20956,
|
"size": 23999,
|
||||||
"lines": 342,
|
"lines": 401,
|
||||||
"role": "",
|
"role": "",
|
||||||
"imports": []
|
"imports": []
|
||||||
},
|
},
|
||||||
@@ -387,9 +387,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/features/astraOffice/view/runtime.ts": {
|
"src/features/astraOffice/view/runtime.ts": {
|
||||||
"mtimeMs": 1778937401000,
|
"mtimeMs": 1778939265000,
|
||||||
"size": 62934,
|
"size": 73382,
|
||||||
"lines": 1350,
|
"lines": 1564,
|
||||||
"role": "자동 분리: src/sidebarProvider.ts 4002-5116 (IIFE 본문) 에서 추출. 동작 동등. ${assets.derivedBase} placeholder 는 panelHtml 에서 .replace() 로 실제 값 주입. 다음 세션에서 OfficeSnapshot 기반으로 단계적으로 잘라낼 예정.",
|
"role": "자동 분리: src/sidebarProvider.ts 4002-5116 (IIFE 본문) 에서 추출. 동작 동등. ${assets.derivedBase} placeholder 는 panelHtml 에서 .replace() 로 실제 값 주입. 다음 세션에서 OfficeSnapshot 기반으로 단계적으로 잘라낼 예정.",
|
||||||
"imports": []
|
"imports": []
|
||||||
},
|
},
|
||||||
@@ -775,16 +775,17 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/features/settings/settingsPanelProvider.ts": {
|
"src/features/settings/settingsPanelProvider.ts": {
|
||||||
"mtimeMs": 1778674336000,
|
"mtimeMs": 1778937992000,
|
||||||
"size": 21422,
|
"size": 26837,
|
||||||
"lines": 489,
|
"lines": 596,
|
||||||
"role": "",
|
"role": "",
|
||||||
"imports": [
|
"imports": [
|
||||||
"src/integrations/telegram/telegramClient",
|
"src/integrations/telegram/telegramClient",
|
||||||
"src/integrations/telegram/telegramBot",
|
"src/integrations/telegram/telegramBot",
|
||||||
"src/utils",
|
"src/utils",
|
||||||
"src/lib/discoverModels",
|
"src/lib/discoverModels",
|
||||||
"src/lib/paths"
|
"src/lib/paths",
|
||||||
|
"src/features/calendar"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/features/sheets/index.ts": {
|
"src/features/sheets/index.ts": {
|
||||||
@@ -1284,16 +1285,16 @@
|
|||||||
"imports": []
|
"imports": []
|
||||||
},
|
},
|
||||||
"media/settings-panel.html": {
|
"media/settings-panel.html": {
|
||||||
"mtimeMs": 1778763966000,
|
"mtimeMs": 1778937850000,
|
||||||
"size": 7678,
|
"size": 12095,
|
||||||
"lines": 164,
|
"lines": 244,
|
||||||
"role": "Astra Settings",
|
"role": "Astra Settings",
|
||||||
"imports": []
|
"imports": []
|
||||||
},
|
},
|
||||||
"media/settings-panel.js": {
|
"media/settings-panel.js": {
|
||||||
"mtimeMs": 1778256014000,
|
"mtimeMs": 1778938082000,
|
||||||
"size": 11176,
|
"size": 15189,
|
||||||
"lines": 270,
|
"lines": 338,
|
||||||
"role": "",
|
"role": "",
|
||||||
"imports": []
|
"imports": []
|
||||||
},
|
},
|
||||||
@@ -1860,7 +1861,7 @@
|
|||||||
"imports": []
|
"imports": []
|
||||||
},
|
},
|
||||||
"docs/records/ConnectAI/chronicle.config.json": {
|
"docs/records/ConnectAI/chronicle.config.json": {
|
||||||
"mtimeMs": 1778937290000,
|
"mtimeMs": 1778937757000,
|
||||||
"size": 416,
|
"size": 416,
|
||||||
"lines": 11,
|
"lines": 11,
|
||||||
"role": "JSON configuration",
|
"role": "JSON configuration",
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
|
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
|
||||||
"createdAt": 1778938100767,
|
"createdAt": 1778939488449,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
||||||
"createdAt": 1778938100758,
|
"createdAt": 1778939488449,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
"result": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
||||||
"createdAt": 1778938100756,
|
"createdAt": 1778939488444,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"result": "---\nid: stress_conflict_1778938100739\ndate: 2026-05-16T13:28:20.772Z\ntype: knowledge_artifact\nstandard: P-Reinforce v3.0\ntags: [automated, connect_ai, brain_sync]\n---\n\n## 📌 Brief Summary\nFinal report with inconsistencies. This should be long enough to pass validation.\n\nFinal report with inconsistencies. This should be long enough to pass validation.\n\n---\n## 💡 Astra의 선제적 제안 (Proactive Next Actions)\nFinal report with inconsistencies. This should be long enough to pass validation.\n---\n## 🛡️ Reliability & Audit Summary\n> [!NOTE]\n> 이 문서는 ConnectAI의 **Intelligent Resilience** 엔진에 의해 검증 및 정제되었습니다.\n\n| Metric | Value | Status |\n| :--- | :--- | :--- |\n| **Conflict Risk** | `60/100` | ⚠️ Medium |\n| **Fallbacks Used** | `0` | ✅ None |\n| **Auto Retries** | `0` | ✅ Stable |\n| **Deduplication** | `0` | Standard |\n| **Processing Time** | `0.0s` | ✅ Fast |\n\n### 🔍 Decision Audit Trail\n- **[PLANNER]** 전략 수립 중... (11ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (6ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (6ms)\n",
|
"result": "---\nid: stress_conflict_1778939488432\ndate: 2026-05-16T13:51:28.450Z\ntype: knowledge_artifact\nstandard: P-Reinforce v3.0\ntags: [automated, connect_ai, brain_sync]\n---\n\n## 📌 Brief Summary\nFinal report with inconsistencies. This should be long enough to pass validation.\n\nFinal report with inconsistencies. This should be long enough to pass validation.\n\n---\n## 💡 Astra의 선제적 제안 (Proactive Next Actions)\nFinal report with inconsistencies. This should be long enough to pass validation.\n---\n## 🛡️ Reliability & Audit Summary\n> [!NOTE]\n> 이 문서는 ConnectAI의 **Intelligent Resilience** 엔진에 의해 검증 및 정제되었습니다.\n\n| Metric | Value | Status |\n| :--- | :--- | :--- |\n| **Conflict Risk** | `60/100` | ⚠️ Medium |\n| **Fallbacks Used** | `0` | ✅ None |\n| **Auto Retries** | `0` | ✅ Stable |\n| **Deduplication** | `0` | Standard |\n| **Processing Time** | `0.0s` | ✅ Fast |\n\n### 🔍 Decision Audit Trail\n- **[PLANNER]** 전략 수립 중... (12ms)\n- **[RESEARCHER]** 핵심 정보 수집 및 분석 중... (1ms)\n- **[WRITER]** 최종 리포트 작성 및 편집 중... (4ms)\n",
|
||||||
"createdAt": 1778938100772,
|
"createdAt": 1778939488450,
|
||||||
"modelVersion": "unknown"
|
"modelVersion": "unknown"
|
||||||
}
|
}
|
||||||
+11
-11
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"missionId": "stress_conflict_1778938100739",
|
"missionId": "stress_conflict_1778939488432",
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"startTime": "2026-05-16T13:28:20.739Z",
|
"startTime": "2026-05-16T13:51:28.432Z",
|
||||||
"totalElapsedMs": 33,
|
"totalElapsedMs": 18,
|
||||||
"results": {
|
"results": {
|
||||||
"planner": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
"planner": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.",
|
||||||
"researcher": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
"researcher": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
|
||||||
@@ -16,30 +16,30 @@
|
|||||||
{
|
{
|
||||||
"from": "idle",
|
"from": "idle",
|
||||||
"to": "planner",
|
"to": "planner",
|
||||||
"durationMs": 11,
|
"durationMs": 12,
|
||||||
"message": "전략 수립 중...",
|
"message": "전략 수립 중...",
|
||||||
"ts": "2026-05-16T13:28:20.750Z"
|
"ts": "2026-05-16T13:51:28.444Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "planner",
|
"from": "planner",
|
||||||
"to": "researcher",
|
"to": "researcher",
|
||||||
"durationMs": 6,
|
"durationMs": 1,
|
||||||
"message": "핵심 정보 수집 및 분석 중...",
|
"message": "핵심 정보 수집 및 분석 중...",
|
||||||
"ts": "2026-05-16T13:28:20.756Z"
|
"ts": "2026-05-16T13:51:28.445Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "researcher",
|
"from": "researcher",
|
||||||
"to": "writer",
|
"to": "writer",
|
||||||
"durationMs": 6,
|
"durationMs": 4,
|
||||||
"message": "최종 리포트 작성 및 편집 중...",
|
"message": "최종 리포트 작성 및 편집 중...",
|
||||||
"ts": "2026-05-16T13:28:20.762Z"
|
"ts": "2026-05-16T13:51:28.449Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "writer",
|
"from": "writer",
|
||||||
"to": "completed",
|
"to": "completed",
|
||||||
"durationMs": 10,
|
"durationMs": 1,
|
||||||
"message": "미션 완료",
|
"message": "미션 완료",
|
||||||
"ts": "2026-05-16T13:28:20.772Z"
|
"ts": "2026-05-16T13:51:28.450Z"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"resilienceMetrics": {
|
"resilienceMetrics": {
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
# Astra Patch Notes
|
# Astra Patch Notes
|
||||||
|
|
||||||
|
## v2.2.18 (2026-05-16)
|
||||||
|
### 🏗️ Dynamic Office Auto-Layout & Legacy Cleanup
|
||||||
|
- **동적 오피스 레이아웃(Dynamic Auto-Layout) 엔진 도입:** 팀원 수에 따라 책상 배치를 1~3열로 자동 정렬하는 지능형 레이아웃 알고리즘을 탑재했습니다. 이제 팀원 수에 관계없이 최적화된 오피스 뷰를 제공합니다.
|
||||||
|
- **레거시 레이아웃 감지 및 마이그레이션:** 구 버전의 고정형(8석) 레이아웃을 자동으로 감지하여 최신 동적 레이아웃으로 전환하는 로직을 추가했습니다.
|
||||||
|
- **스테이지 밸런스 조정:** CEO 책상 위치를 중앙 하단으로 재배치하고, 전반적인 오브젝트 크기와 간격을 조정하여 시각적 안정성을 높였습니다.
|
||||||
|
- **오피스 동기화 로직 강화:** 작업 흐름(Pipeline)이 없을 때도 활성 팀원(Roster) 기반으로 오피스가 자동 구성되도록 동기화 엔진을 개선했습니다.
|
||||||
|
- **신규 패키징:** `astra-2.2.18.vsix` 패키지를 통해 팀 규모에 따라 유연하게 변하는 지능형 오피스 환경을 배포합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## v2.2.17 (2026-05-16)
|
## v2.2.17 (2026-05-16)
|
||||||
### ⚙️ Google Service Control & Astra Office Flow Layer
|
### ⚙️ Google Service Control & Astra Office Flow Layer
|
||||||
- **구글 서비스 설정 패널(Google Control) 도입:** 설정 패널 내에 구글 캘린더 OAuth 연결, iCal 동기화, 클라이언트 자격 증명 관리를 위한 전용 섹션을 추가했습니다.
|
- **구글 서비스 설정 패널(Google Control) 도입:** 설정 패널 내에 구글 캘린더 OAuth 연결, iCal 동기화, 클라이언트 자격 증명 관리를 위한 전용 섹션을 추가했습니다.
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
"corePurpose": "",
|
"corePurpose": "",
|
||||||
"detailLevel": "standard",
|
"detailLevel": "standard",
|
||||||
"createdAt": "2026-05-13T13:09:33.788Z",
|
"createdAt": "2026-05-13T13:09:33.788Z",
|
||||||
"updatedAt": "2026-05-16T13:22:37.704Z"
|
"updatedAt": "2026-05-16T13:52:09.001Z"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "astra",
|
"name": "astra",
|
||||||
"displayName": "Astra",
|
"displayName": "Astra",
|
||||||
"description": "The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.",
|
"description": "The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.",
|
||||||
"version": "2.2.17",
|
"version": "2.2.18",
|
||||||
"publisher": "g1nation",
|
"publisher": "g1nation",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"icon": "assets/icon.png",
|
"icon": "assets/icon.png",
|
||||||
|
|||||||
@@ -29,16 +29,13 @@ setTimeout(_fitStage, 0);
|
|||||||
// charRow = \uC0AC\uC6A9\uD560 sprite row (0~7). \uCE90\uB9AD\uD130 \uC678\uBAA8/\uBC29\uD5A5.
|
// charRow = \uC0AC\uC6A9\uD560 sprite row (0~7). \uCE90\uB9AD\uD130 \uC678\uBAA8/\uBC29\uD5A5.
|
||||||
// deskSprite= \uCC45\uC0C1 PNG \uC774\uB984 (desk-main / desk-boss / desk-dark-mirror \uB4F1).
|
// deskSprite= \uCC45\uC0C1 PNG \uC774\uB984 (desk-main / desk-boss / desk-dark-mirror \uB4F1).
|
||||||
// objs: \uD504\uB78D \uC815\uC758 \uBC30\uC5F4 (let). user-add/remove.
|
// objs: \uD504\uB78D \uC815\uC758 \uBC30\uC5F4 (let). user-add/remove.
|
||||||
const DEFAULT_STATIONS=[
|
// 기본 장면은 더 이상 "가짜 8인 사무실"을 깔지 않는다.
|
||||||
{key:'ceo',agentKey:'ceo',label:'CEO',charRow:0,deskSprite:'desk-boss',deskX:304,deskY:84,deskW:136,seatX:331,seatY:115,face:'R',dock:[362,164],roam:[[320,196],[396,196]],boss:true},
|
// 실제 roster 가 들어오면 그 인원만으로 장면을 짓고, roster 가 비면 빈 오피스 그대로 둔다.
|
||||||
{key:'planner',agentKey:'planner',label:'\uAE30\uD68D',charRow:1,deskSprite:'desk-main',deskX:60,deskY:228,deskW:112,seatX:64,seatY:264,face:'R',dock:[96,322],roam:[[154,350],[200,330]]},
|
const DEFAULT_STATIONS=[];
|
||||||
{key:'researcher',agentKey:'researcher',label:'\uB9AC\uC11C\uCE58',charRow:2,deskSprite:'desk-dark-mirror',deskX:236,deskY:214,deskW:112,seatX:284,seatY:248,face:'L',dock:[304,310],roam:[[322,340],[344,322]]},
|
const LEGACY_SHOWROOM_POS={
|
||||||
{key:'designer',agentKey:'designer',label:'\uB514\uC790\uC778',charRow:3,deskSprite:'desk-main',deskX:402,deskY:240,deskW:112,seatX:406,seatY:276,face:'R',dock:[438,334],roam:[[492,350],[520,326]]},
|
ceo:[304,84],planner:[60,228],researcher:[236,214],designer:[402,240],
|
||||||
{key:'developer',agentKey:'developer',label:'\uAC1C\uBC1C',charRow:4,deskSprite:'desk-dark-mirror',deskX:56,deskY:410,deskW:112,seatX:104,seatY:442,face:'L',dock:[124,500],roam:[[150,534],[188,518]]},
|
developer:[56,410],qa:[232,394],inspector:[408,420],support:[220,472],
|
||||||
{key:'qa',agentKey:'qa',label:'QA',charRow:5,deskSprite:'desk-main',deskX:232,deskY:394,deskW:112,seatX:236,seatY:430,face:'R',dock:[268,486],roam:[[320,520],[352,500]]},
|
};
|
||||||
{key:'inspector',agentKey:'inspector',label:'\uAC10\uB9AC',charRow:6,deskSprite:'desk-dark-mirror',deskX:408,deskY:420,deskW:112,seatX:456,seatY:452,face:'L',dock:[476,506],roam:[[506,532],[540,502]]},
|
|
||||||
{key:'support',agentKey:'support',label:'\uC9C0\uC6D0',charRow:7,deskSprite:'desk-main',deskX:220,deskY:472,deskW:112,seatX:224,seatY:504,face:'R',dock:[256,548],roam:[[360,548],[484,540]]},
|
|
||||||
];
|
|
||||||
// \uC5D0\uC774\uC804\uD2B8 ID alias \u2014 \uAC19\uC740 \uD398\uB974\uC18C\uB098\uC758 \uB2E4\uC591\uD55C \uD638\uCE6D\uC744 \uAC19\uC740 book agentKey \uB85C. (writer\u2192planner, editor\u2192designer, secretary\u2192support, business\u2192inspector)
|
// \uC5D0\uC774\uC804\uD2B8 ID alias \u2014 \uAC19\uC740 \uD398\uB974\uC18C\uB098\uC758 \uB2E4\uC591\uD55C \uD638\uCE6D\uC744 \uAC19\uC740 book agentKey \uB85C. (writer\u2192planner, editor\u2192designer, secretary\u2192support, business\u2192inspector)
|
||||||
const AGENT_ALIASES={writer:'planner',editor:'designer',secretary:'support',business:'inspector'};
|
const AGENT_ALIASES={writer:'planner',editor:'designer',secretary:'support',business:'inspector'};
|
||||||
// \uB9E4\uD551 dropdown \uC5D0 \uBCF4\uC5EC\uC904 \uC5D0\uC774\uC804\uD2B8 \uD6C4\uBCF4. agentKey \uAC00 unique \uD55C base set.
|
// \uB9E4\uD551 dropdown \uC5D0 \uBCF4\uC5EC\uC904 \uC5D0\uC774\uC804\uD2B8 \uD6C4\uBCF4. agentKey \uAC00 unique \uD55C base set.
|
||||||
@@ -47,17 +44,14 @@ const AGENT_CHOICES=['','ceo','planner','researcher','designer','developer','qa'
|
|||||||
const DESK_SPRITE_CHOICES=['desk-main','desk-boss','desk-dark-mirror','desk-main-mirror','desk-dark','desk-boss-mirror','desk-main-front','desk-dark-front','desk-boss-front','desk-partition'];
|
const DESK_SPRITE_CHOICES=['desk-main','desk-boss','desk-dark-mirror','desk-main-mirror','desk-dark','desk-boss-mirror','desk-main-front','desk-dark-front','desk-boss-front','desk-partition'];
|
||||||
// \uCD94\uAC00 \uAC00\uB2A5\uD55C \uD504\uB78D sprite \uD6C4\uBCF4.
|
// \uCD94\uAC00 \uAC00\uB2A5\uD55C \uD504\uB78D sprite \uD6C4\uBCF4.
|
||||||
const PROP_SPRITE_CHOICES=['board','plant-tall','bookshelf','plant-bushy','partition','cooler','filing','couch','rug','shelf','printer','monitor-blue','monitor-black','chair-blue','crt'];
|
const PROP_SPRITE_CHOICES=['board','plant-tall','bookshelf','plant-bushy','partition','cooler','filing','couch','rug','shelf','printer','monitor-blue','monitor-black','chair-blue','crt'];
|
||||||
const DEFAULT_PROPS=[
|
// 후면 배경 자체가 이미 충분히 강하므로, 목적 없는 프랍은 기본 장면에서 제거.
|
||||||
{name:'plant-tall',x:40,y:118,w:42},{name:'bookshelf',x:86,y:88,w:54},
|
// 커스텀 편집 모드에선 여전히 사용자가 원하는 프랍을 직접 추가할 수 있다.
|
||||||
{name:'plant-bushy',x:640,y:118,w:42},{name:'cooler',x:646,y:286,w:38},
|
const DEFAULT_PROPS=[];
|
||||||
{name:'filing',x:618,y:374,w:42},{name:'couch',x:584,y:452,w:96},
|
|
||||||
{name:'rug',x:560,y:514,w:126},{name:'printer',x:520,y:526,w:58},
|
|
||||||
];
|
|
||||||
|
|
||||||
let stations=[]; // mutable, \uC2DC\uC791 \uC2DC default \uB610\uB294 saved layout \uB85C \uCC44\uC6C0.
|
let stations=[]; // mutable, \uC2DC\uC791 \uC2DC default \uB610\uB294 saved layout \uB85C \uCC44\uC6C0.
|
||||||
let _hasSavedLayout=false;
|
let _hasSavedLayout=false;
|
||||||
let _layoutSource='default';
|
let _layoutSource='default';
|
||||||
let _lastPipelineLayoutSignature='';
|
let _lastAutoLayoutSignature='';
|
||||||
let _latestSnapshot=null;
|
let _latestSnapshot=null;
|
||||||
let __nextDeskN=100; // user-add \uCC45\uC0C1 id \uCE74\uC6B4\uD130 (default \uC640 \uCDA9\uB3CC \uD68C\uD53C \uC704\uD574 \uD070 \uC218\uC5D0\uC11C \uC2DC\uC791).
|
let __nextDeskN=100; // user-add \uCC45\uC0C1 id \uCE74\uC6B4\uD130 (default \uC640 \uCDA9\uB3CC \uD68C\uD53C \uC704\uD574 \uD070 \uC218\uC5D0\uC11C \uC2DC\uC791).
|
||||||
let __nextObjN=0;
|
let __nextObjN=0;
|
||||||
@@ -65,6 +59,8 @@ const stationByKey={}; // \uBE60\uB978 lookup. stations \uBCC0\uACBD \uC2DC rebu
|
|||||||
const __deskWrap={}; // role \u2192 desk DOM wrap.
|
const __deskWrap={}; // role \u2192 desk DOM wrap.
|
||||||
const chars={}; // role \u2192 char DOM.
|
const chars={}; // role \u2192 char DOM.
|
||||||
const anim={}; // role \u2192 animation state.
|
const anim={}; // role \u2192 animation state.
|
||||||
|
// 한 번 처리된 agentId 는 여기에 기록 → 사용자가 그 desk 를 지워도 자동 재생성 안 함.
|
||||||
|
const _autoDeskedFor = new Set();
|
||||||
|
|
||||||
function png(name){return base+'/'+name+'.png'}
|
function png(name){return base+'/'+name+'.png'}
|
||||||
|
|
||||||
@@ -118,36 +114,47 @@ function _pipelineRosterOrder(roster, pipeline){
|
|||||||
}
|
}
|
||||||
function _stationSpriteFor(slot, roleCategory){
|
function _stationSpriteFor(slot, roleCategory){
|
||||||
if(roleCategory==='ceo') return 'desk-boss';
|
if(roleCategory==='ceo') return 'desk-boss';
|
||||||
return slot >= 4 ? 'desk-dark-mirror' : (slot % 2 ? 'desk-dark' : 'desk-main');
|
return slot % 2 ? 'desk-dark' : 'desk-main';
|
||||||
}
|
}
|
||||||
function _stationSlot(slot){
|
function _stationSlots(count){
|
||||||
const path=[
|
const rows=[];
|
||||||
[72,220,'R'],[214,210,'R'],[356,220,'R'],[498,210,'R'],
|
if(count <= 0) return rows;
|
||||||
[498,394,'L'],[356,404,'L'],[214,394,'L'],[72,404,'L'],
|
const cols = count <= 2 ? count : (count >= 7 ? 4 : 3);
|
||||||
];
|
const rowCount = Math.ceil(count / cols);
|
||||||
if(path[slot]) return path[slot];
|
const xSets = {
|
||||||
const extra=slot-path.length;
|
1:[308],
|
||||||
const cols=4;
|
2:[216,400],
|
||||||
return [54+(extra%cols)*144, 42+Math.floor(extra/cols)*86, extra%2?'L':'R'];
|
3:[124,308,492],
|
||||||
|
4:[78,230,382,534],
|
||||||
|
};
|
||||||
|
for(let row=0; row<rowCount; row++){
|
||||||
|
const remaining = count - row*cols;
|
||||||
|
const inRow = Math.min(cols, remaining);
|
||||||
|
const xs = xSets[inRow] || xSets[3];
|
||||||
|
const rowYs = rowCount === 1 ? [330] : (rowCount === 2 ? [304,432] : [270,380,490]);
|
||||||
|
const y = rowYs[row] ?? (490 + Math.max(0,row-rowYs.length+1)*88);
|
||||||
|
xs.forEach((x)=>rows.push([x,y,'R']));
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
}
|
}
|
||||||
function _makeStationForRosterAgent(agent, slot){
|
function _makeStationForRosterAgent(agent, slot, slots){
|
||||||
if(agent.roleCategory==='ceo' || agent.agentId==='ceo'){
|
if(agent.roleCategory==='ceo' || agent.agentId==='ceo'){
|
||||||
return {
|
return {
|
||||||
key:'ceo',agentKey:agent.agentId,label:agent.agentName||'CEO',charRow:_roleCategoryToCharRow(agent.roleCategory),
|
key:'ceo',agentKey:agent.agentId,label:agent.agentName||'CEO',charRow:_roleCategoryToCharRow(agent.roleCategory),
|
||||||
deskSprite:'desk-boss',deskX:292,deskY:82,deskW:136,seatX:319,seatY:113,face:'R',
|
deskSprite:'desk-boss',deskX:294,deskY:188,deskW:124,seatX:319,seatY:218,face:'R',
|
||||||
dock:[350,164],roam:[[306,196],[396,196]],boss:true,
|
dock:[350,268],roam:[[304,280],[402,280]],boss:true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const [deskX,deskY,face]=_stationSlot(slot);
|
const [deskX,deskY,face]=(slots && slots[slot]) || [308,318,'R'];
|
||||||
const deskW=112;
|
const deskW=104;
|
||||||
const seatX=face==='L' ? deskX+48 : deskX+4;
|
const seatX=deskX+4;
|
||||||
const seatY=deskY+36;
|
const seatY=deskY+36;
|
||||||
return {
|
return {
|
||||||
key:_safeDeskKey(agent.agentId),agentKey:agent.agentId,label:agent.agentName||agent.agentId,
|
key:_safeDeskKey(agent.agentId),agentKey:agent.agentId,label:agent.agentName||agent.agentId,
|
||||||
charRow:_roleCategoryToCharRow(agent.roleCategory),deskSprite:_stationSpriteFor(slot,agent.roleCategory),
|
charRow:_roleCategoryToCharRow(agent.roleCategory),deskSprite:_stationSpriteFor(slot,agent.roleCategory),
|
||||||
deskX,deskY,deskW,seatX,seatY,face,boss:false,
|
deskX,deskY,deskW,seatX,seatY,face,boss:false,
|
||||||
dock:[deskX+(face==='L'?72:32),deskY+82],
|
dock:[deskX+32,deskY+82],
|
||||||
roam:[[deskX+18,deskY+116],[deskX+78,deskY+102]],
|
roam:[[deskX+12,deskY+112],[deskX+74,deskY+104]],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function _rebuildScene(nextStations, props){
|
function _rebuildScene(nextStations, props){
|
||||||
@@ -160,31 +167,41 @@ function _rebuildScene(nextStations, props){
|
|||||||
function _restoreDefaultScene(){
|
function _restoreDefaultScene(){
|
||||||
_rebuildScene(DEFAULT_STATIONS,DEFAULT_PROPS);
|
_rebuildScene(DEFAULT_STATIONS,DEFAULT_PROPS);
|
||||||
_layoutSource='default';
|
_layoutSource='default';
|
||||||
_lastPipelineLayoutSignature='';
|
_lastAutoLayoutSignature='';
|
||||||
const shell=document.querySelector('.office-shell');
|
const shell=document.querySelector('.office-shell');
|
||||||
if(shell) shell.dataset.layout='default';
|
if(shell) shell.dataset.layout='default';
|
||||||
}
|
}
|
||||||
function _syncPipelineLayout(pipeline, roster){
|
function _looksLikeLegacyShowroomLayout(snap){
|
||||||
|
if(!snap || !Array.isArray(snap.cells) || snap.cells.length!==8) return false;
|
||||||
|
return snap.cells.every(c=>{
|
||||||
|
const legacy=LEGACY_SHOWROOM_POS[c && c.roleKey];
|
||||||
|
return !!legacy
|
||||||
|
&& Math.abs((c.deskX||0)-legacy[0])<=2
|
||||||
|
&& Math.abs((c.deskY||0)-legacy[1])<=2;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function _syncOfficeLayout(pipeline, roster){
|
||||||
if(_hasSavedLayout || _editMode) return;
|
if(_hasSavedLayout || _editMode) return;
|
||||||
const stages=pipeline && Array.isArray(pipeline.stages) ? pipeline.stages : [];
|
const stages=pipeline && Array.isArray(pipeline.stages) ? pipeline.stages : [];
|
||||||
if(!stages.length){
|
const ordered=stages.length ? _pipelineRosterOrder(roster,pipeline) : _uniqueAgentsById(roster||[]);
|
||||||
if(_layoutSource==='pipeline') _restoreDefaultScene();
|
if(!ordered.length){
|
||||||
|
if(_layoutSource!=='default') _restoreDefaultScene();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ordered=_pipelineRosterOrder(roster,pipeline);
|
|
||||||
const signature=ordered.map(r=>r.agentId).join('|')+'::'+stages.map(s=>(s.label||'')+':'+(_resolvedStageAgentId(s,ordered)||'')).join('|');
|
const signature=ordered.map(r=>r.agentId).join('|')+'::'+stages.map(s=>(s.label||'')+':'+(_resolvedStageAgentId(s,ordered)||'')).join('|');
|
||||||
if(_layoutSource==='pipeline' && signature===_lastPipelineLayoutSignature) return;
|
if(_layoutSource==='roster' && signature===_lastAutoLayoutSignature) return;
|
||||||
const ceo=ordered.find(r=>r.agentId==='ceo' || r.roleCategory==='ceo');
|
const ceo=ordered.find(r=>r.agentId==='ceo' || r.roleCategory==='ceo');
|
||||||
const workers=ordered.filter(r=>r!==ceo);
|
const workers=ordered.filter(r=>r!==ceo);
|
||||||
|
const slots=_stationSlots(workers.length);
|
||||||
const next=[];
|
const next=[];
|
||||||
if(ceo) next.push(_makeStationForRosterAgent(ceo,0));
|
if(ceo) next.push(_makeStationForRosterAgent(ceo,0,slots));
|
||||||
workers.forEach((agent,idx)=>next.push(_makeStationForRosterAgent(agent,idx)));
|
workers.forEach((agent,idx)=>next.push(_makeStationForRosterAgent(agent,idx,slots)));
|
||||||
if(next.length){
|
if(next.length){
|
||||||
_rebuildScene(next,DEFAULT_PROPS);
|
_rebuildScene(next,DEFAULT_PROPS);
|
||||||
_layoutSource='pipeline';
|
_layoutSource='roster';
|
||||||
_lastPipelineLayoutSignature=signature;
|
_lastAutoLayoutSignature=signature;
|
||||||
const shell=document.querySelector('.office-shell');
|
const shell=document.querySelector('.office-shell');
|
||||||
if(shell) shell.dataset.layout='pipeline';
|
if(shell) shell.dataset.layout=stages.length?'pipeline':'roster';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function _clearPipelineDecor(){
|
function _clearPipelineDecor(){
|
||||||
@@ -317,14 +334,7 @@ function addChar(st){
|
|||||||
|
|
||||||
function buildStation(st){ addDesk(st); if(!st.noChar) addChar(st); }
|
function buildStation(st){ addDesk(st); if(!st.noChar) addChar(st); }
|
||||||
|
|
||||||
function _renderDefaultStations(){
|
_restoreDefaultScene();
|
||||||
// default 8 stations + default props.
|
|
||||||
stations = DEFAULT_STATIONS.map(s=>Object.assign({},s));
|
|
||||||
_rebuildStationIndex();
|
|
||||||
stations.forEach(buildStation);
|
|
||||||
DEFAULT_PROPS.forEach(p=>addImg(p.name,p.x,p.y,p.w));
|
|
||||||
}
|
|
||||||
_renderDefaultStations();
|
|
||||||
// ── 4방향 dir 매핑 ──
|
// ── 4방향 dir 매핑 ──
|
||||||
// PNG 파일명 규약: walk-r<row>-d<DIR>-f<frame>.png
|
// PNG 파일명 규약: walk-r<row>-d<DIR>-f<frame>.png
|
||||||
// 사용자의 sprite 컨벤션이 다르면 이 4 상수만 수정하면 됨.
|
// 사용자의 sprite 컨벤션이 다르면 이 4 상수만 수정하면 됨.
|
||||||
@@ -898,8 +908,6 @@ function _previewAgent(agentId, on, item){
|
|||||||
if(ch) ch.classList.toggle('preview', !!on);
|
if(ch) ch.classList.toggle('preview', !!on);
|
||||||
}
|
}
|
||||||
// refactor #G-full — roster 에 있는 agent 중 desk 가 없는 경우 자동 생성.
|
// refactor #G-full — roster 에 있는 agent 중 desk 가 없는 경우 자동 생성.
|
||||||
// 한 번 처리된 agentId 는 _autoDeskedFor 에 기록 → 사용자가 그 desk 를 지워도 재생성 안 함.
|
|
||||||
const _autoDeskedFor = new Set();
|
|
||||||
function _roleCategoryToCharRow(role){
|
function _roleCategoryToCharRow(role){
|
||||||
switch(role){
|
switch(role){
|
||||||
case 'ceo': return 0;
|
case 'ceo': return 0;
|
||||||
@@ -966,8 +974,8 @@ function applyFromSnapshot(snap){
|
|||||||
const roster = Array.isArray(snap.roster) ? snap.roster : [];
|
const roster = Array.isArray(snap.roster) ? snap.roster : [];
|
||||||
const orderedRoster = _pipelineRosterOrder(roster, snap.pipeline);
|
const orderedRoster = _pipelineRosterOrder(roster, snap.pipeline);
|
||||||
_renderRoster(orderedRoster, snap.activeAgentId);
|
_renderRoster(orderedRoster, snap.activeAgentId);
|
||||||
_syncPipelineLayout(snap.pipeline, orderedRoster);
|
_syncOfficeLayout(snap.pipeline, orderedRoster);
|
||||||
if(_layoutSource!=='pipeline' || _hasSavedLayout) _ensureRosterDesks(orderedRoster);
|
if(_layoutSource!=='roster' || _hasSavedLayout) _ensureRosterDesks(orderedRoster);
|
||||||
_renderPipelineDecor(snap.pipeline, orderedRoster);
|
_renderPipelineDecor(snap.pipeline, orderedRoster);
|
||||||
const active = (snap.activeAgentId && roster.find(a => a.agentId === snap.activeAgentId)) || roster[0];
|
const active = (snap.activeAgentId && roster.find(a => a.agentId === snap.activeAgentId)) || roster[0];
|
||||||
const activeStage = snap.pipeline && Array.isArray(snap.pipeline.stages)
|
const activeStage = snap.pipeline && Array.isArray(snap.pipeline.stages)
|
||||||
@@ -1570,12 +1578,13 @@ window.addEventListener('message', e=>{
|
|||||||
const d = e.data;
|
const d = e.data;
|
||||||
if(!d || typeof d !== 'object') return;
|
if(!d || typeof d !== 'object') return;
|
||||||
if(d.type === 'pixelOfficeLayoutLoaded'){
|
if(d.type === 'pixelOfficeLayoutLoaded'){
|
||||||
_hasSavedLayout = !!d.value;
|
const saved = d.value && !_looksLikeLegacyShowroomLayout(d.value) ? d.value : null;
|
||||||
if(d.value) {
|
_hasSavedLayout = !!saved;
|
||||||
|
if(saved) {
|
||||||
_layoutSource = 'saved';
|
_layoutSource = 'saved';
|
||||||
_restoreLayout(d.value);
|
_restoreLayout(saved);
|
||||||
} else if(_latestSnapshot) {
|
} else if(_latestSnapshot) {
|
||||||
_syncPipelineLayout(_latestSnapshot.pipeline, _pipelineRosterOrder(_latestSnapshot.roster || [], _latestSnapshot.pipeline));
|
_syncOfficeLayout(_latestSnapshot.pipeline, _pipelineRosterOrder(_latestSnapshot.roster || [], _latestSnapshot.pipeline));
|
||||||
_renderPipelineDecor(_latestSnapshot.pipeline, _latestSnapshot.roster || []);
|
_renderPipelineDecor(_latestSnapshot.pipeline, _latestSnapshot.roster || []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user