gemkit-cli 0.2.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +141 -7
  2. package/dist/commands/agent/index.d.ts +9 -0
  3. package/dist/commands/agent/index.js +1329 -0
  4. package/dist/commands/cache/index.d.ts +5 -0
  5. package/dist/commands/cache/index.js +43 -0
  6. package/dist/commands/catalog/index.d.ts +2 -0
  7. package/dist/commands/catalog/index.js +57 -0
  8. package/dist/commands/config/index.d.ts +7 -0
  9. package/dist/commands/config/index.js +122 -0
  10. package/dist/commands/convert/index.d.ts +8 -0
  11. package/dist/commands/convert/index.js +391 -0
  12. package/dist/commands/doctor/index.d.ts +2 -0
  13. package/dist/commands/doctor/index.js +243 -0
  14. package/dist/commands/extension/index.d.ts +5 -0
  15. package/dist/commands/extension/index.js +52 -0
  16. package/dist/commands/index.d.ts +5 -0
  17. package/dist/commands/index.js +37 -0
  18. package/dist/commands/init/index.d.ts +6 -0
  19. package/dist/commands/init/index.js +345 -0
  20. package/dist/commands/new/index.d.ts +5 -0
  21. package/dist/commands/new/index.js +49 -0
  22. package/dist/commands/office/index.d.ts +5 -0
  23. package/dist/commands/office/index.js +283 -0
  24. package/dist/commands/paste/index.d.ts +10 -0
  25. package/dist/commands/paste/index.js +533 -0
  26. package/dist/commands/plan/index.d.ts +8 -0
  27. package/dist/commands/plan/index.js +247 -0
  28. package/dist/commands/session/index.d.ts +8 -0
  29. package/dist/commands/session/index.js +289 -0
  30. package/dist/commands/tokens/index.d.ts +6 -0
  31. package/dist/commands/tokens/index.js +148 -0
  32. package/dist/commands/update/index.d.ts +26 -0
  33. package/dist/commands/update/index.js +199 -0
  34. package/dist/commands/versions/index.d.ts +5 -0
  35. package/dist/commands/versions/index.js +39 -0
  36. package/dist/domains/agent/index.d.ts +8 -0
  37. package/dist/domains/agent/index.js +8 -0
  38. package/dist/domains/agent/mappings.d.ts +32 -0
  39. package/dist/domains/agent/mappings.js +164 -0
  40. package/dist/domains/agent/profile.d.ts +26 -0
  41. package/dist/domains/agent/profile.js +225 -0
  42. package/dist/domains/agent/pty-context.d.ts +11 -0
  43. package/dist/domains/agent/pty-context.js +83 -0
  44. package/dist/domains/agent/pty-providers.d.ts +18 -0
  45. package/dist/domains/agent/pty-providers.js +66 -0
  46. package/dist/domains/agent/pty-session.d.ts +33 -0
  47. package/dist/domains/agent/pty-session.js +82 -0
  48. package/dist/domains/agent/pty-types.d.ts +127 -0
  49. package/dist/domains/agent/pty-types.js +4 -0
  50. package/dist/domains/agent/search.d.ts +45 -0
  51. package/dist/domains/agent/search.js +614 -0
  52. package/dist/domains/agent/types.d.ts +78 -0
  53. package/dist/domains/agent/types.js +5 -0
  54. package/dist/domains/agent-office/documents-scanner.d.ts +9 -0
  55. package/dist/domains/agent-office/documents-scanner.js +143 -0
  56. package/dist/domains/agent-office/event-emitter.d.ts +43 -0
  57. package/dist/domains/agent-office/event-emitter.js +86 -0
  58. package/dist/domains/agent-office/file-watcher.d.ts +40 -0
  59. package/dist/domains/agent-office/file-watcher.js +173 -0
  60. package/dist/domains/agent-office/icons.d.ts +11 -0
  61. package/dist/domains/agent-office/icons.js +36 -0
  62. package/dist/domains/agent-office/index.d.ts +12 -0
  63. package/dist/domains/agent-office/index.js +20 -0
  64. package/dist/domains/agent-office/renderer/web/assets.d.ts +11 -0
  65. package/dist/domains/agent-office/renderer/web/assets.js +3419 -0
  66. package/dist/domains/agent-office/renderer/web/server.d.ts +42 -0
  67. package/dist/domains/agent-office/renderer/web/server.js +228 -0
  68. package/dist/domains/agent-office/renderer/web.d.ts +30 -0
  69. package/dist/domains/agent-office/renderer/web.js +111 -0
  70. package/dist/domains/agent-office/session-bridge.d.ts +23 -0
  71. package/dist/domains/agent-office/session-bridge.js +171 -0
  72. package/dist/domains/agent-office/state-machine.d.ts +5 -0
  73. package/dist/domains/agent-office/state-machine.js +82 -0
  74. package/dist/domains/agent-office/types.d.ts +91 -0
  75. package/dist/domains/agent-office/types.js +4 -0
  76. package/dist/domains/cache/index.d.ts +1 -0
  77. package/dist/domains/cache/index.js +1 -0
  78. package/dist/domains/cache/manager.d.ts +22 -0
  79. package/dist/domains/cache/manager.js +84 -0
  80. package/dist/domains/config/index.d.ts +5 -0
  81. package/dist/domains/config/index.js +5 -0
  82. package/dist/domains/config/manager.d.ts +24 -0
  83. package/dist/domains/config/manager.js +85 -0
  84. package/dist/domains/config/schema.d.ts +17 -0
  85. package/dist/domains/config/schema.js +96 -0
  86. package/dist/domains/convert/converter.d.ts +78 -0
  87. package/dist/domains/convert/converter.js +471 -0
  88. package/dist/domains/convert/index.d.ts +5 -0
  89. package/dist/domains/convert/index.js +5 -0
  90. package/dist/domains/convert/types.d.ts +88 -0
  91. package/dist/domains/convert/types.js +18 -0
  92. package/dist/domains/github/download.d.ts +12 -0
  93. package/dist/domains/github/download.js +51 -0
  94. package/dist/domains/github/index.d.ts +2 -0
  95. package/dist/domains/github/index.js +2 -0
  96. package/dist/domains/github/releases.d.ts +16 -0
  97. package/dist/domains/github/releases.js +68 -0
  98. package/dist/domains/installation/conflict.d.ts +13 -0
  99. package/dist/domains/installation/conflict.js +38 -0
  100. package/dist/domains/installation/file-sync.d.ts +16 -0
  101. package/dist/domains/installation/file-sync.js +77 -0
  102. package/dist/domains/installation/index.d.ts +3 -0
  103. package/dist/domains/installation/index.js +3 -0
  104. package/dist/domains/installation/metadata.d.ts +20 -0
  105. package/dist/domains/installation/metadata.js +52 -0
  106. package/dist/domains/plan/index.d.ts +2 -0
  107. package/dist/domains/plan/index.js +2 -0
  108. package/dist/domains/plan/resolver.d.ts +24 -0
  109. package/dist/domains/plan/resolver.js +164 -0
  110. package/dist/domains/plan/types.d.ts +13 -0
  111. package/dist/domains/plan/types.js +4 -0
  112. package/dist/domains/session/env.d.ts +51 -0
  113. package/dist/domains/session/env.js +118 -0
  114. package/dist/domains/session/index.d.ts +8 -0
  115. package/dist/domains/session/index.js +8 -0
  116. package/dist/domains/session/manager.d.ts +56 -0
  117. package/dist/domains/session/manager.js +205 -0
  118. package/dist/domains/session/paths.d.ts +6 -0
  119. package/dist/domains/session/paths.js +6 -0
  120. package/dist/domains/session/types.d.ts +121 -0
  121. package/dist/domains/session/types.js +5 -0
  122. package/dist/domains/session/writer.d.ts +82 -0
  123. package/dist/domains/session/writer.js +431 -0
  124. package/dist/domains/tokens/index.d.ts +5 -0
  125. package/dist/domains/tokens/index.js +5 -0
  126. package/dist/domains/tokens/pricing.d.ts +38 -0
  127. package/dist/domains/tokens/pricing.js +129 -0
  128. package/dist/domains/tokens/scanner.d.ts +42 -0
  129. package/dist/domains/tokens/scanner.js +168 -0
  130. package/dist/index.d.ts +5 -0
  131. package/dist/index.js +90 -59
  132. package/dist/services/aipty.d.ts +76 -0
  133. package/dist/services/aipty.js +276 -0
  134. package/dist/services/archive.d.ts +22 -0
  135. package/dist/services/archive.js +53 -0
  136. package/dist/services/auto-update.d.ts +26 -0
  137. package/dist/services/auto-update.js +117 -0
  138. package/dist/services/hash.d.ts +36 -0
  139. package/dist/services/hash.js +63 -0
  140. package/dist/services/logger.d.ts +28 -0
  141. package/dist/services/logger.js +102 -0
  142. package/dist/services/music.d.ts +67 -0
  143. package/dist/services/music.js +290 -0
  144. package/dist/services/npm.d.ts +22 -0
  145. package/dist/services/npm.js +65 -0
  146. package/dist/services/pty-client.d.ts +66 -0
  147. package/dist/services/pty-client.js +154 -0
  148. package/dist/services/pty-server.d.ts +102 -0
  149. package/dist/services/pty-server.js +613 -0
  150. package/dist/types/index.d.ts +155 -0
  151. package/dist/types/index.js +4 -0
  152. package/dist/utils/colors.d.ts +43 -0
  153. package/dist/utils/colors.js +98 -0
  154. package/dist/utils/errors.d.ts +24 -0
  155. package/dist/utils/errors.js +56 -0
  156. package/dist/utils/paths.d.ts +46 -0
  157. package/dist/utils/paths.js +89 -0
  158. package/dist/utils/platform.d.ts +11 -0
  159. package/dist/utils/platform.js +31 -0
  160. package/package.json +55 -54
@@ -0,0 +1,42 @@
1
+ import { OfficeEventEmitter } from '../../event-emitter.js';
2
+ export interface WebServerOptions {
3
+ port: number;
4
+ host?: string;
5
+ autoOpen?: boolean;
6
+ emitter: OfficeEventEmitter;
7
+ }
8
+ export declare class OfficeWebServer {
9
+ private server;
10
+ private wss;
11
+ private options;
12
+ private clients;
13
+ constructor(options: WebServerOptions);
14
+ /**
15
+ * Start the server
16
+ */
17
+ start(): Promise<number>;
18
+ /**
19
+ * Stop the server
20
+ */
21
+ stop(): void;
22
+ /**
23
+ * Handle HTTP requests
24
+ */
25
+ private handleRequest;
26
+ /**
27
+ * Setup WebSocket handlers
28
+ */
29
+ private setupWebSocket;
30
+ /**
31
+ * Broadcast state to all clients
32
+ */
33
+ private broadcastState;
34
+ /**
35
+ * Broadcast event to all clients
36
+ */
37
+ private broadcastEvent;
38
+ /**
39
+ * Serialize state for JSON (convert Map to object)
40
+ */
41
+ private serializeState;
42
+ }
@@ -0,0 +1,228 @@
1
+ import { createServer } from 'http';
2
+ import { WebSocketServer, WebSocket } from 'ws';
3
+ import { exec } from 'child_process';
4
+ import { existsSync } from 'fs';
5
+ import { resolve } from 'path';
6
+ import { getIndexHtml } from './assets.js';
7
+ export class OfficeWebServer {
8
+ server = null;
9
+ wss = null;
10
+ options;
11
+ clients = new Set();
12
+ constructor(options) {
13
+ this.options = options;
14
+ }
15
+ /**
16
+ * Start the server
17
+ */
18
+ async start() {
19
+ return new Promise((resolve, reject) => {
20
+ let port = this.options.port;
21
+ let attempts = 0;
22
+ const maxAttempts = 10;
23
+ const tryStart = () => {
24
+ this.server = createServer((req, res) => this.handleRequest(req, res));
25
+ this.server.on('error', (err) => {
26
+ if (err.code === 'EADDRINUSE' && attempts < maxAttempts) {
27
+ attempts++;
28
+ port++;
29
+ tryStart();
30
+ }
31
+ else {
32
+ reject(err);
33
+ }
34
+ });
35
+ this.server.listen(port, this.options.host || 'localhost', () => {
36
+ // Setup WebSocket server
37
+ this.wss = new WebSocketServer({ server: this.server });
38
+ this.setupWebSocket();
39
+ // Subscribe to state changes
40
+ this.options.emitter.onStateChange((state) => {
41
+ this.broadcastState(state);
42
+ });
43
+ this.options.emitter.onEvent((event) => {
44
+ this.broadcastEvent(event);
45
+ });
46
+ resolve(port);
47
+ });
48
+ };
49
+ tryStart();
50
+ });
51
+ }
52
+ /**
53
+ * Stop the server
54
+ */
55
+ stop() {
56
+ if (this.wss) {
57
+ for (const client of this.clients) {
58
+ client.close();
59
+ }
60
+ this.wss.close();
61
+ this.wss = null;
62
+ }
63
+ if (this.server) {
64
+ this.server.close();
65
+ this.server = null;
66
+ }
67
+ this.clients.clear();
68
+ }
69
+ /**
70
+ * Handle HTTP requests
71
+ */
72
+ handleRequest(req, res) {
73
+ const url = req.url || '/';
74
+ // API endpoints
75
+ if (url === '/api/state') {
76
+ res.writeHead(200, { 'Content-Type': 'application/json' });
77
+ res.end(JSON.stringify(this.serializeState(this.options.emitter.getState())));
78
+ return;
79
+ }
80
+ if (url === '/api/history') {
81
+ res.writeHead(200, { 'Content-Type': 'application/json' });
82
+ res.end(JSON.stringify(this.options.emitter.getHistory()));
83
+ return;
84
+ }
85
+ // Open document API
86
+ if (url.startsWith('/api/open-doc')) {
87
+ const urlObj = new URL(url, `http://${req.headers.host}`);
88
+ const docPath = urlObj.searchParams.get('path');
89
+ if (!docPath) {
90
+ res.writeHead(400, { 'Content-Type': 'application/json' });
91
+ res.end(JSON.stringify({ success: false, error: 'No path provided' }));
92
+ return;
93
+ }
94
+ // Path could be absolute or relative - try both
95
+ let fullPath = docPath;
96
+ if (!existsSync(fullPath)) {
97
+ fullPath = resolve(process.cwd(), docPath);
98
+ }
99
+ if (!existsSync(fullPath)) {
100
+ res.writeHead(404, { 'Content-Type': 'application/json' });
101
+ res.end(JSON.stringify({ success: false, error: `File not found: ${docPath}` }));
102
+ return;
103
+ }
104
+ // Detect IDE from multiple sources
105
+ // Priority: EDITOR env var > session appName (if valid) > code (fallback)
106
+ const editor = process.env.EDITOR || process.env.VISUAL;
107
+ const state = this.options.emitter.getState();
108
+ const sessionAppName = state.appName?.toLowerCase() || '';
109
+ // Valid IDE commands whitelist
110
+ const validIdes = ['code', 'cursor', 'windsurf', 'zed', 'vim', 'nvim', 'nano', 'subl', 'atom', 'idea', 'webstorm', 'notepad++', 'antigravity'];
111
+ const isValidIde = validIdes.some(ide => sessionAppName.includes(ide));
112
+ let command;
113
+ if (editor) {
114
+ // User explicitly set EDITOR env var - use that
115
+ command = `"${editor}" "${fullPath}"`;
116
+ console.log(`[Agent Office] Using IDE from EDITOR env: ${editor}`);
117
+ }
118
+ else if (sessionAppName && isValidIde) {
119
+ // Use appName from session only if it's a valid IDE
120
+ command = `${sessionAppName} "${fullPath}"`;
121
+ console.log(`[Agent Office] Using IDE from session: ${sessionAppName}`);
122
+ }
123
+ else {
124
+ // Fallback to VS Code
125
+ command = `code "${fullPath}"`;
126
+ console.log('[Agent Office] No IDE detected, defaulting to VS Code');
127
+ }
128
+ exec(command, (error) => {
129
+ if (error) {
130
+ res.writeHead(500, { 'Content-Type': 'application/json' });
131
+ res.end(JSON.stringify({ success: false, error: error.message }));
132
+ }
133
+ else {
134
+ res.writeHead(200, { 'Content-Type': 'application/json' });
135
+ res.end(JSON.stringify({ success: true }));
136
+ }
137
+ });
138
+ return;
139
+ }
140
+ // Serve embedded index.html for root path
141
+ const urlPath = url.split('?')[0].split('#')[0];
142
+ if (urlPath === '/' || urlPath === '/index.html') {
143
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
144
+ res.end(getIndexHtml());
145
+ return;
146
+ }
147
+ // All other paths return 404 (CSS/JS are inlined in HTML)
148
+ res.writeHead(404);
149
+ res.end('Not Found');
150
+ }
151
+ /**
152
+ * Setup WebSocket handlers
153
+ */
154
+ setupWebSocket() {
155
+ if (!this.wss)
156
+ return;
157
+ this.wss.on('connection', (ws) => {
158
+ this.clients.add(ws);
159
+ // Send current state immediately
160
+ const state = this.options.emitter.getState();
161
+ try {
162
+ ws.send(JSON.stringify({ type: 'state', data: this.serializeState(state) }));
163
+ }
164
+ catch (e) {
165
+ // Ignore send errors on initial connect
166
+ }
167
+ ws.on('message', (data) => {
168
+ try {
169
+ const msg = JSON.parse(data.toString());
170
+ if (msg.type === 'ping') {
171
+ ws.send(JSON.stringify({ type: 'pong' }));
172
+ }
173
+ if (msg.type === 'replay') {
174
+ const events = this.options.emitter.replay(msg.fromTimestamp);
175
+ ws.send(JSON.stringify({ type: 'replay', data: events }));
176
+ }
177
+ }
178
+ catch (e) {
179
+ // Ignore invalid messages
180
+ }
181
+ });
182
+ ws.on('close', () => {
183
+ this.clients.delete(ws);
184
+ });
185
+ });
186
+ }
187
+ /**
188
+ * Broadcast state to all clients
189
+ */
190
+ broadcastState(state) {
191
+ const message = JSON.stringify({ type: 'state', data: this.serializeState(state) });
192
+ for (const client of this.clients) {
193
+ if (client.readyState === WebSocket.OPEN) {
194
+ try {
195
+ client.send(message);
196
+ }
197
+ catch (e) {
198
+ // Ignore send errors
199
+ }
200
+ }
201
+ }
202
+ }
203
+ /**
204
+ * Broadcast event to all clients
205
+ */
206
+ broadcastEvent(event) {
207
+ const message = JSON.stringify({ type: 'event', data: event });
208
+ for (const client of this.clients) {
209
+ if (client.readyState === WebSocket.OPEN) {
210
+ try {
211
+ client.send(message);
212
+ }
213
+ catch (e) {
214
+ // Ignore send errors
215
+ }
216
+ }
217
+ }
218
+ }
219
+ /**
220
+ * Serialize state for JSON (convert Map to object)
221
+ */
222
+ serializeState(state) {
223
+ return {
224
+ ...state,
225
+ agents: Object.fromEntries(state.agents),
226
+ };
227
+ }
228
+ }
@@ -0,0 +1,30 @@
1
+ export interface WebDashboardOptions {
2
+ port?: number;
3
+ host?: string;
4
+ autoOpen?: boolean;
5
+ onReady?: (port: number) => void;
6
+ onError?: (error: Error) => void;
7
+ }
8
+ export declare class WebDashboard {
9
+ private server;
10
+ private emitter;
11
+ private watcher;
12
+ private options;
13
+ constructor(options?: WebDashboardOptions);
14
+ /**
15
+ * Start the web dashboard
16
+ */
17
+ start(): Promise<number>;
18
+ /**
19
+ * Stop the dashboard
20
+ */
21
+ stop(): void;
22
+ /**
23
+ * Open browser to URL
24
+ */
25
+ private openBrowser;
26
+ }
27
+ /**
28
+ * Start web dashboard (convenience function)
29
+ */
30
+ export declare function startWebDashboard(options?: WebDashboardOptions): Promise<WebDashboard>;
@@ -0,0 +1,111 @@
1
+ import { exec } from 'child_process';
2
+ import { join } from 'path';
3
+ import { OfficeEventEmitter } from '../event-emitter.js';
4
+ import { SessionFileWatcher } from '../file-watcher.js';
5
+ import { sessionToOfficeState } from '../session-bridge.js';
6
+ import { scanPlanDocuments } from '../documents-scanner.js';
7
+ import { OfficeWebServer } from './web/server.js';
8
+ import { createInitialState } from '../state-machine.js';
9
+ import { getPlansDir } from '../../../utils/paths.js';
10
+ export class WebDashboard {
11
+ server = null;
12
+ emitter;
13
+ watcher;
14
+ options;
15
+ constructor(options = {}) {
16
+ this.options = {
17
+ port: 3847,
18
+ host: 'localhost',
19
+ autoOpen: true,
20
+ ...options,
21
+ };
22
+ // Initialize event emitter
23
+ this.emitter = new OfficeEventEmitter(createInitialState());
24
+ // Initialize file watcher
25
+ this.watcher = new SessionFileWatcher({
26
+ onSessionChange: (session) => {
27
+ const state = sessionToOfficeState(session);
28
+ // Scan documents if plan set
29
+ if (state.activePlan) {
30
+ try {
31
+ const plansDir = getPlansDir();
32
+ const planPath = join(plansDir, state.activePlan);
33
+ state.documents = scanPlanDocuments(planPath);
34
+ }
35
+ catch (e) {
36
+ // Ignore doc scan errors
37
+ }
38
+ }
39
+ this.emitter.setState(state);
40
+ },
41
+ onEvent: (event) => {
42
+ this.emitter.emit(event);
43
+ },
44
+ onError: (error) => {
45
+ if (this.options.onError) {
46
+ this.options.onError(error);
47
+ }
48
+ },
49
+ });
50
+ }
51
+ /**
52
+ * Start the web dashboard
53
+ */
54
+ async start() {
55
+ // Start file watcher
56
+ const watcherStarted = this.watcher.start();
57
+ if (!watcherStarted) {
58
+ throw new Error('No active session found');
59
+ }
60
+ // Create and start server
61
+ this.server = new OfficeWebServer({
62
+ port: this.options.port,
63
+ host: this.options.host,
64
+ emitter: this.emitter,
65
+ });
66
+ const actualPort = await this.server.start();
67
+ // Auto-open browser
68
+ if (this.options.autoOpen) {
69
+ this.openBrowser(`http://${this.options.host}:${actualPort}`);
70
+ }
71
+ if (this.options.onReady) {
72
+ this.options.onReady(actualPort);
73
+ }
74
+ return actualPort;
75
+ }
76
+ /**
77
+ * Stop the dashboard
78
+ */
79
+ stop() {
80
+ if (this.server) {
81
+ this.server.stop();
82
+ this.server = null;
83
+ }
84
+ this.watcher.stop();
85
+ this.emitter.dispose();
86
+ }
87
+ /**
88
+ * Open browser to URL
89
+ */
90
+ openBrowser(url) {
91
+ const command = process.platform === 'win32'
92
+ ? `start ${url}`
93
+ : process.platform === 'darwin'
94
+ ? `open ${url}`
95
+ : `xdg-open ${url}`;
96
+ exec(command, (error) => {
97
+ if (error) {
98
+ // Log to console if browser opening fails
99
+ console.log(`Open ${url} in your browser`);
100
+ }
101
+ });
102
+ }
103
+ }
104
+ /**
105
+ * Start web dashboard (convenience function)
106
+ */
107
+ export async function startWebDashboard(options = {}) {
108
+ const dashboard = new WebDashboard(options);
109
+ await dashboard.start();
110
+ return dashboard;
111
+ }
@@ -0,0 +1,23 @@
1
+ import { GkSession, GkAgent } from '../session/types.js';
2
+ import { OfficeState, OfficeAgent, OrchestratorAgent, InboxItem, CharacterType } from './types.js';
3
+ /**
4
+ * Map agent role to character type
5
+ * Must match JS role detection: research, code/executor, plan, test, design/ui/ux
6
+ */
7
+ export declare function getCharacterType(role: string): CharacterType;
8
+ /**
9
+ * Check if agent is the orchestrator (Main Agent)
10
+ */
11
+ export declare function isOrchestrator(agent: GkAgent, session: GkSession): boolean;
12
+ /**
13
+ * Convert single GkAgent to OfficeAgent
14
+ */
15
+ export declare function agentToOfficeAgent(agent: GkAgent, session: GkSession): OfficeAgent | OrchestratorAgent;
16
+ /**
17
+ * Generate inbox item from completed agent
18
+ */
19
+ export declare function agentToInboxItem(agent: GkAgent): InboxItem;
20
+ /**
21
+ * Convert full GkSession to OfficeState
22
+ */
23
+ export declare function sessionToOfficeState(session: GkSession | null): OfficeState;
@@ -0,0 +1,171 @@
1
+ import { createInitialState } from './state-machine.js';
2
+ import { getIconForRole, formatDisplayName } from './icons.js';
3
+ /**
4
+ * Map agent role to character type
5
+ * Must match JS role detection: research, code/executor, plan, test, design/ui/ux
6
+ */
7
+ export function getCharacterType(role) {
8
+ const r = role.toLowerCase();
9
+ // Only main agent is orchestrator
10
+ if (r.includes('main'))
11
+ return 'orchestrator';
12
+ // Match roles to JS detection logic
13
+ if (r.includes('research') || r.includes('scout'))
14
+ return 'researcher';
15
+ if (r.includes('code') || r.includes('executor') || r.includes('debug'))
16
+ return 'coder';
17
+ if (r.includes('plan'))
18
+ return 'planner';
19
+ if (r.includes('test'))
20
+ return 'tester';
21
+ if (r.includes('design') || r.includes('ui') || r.includes('ux') || r.includes('artist'))
22
+ return 'designer';
23
+ // Legacy mappings
24
+ if (r.includes('doc') || r.includes('writer'))
25
+ return 'writer';
26
+ if (r.includes('manager') || r.includes('git'))
27
+ return 'manager';
28
+ // Everything else goes to other
29
+ return 'other';
30
+ }
31
+ /**
32
+ * Check if agent is the orchestrator (Main Agent)
33
+ */
34
+ export function isOrchestrator(agent, session) {
35
+ return (agent.agentType === 'Main Agent' ||
36
+ agent.parentGkSessionId === null ||
37
+ agent.gkSessionId === session.gkSessionId);
38
+ }
39
+ /**
40
+ * Map GkAgent status to OfficeAgent state
41
+ */
42
+ function mapStatusToState(status, hasActiveSkill) {
43
+ if (status === 'active') {
44
+ return hasActiveSkill ? 'working' : 'working';
45
+ }
46
+ if (status === 'completed')
47
+ return 'idle';
48
+ if (status === 'failed')
49
+ return 'idle';
50
+ return 'idle';
51
+ }
52
+ /**
53
+ * Calculate progress from agent timing
54
+ */
55
+ function calculateProgress(agent) {
56
+ if (agent.status === 'completed')
57
+ return 100;
58
+ if (agent.status === 'failed')
59
+ return 0;
60
+ if (!agent.startTime)
61
+ return 0;
62
+ const start = new Date(agent.startTime).getTime();
63
+ const now = Date.now();
64
+ const elapsed = now - start;
65
+ // Estimate based on typical task duration (2 min default)
66
+ const estimatedMs = 120000;
67
+ return Math.min(100, Math.round((elapsed / estimatedMs) * 100));
68
+ }
69
+ /**
70
+ * Generate speech bubble based on agent state
71
+ */
72
+ function generateSpeechBubble(agent) {
73
+ if (agent.status === 'completed')
74
+ return 'Task complete!';
75
+ if (agent.status === 'failed')
76
+ return 'Task failed';
77
+ if (agent.injected?.skills?.length) {
78
+ return `Working on ${agent.injected.skills[0]}...`;
79
+ }
80
+ return null;
81
+ }
82
+ /**
83
+ * Convert single GkAgent to OfficeAgent
84
+ */
85
+ export function agentToOfficeAgent(agent, session) {
86
+ const isOrch = isOrchestrator(agent, session);
87
+ const hasActiveSkill = !!(agent.injected?.skills?.length && agent.status === 'active');
88
+ const role = agent.agentRole || 'unknown';
89
+ const base = {
90
+ id: agent.gkSessionId,
91
+ agentType: isOrch ? 'orchestrator' : 'sub-agent',
92
+ role,
93
+ characterType: getCharacterType(role),
94
+ icon: getIconForRole(role, isOrch),
95
+ state: mapStatusToState(agent.status, hasActiveSkill),
96
+ activeSkill: agent.injected?.skills?.[0] || null,
97
+ progress: calculateProgress(agent),
98
+ speechBubble: generateSpeechBubble(agent),
99
+ hasFireEffect: hasActiveSkill,
100
+ gkSessionId: agent.gkSessionId,
101
+ parentSessionId: agent.parentGkSessionId,
102
+ };
103
+ if (isOrch) {
104
+ const subAgents = session.agents.filter(a => !isOrchestrator(a, session));
105
+ return {
106
+ ...base,
107
+ agentType: 'orchestrator',
108
+ delegatedTo: subAgents.filter(a => a.status === 'active').map(a => a.gkSessionId),
109
+ totalSubAgents: subAgents.length,
110
+ completedSubAgents: subAgents.filter(a => a.status === 'completed').length,
111
+ };
112
+ }
113
+ return base;
114
+ }
115
+ /**
116
+ * Generate inbox item from completed agent
117
+ */
118
+ export function agentToInboxItem(agent) {
119
+ const duration = agent.endTime && agent.startTime
120
+ ? new Date(agent.endTime).getTime() - new Date(agent.startTime).getTime()
121
+ : 0;
122
+ return {
123
+ id: `inbox-${agent.gkSessionId}`,
124
+ agentId: agent.gkSessionId,
125
+ agentRole: agent.agentRole || 'unknown',
126
+ agentIcon: getIconForRole(agent.agentRole || ''),
127
+ timestamp: agent.endTime ? new Date(agent.endTime).getTime() : Date.now(),
128
+ status: 'unread',
129
+ title: `${formatDisplayName(agent.agentRole || 'Agent')} completed`,
130
+ preview: agent.prompt ? agent.prompt.slice(0, 100) : 'Task completed',
131
+ fullContent: null,
132
+ tokenUsage: agent.tokenUsage
133
+ ? { input: agent.tokenUsage.input, output: agent.tokenUsage.output }
134
+ : null,
135
+ duration,
136
+ skillsUsed: agent.injected?.skills || [],
137
+ };
138
+ }
139
+ /**
140
+ * Convert full GkSession to OfficeState
141
+ */
142
+ export function sessionToOfficeState(session) {
143
+ const state = createInitialState();
144
+ if (!session)
145
+ return state;
146
+ state.sessionId = session.gkSessionId;
147
+ state.projectDir = session.projectDir;
148
+ state.activePlan = session.activePlan;
149
+ state.appName = session.appName || null; // IDE name from gk session init
150
+ state.isActive = session.agents.some(a => a.status === 'active');
151
+ // Process agents
152
+ for (const agent of session.agents) {
153
+ const officeAgent = agentToOfficeAgent(agent, session);
154
+ if (officeAgent.agentType === 'orchestrator') {
155
+ state.orchestrator = officeAgent;
156
+ }
157
+ else {
158
+ state.agents.set(officeAgent.id, officeAgent);
159
+ }
160
+ // Add completed agents to inbox
161
+ if (agent.status === 'completed') {
162
+ const existingItem = state.inbox.find(i => i.agentId === agent.gkSessionId);
163
+ if (!existingItem) {
164
+ state.inbox.push(agentToInboxItem(agent));
165
+ }
166
+ }
167
+ }
168
+ // Sort inbox by timestamp descending
169
+ state.inbox.sort((a, b) => b.timestamp - a.timestamp);
170
+ return state;
171
+ }
@@ -0,0 +1,5 @@
1
+ import { AgentState, OfficeState, OfficeAgent, OfficeEvent } from './types.js';
2
+ export declare function isValidTransition(from: AgentState, to: AgentState): boolean;
3
+ export declare function createInitialState(): OfficeState;
4
+ export declare function transitionAgent(agent: OfficeAgent, event: OfficeEvent): OfficeAgent;
5
+ export declare function processEvent(state: OfficeState, event: OfficeEvent): OfficeState;
@@ -0,0 +1,82 @@
1
+ // Valid state transitions
2
+ const VALID_TRANSITIONS = {
3
+ idle: ['working', 'walking', 'receiving'],
4
+ working: ['idle', 'delivering', 'walking'],
5
+ walking: ['idle', 'working', 'delivering'],
6
+ delivering: ['idle', 'walking'],
7
+ receiving: ['working', 'idle'],
8
+ };
9
+ export function isValidTransition(from, to) {
10
+ return VALID_TRANSITIONS[from]?.includes(to) ?? false;
11
+ }
12
+ export function createInitialState() {
13
+ return {
14
+ orchestrator: null,
15
+ agents: new Map(),
16
+ sessionId: null,
17
+ projectDir: null,
18
+ activePlan: null,
19
+ appName: null,
20
+ currentNotification: null,
21
+ inbox: [],
22
+ documents: [],
23
+ isActive: false,
24
+ };
25
+ }
26
+ export function transitionAgent(agent, event) {
27
+ // Map event type to new state
28
+ const stateMap = {
29
+ agent_idle: 'idle',
30
+ agent_working: 'working',
31
+ skill_activated: 'working',
32
+ handoff_start: 'walking',
33
+ received_work: 'receiving',
34
+ delivering: 'delivering',
35
+ task_complete: 'idle',
36
+ };
37
+ const newState = stateMap[event.type];
38
+ if (!newState)
39
+ return agent;
40
+ if (!isValidTransition(agent.state, newState)) {
41
+ return agent; // Invalid transition, no change
42
+ }
43
+ return {
44
+ ...agent,
45
+ state: newState,
46
+ activeSkill: event.skill ?? agent.activeSkill,
47
+ hasFireEffect: event.type === 'skill_activated',
48
+ speechBubble: event.message || null,
49
+ };
50
+ }
51
+ export function processEvent(state, event) {
52
+ // Update agent state
53
+ const agent = state.agents.get(event.agentId);
54
+ if (agent) {
55
+ state.agents.set(event.agentId, transitionAgent(agent, event));
56
+ }
57
+ else if (state.orchestrator && state.orchestrator.id === event.agentId) {
58
+ state.orchestrator = transitionAgent(state.orchestrator, event); // Cast as any to handle type overlap
59
+ }
60
+ // Generate notification if needed
61
+ const notification = generateNotification(event);
62
+ if (notification) {
63
+ state.currentNotification = notification;
64
+ }
65
+ return { ...state };
66
+ }
67
+ function generateNotification(event) {
68
+ const notificationMap = {
69
+ skill_activated: { type: 'skill', template: `Skill activated: ${event.skill}` },
70
+ handoff_start: { type: 'handoff', template: 'Agent delivering information...' },
71
+ handoff_complete: { type: 'handoff', template: 'Handoff complete!' },
72
+ task_complete: { type: 'success', template: 'Task completed! Check your inbox' },
73
+ };
74
+ const config = notificationMap[event.type];
75
+ if (!config)
76
+ return null;
77
+ return {
78
+ message: config.template,
79
+ type: config.type,
80
+ timestamp: event.timestamp,
81
+ };
82
+ }