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.
- package/README.md +141 -7
- package/dist/commands/agent/index.d.ts +9 -0
- package/dist/commands/agent/index.js +1329 -0
- package/dist/commands/cache/index.d.ts +5 -0
- package/dist/commands/cache/index.js +43 -0
- package/dist/commands/catalog/index.d.ts +2 -0
- package/dist/commands/catalog/index.js +57 -0
- package/dist/commands/config/index.d.ts +7 -0
- package/dist/commands/config/index.js +122 -0
- package/dist/commands/convert/index.d.ts +8 -0
- package/dist/commands/convert/index.js +391 -0
- package/dist/commands/doctor/index.d.ts +2 -0
- package/dist/commands/doctor/index.js +243 -0
- package/dist/commands/extension/index.d.ts +5 -0
- package/dist/commands/extension/index.js +52 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.js +37 -0
- package/dist/commands/init/index.d.ts +6 -0
- package/dist/commands/init/index.js +345 -0
- package/dist/commands/new/index.d.ts +5 -0
- package/dist/commands/new/index.js +49 -0
- package/dist/commands/office/index.d.ts +5 -0
- package/dist/commands/office/index.js +283 -0
- package/dist/commands/paste/index.d.ts +10 -0
- package/dist/commands/paste/index.js +533 -0
- package/dist/commands/plan/index.d.ts +8 -0
- package/dist/commands/plan/index.js +247 -0
- package/dist/commands/session/index.d.ts +8 -0
- package/dist/commands/session/index.js +289 -0
- package/dist/commands/tokens/index.d.ts +6 -0
- package/dist/commands/tokens/index.js +148 -0
- package/dist/commands/update/index.d.ts +26 -0
- package/dist/commands/update/index.js +199 -0
- package/dist/commands/versions/index.d.ts +5 -0
- package/dist/commands/versions/index.js +39 -0
- package/dist/domains/agent/index.d.ts +8 -0
- package/dist/domains/agent/index.js +8 -0
- package/dist/domains/agent/mappings.d.ts +32 -0
- package/dist/domains/agent/mappings.js +164 -0
- package/dist/domains/agent/profile.d.ts +26 -0
- package/dist/domains/agent/profile.js +225 -0
- package/dist/domains/agent/pty-context.d.ts +11 -0
- package/dist/domains/agent/pty-context.js +83 -0
- package/dist/domains/agent/pty-providers.d.ts +18 -0
- package/dist/domains/agent/pty-providers.js +66 -0
- package/dist/domains/agent/pty-session.d.ts +33 -0
- package/dist/domains/agent/pty-session.js +82 -0
- package/dist/domains/agent/pty-types.d.ts +127 -0
- package/dist/domains/agent/pty-types.js +4 -0
- package/dist/domains/agent/search.d.ts +45 -0
- package/dist/domains/agent/search.js +614 -0
- package/dist/domains/agent/types.d.ts +78 -0
- package/dist/domains/agent/types.js +5 -0
- package/dist/domains/agent-office/documents-scanner.d.ts +9 -0
- package/dist/domains/agent-office/documents-scanner.js +143 -0
- package/dist/domains/agent-office/event-emitter.d.ts +43 -0
- package/dist/domains/agent-office/event-emitter.js +86 -0
- package/dist/domains/agent-office/file-watcher.d.ts +40 -0
- package/dist/domains/agent-office/file-watcher.js +173 -0
- package/dist/domains/agent-office/icons.d.ts +11 -0
- package/dist/domains/agent-office/icons.js +36 -0
- package/dist/domains/agent-office/index.d.ts +12 -0
- package/dist/domains/agent-office/index.js +20 -0
- package/dist/domains/agent-office/renderer/web/assets.d.ts +11 -0
- package/dist/domains/agent-office/renderer/web/assets.js +3419 -0
- package/dist/domains/agent-office/renderer/web/server.d.ts +42 -0
- package/dist/domains/agent-office/renderer/web/server.js +228 -0
- package/dist/domains/agent-office/renderer/web.d.ts +30 -0
- package/dist/domains/agent-office/renderer/web.js +111 -0
- package/dist/domains/agent-office/session-bridge.d.ts +23 -0
- package/dist/domains/agent-office/session-bridge.js +171 -0
- package/dist/domains/agent-office/state-machine.d.ts +5 -0
- package/dist/domains/agent-office/state-machine.js +82 -0
- package/dist/domains/agent-office/types.d.ts +91 -0
- package/dist/domains/agent-office/types.js +4 -0
- package/dist/domains/cache/index.d.ts +1 -0
- package/dist/domains/cache/index.js +1 -0
- package/dist/domains/cache/manager.d.ts +22 -0
- package/dist/domains/cache/manager.js +84 -0
- package/dist/domains/config/index.d.ts +5 -0
- package/dist/domains/config/index.js +5 -0
- package/dist/domains/config/manager.d.ts +24 -0
- package/dist/domains/config/manager.js +85 -0
- package/dist/domains/config/schema.d.ts +17 -0
- package/dist/domains/config/schema.js +96 -0
- package/dist/domains/convert/converter.d.ts +78 -0
- package/dist/domains/convert/converter.js +471 -0
- package/dist/domains/convert/index.d.ts +5 -0
- package/dist/domains/convert/index.js +5 -0
- package/dist/domains/convert/types.d.ts +88 -0
- package/dist/domains/convert/types.js +18 -0
- package/dist/domains/github/download.d.ts +12 -0
- package/dist/domains/github/download.js +51 -0
- package/dist/domains/github/index.d.ts +2 -0
- package/dist/domains/github/index.js +2 -0
- package/dist/domains/github/releases.d.ts +16 -0
- package/dist/domains/github/releases.js +68 -0
- package/dist/domains/installation/conflict.d.ts +13 -0
- package/dist/domains/installation/conflict.js +38 -0
- package/dist/domains/installation/file-sync.d.ts +16 -0
- package/dist/domains/installation/file-sync.js +77 -0
- package/dist/domains/installation/index.d.ts +3 -0
- package/dist/domains/installation/index.js +3 -0
- package/dist/domains/installation/metadata.d.ts +20 -0
- package/dist/domains/installation/metadata.js +52 -0
- package/dist/domains/plan/index.d.ts +2 -0
- package/dist/domains/plan/index.js +2 -0
- package/dist/domains/plan/resolver.d.ts +24 -0
- package/dist/domains/plan/resolver.js +164 -0
- package/dist/domains/plan/types.d.ts +13 -0
- package/dist/domains/plan/types.js +4 -0
- package/dist/domains/session/env.d.ts +51 -0
- package/dist/domains/session/env.js +118 -0
- package/dist/domains/session/index.d.ts +8 -0
- package/dist/domains/session/index.js +8 -0
- package/dist/domains/session/manager.d.ts +56 -0
- package/dist/domains/session/manager.js +205 -0
- package/dist/domains/session/paths.d.ts +6 -0
- package/dist/domains/session/paths.js +6 -0
- package/dist/domains/session/types.d.ts +121 -0
- package/dist/domains/session/types.js +5 -0
- package/dist/domains/session/writer.d.ts +82 -0
- package/dist/domains/session/writer.js +431 -0
- package/dist/domains/tokens/index.d.ts +5 -0
- package/dist/domains/tokens/index.js +5 -0
- package/dist/domains/tokens/pricing.d.ts +38 -0
- package/dist/domains/tokens/pricing.js +129 -0
- package/dist/domains/tokens/scanner.d.ts +42 -0
- package/dist/domains/tokens/scanner.js +168 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +90 -59
- package/dist/services/aipty.d.ts +76 -0
- package/dist/services/aipty.js +276 -0
- package/dist/services/archive.d.ts +22 -0
- package/dist/services/archive.js +53 -0
- package/dist/services/auto-update.d.ts +26 -0
- package/dist/services/auto-update.js +117 -0
- package/dist/services/hash.d.ts +36 -0
- package/dist/services/hash.js +63 -0
- package/dist/services/logger.d.ts +28 -0
- package/dist/services/logger.js +102 -0
- package/dist/services/music.d.ts +67 -0
- package/dist/services/music.js +290 -0
- package/dist/services/npm.d.ts +22 -0
- package/dist/services/npm.js +65 -0
- package/dist/services/pty-client.d.ts +66 -0
- package/dist/services/pty-client.js +154 -0
- package/dist/services/pty-server.d.ts +102 -0
- package/dist/services/pty-server.js +613 -0
- package/dist/types/index.d.ts +155 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/colors.d.ts +43 -0
- package/dist/utils/colors.js +98 -0
- package/dist/utils/errors.d.ts +24 -0
- package/dist/utils/errors.js +56 -0
- package/dist/utils/paths.d.ts +46 -0
- package/dist/utils/paths.js +89 -0
- package/dist/utils/platform.d.ts +11 -0
- package/dist/utils/platform.js +31 -0
- package/package.json +55 -54
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PlanDocument, DocumentType } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Scan plan folder for documents
|
|
4
|
+
*/
|
|
5
|
+
export declare function scanPlanDocuments(planPath: string): PlanDocument[];
|
|
6
|
+
/**
|
|
7
|
+
* Group documents by type for display
|
|
8
|
+
*/
|
|
9
|
+
export declare function groupDocumentsByType(documents: PlanDocument[]): Record<DocumentType, PlanDocument[]>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, basename, extname, relative } from 'path';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
/**
|
|
5
|
+
* Get icon for document type/extension
|
|
6
|
+
*/
|
|
7
|
+
function getDocumentIcon(type, ext) {
|
|
8
|
+
const typeIcons = {
|
|
9
|
+
plan: '\uD83D\uDCCB', // clipboard
|
|
10
|
+
phase: '\uD83D\uDCD1', // bookmark tabs
|
|
11
|
+
research: '\uD83D\uDD0D', // magnifier
|
|
12
|
+
artifact: '\uD83D\uDCE6', // package
|
|
13
|
+
report: '\uD83D\uDCCA', // chart
|
|
14
|
+
other: '\uD83D\uDCC4', // document
|
|
15
|
+
};
|
|
16
|
+
if (type === 'artifact') {
|
|
17
|
+
const extIcons = {
|
|
18
|
+
sql: '\uD83D\uDDC4\uFE0F', // file cabinet
|
|
19
|
+
json: '\uD83D\uDCE6', // package
|
|
20
|
+
png: '\uD83D\uDDBC\uFE0F', // frame
|
|
21
|
+
jpg: '\uD83D\uDDBC\uFE0F', // frame
|
|
22
|
+
ts: '\uD83D\uDCBB', // laptop
|
|
23
|
+
js: '\uD83D\uDCBB', // laptop
|
|
24
|
+
};
|
|
25
|
+
return extIcons[ext] || typeIcons.artifact;
|
|
26
|
+
}
|
|
27
|
+
return typeIcons[type];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Extract phase number from filename
|
|
31
|
+
*/
|
|
32
|
+
function extractPhaseNumber(name) {
|
|
33
|
+
const match = name.match(/^phase-(\d+)/);
|
|
34
|
+
return match ? parseInt(match[1], 10) : null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Format document name for display
|
|
38
|
+
*/
|
|
39
|
+
function formatDocumentName(name, type) {
|
|
40
|
+
if (type === 'phase') {
|
|
41
|
+
const num = extractPhaseNumber(name);
|
|
42
|
+
const rest = name.replace(/^phase-\d+-?/, '').replace(/-/g, ' ');
|
|
43
|
+
return num !== null ? `Phase ${num}: ${rest}` : name;
|
|
44
|
+
}
|
|
45
|
+
return name.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create document object from file path
|
|
49
|
+
*/
|
|
50
|
+
function createDocument(filePath, type, planPath) {
|
|
51
|
+
const stat = statSync(filePath);
|
|
52
|
+
const name = basename(filePath, extname(filePath));
|
|
53
|
+
const ext = extname(filePath).slice(1);
|
|
54
|
+
return {
|
|
55
|
+
id: createHash('md5').update(filePath).digest('hex').slice(0, 8),
|
|
56
|
+
name,
|
|
57
|
+
displayName: formatDocumentName(name, type),
|
|
58
|
+
type,
|
|
59
|
+
icon: getDocumentIcon(type, ext),
|
|
60
|
+
path: filePath,
|
|
61
|
+
relativePath: relative(planPath, filePath),
|
|
62
|
+
modifiedAt: stat.mtimeMs,
|
|
63
|
+
createdAt: stat.birthtimeMs,
|
|
64
|
+
size: stat.size,
|
|
65
|
+
extension: ext,
|
|
66
|
+
phaseNumber: extractPhaseNumber(name),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Scan plan folder for documents
|
|
71
|
+
*/
|
|
72
|
+
export function scanPlanDocuments(planPath) {
|
|
73
|
+
if (!existsSync(planPath))
|
|
74
|
+
return [];
|
|
75
|
+
const documents = [];
|
|
76
|
+
// 1. Scan plan.md (main plan)
|
|
77
|
+
const planFile = join(planPath, 'plan.md');
|
|
78
|
+
if (existsSync(planFile)) {
|
|
79
|
+
documents.push(createDocument(planFile, 'plan', planPath));
|
|
80
|
+
}
|
|
81
|
+
// 2. Scan phase-*.md or phase-*/ directories
|
|
82
|
+
const rootFiles = readdirSync(planPath);
|
|
83
|
+
for (const file of rootFiles) {
|
|
84
|
+
const fullPath = join(planPath, file);
|
|
85
|
+
const stat = statSync(fullPath);
|
|
86
|
+
if (file.startsWith('phase-')) {
|
|
87
|
+
if (stat.isDirectory()) {
|
|
88
|
+
// Check for phase.md inside directory
|
|
89
|
+
const phaseFile = join(fullPath, 'phase.md');
|
|
90
|
+
if (existsSync(phaseFile)) {
|
|
91
|
+
documents.push(createDocument(phaseFile, 'phase', planPath));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (file.endsWith('.md')) {
|
|
95
|
+
documents.push(createDocument(fullPath, 'phase', planPath));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// 3. Scan research/ folder
|
|
100
|
+
const researchDir = join(planPath, 'research');
|
|
101
|
+
if (existsSync(researchDir)) {
|
|
102
|
+
const researchFiles = readdirSync(researchDir).filter(f => f.endsWith('.md'));
|
|
103
|
+
for (const file of researchFiles) {
|
|
104
|
+
documents.push(createDocument(join(researchDir, file), 'research', planPath));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// 4. Scan artifacts/ folder
|
|
108
|
+
const artifactsDir = join(planPath, 'artifacts');
|
|
109
|
+
if (existsSync(artifactsDir)) {
|
|
110
|
+
const artifactFiles = readdirSync(artifactsDir);
|
|
111
|
+
for (const file of artifactFiles) {
|
|
112
|
+
const fullPath = join(artifactsDir, file);
|
|
113
|
+
if (statSync(fullPath).isFile()) {
|
|
114
|
+
documents.push(createDocument(fullPath, 'artifact', planPath));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// 5. Scan reports/ folder
|
|
119
|
+
const reportsDir = join(planPath, 'reports');
|
|
120
|
+
if (existsSync(reportsDir)) {
|
|
121
|
+
const reportFiles = readdirSync(reportsDir).filter(f => f.endsWith('.md'));
|
|
122
|
+
for (const file of reportFiles) {
|
|
123
|
+
documents.push(createDocument(join(reportsDir, file), 'report', planPath));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Sort by modification time (newest first)
|
|
127
|
+
documents.sort((a, b) => b.modifiedAt - a.modifiedAt);
|
|
128
|
+
return documents;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Group documents by type for display
|
|
132
|
+
*/
|
|
133
|
+
export function groupDocumentsByType(documents) {
|
|
134
|
+
return {
|
|
135
|
+
plan: documents.filter(d => d.type === 'plan'),
|
|
136
|
+
phase: documents.filter(d => d.type === 'phase')
|
|
137
|
+
.sort((a, b) => (b.phaseNumber || 0) - (a.phaseNumber || 0)),
|
|
138
|
+
research: documents.filter(d => d.type === 'research'),
|
|
139
|
+
artifact: documents.filter(d => d.type === 'artifact'),
|
|
140
|
+
report: documents.filter(d => d.type === 'report'),
|
|
141
|
+
other: documents.filter(d => d.type === 'other'),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { OfficeState, OfficeEvent } from './types.js';
|
|
2
|
+
type StateListener = (state: OfficeState) => void;
|
|
3
|
+
type EventListener = (event: OfficeEvent) => void;
|
|
4
|
+
export declare class OfficeEventEmitter {
|
|
5
|
+
private state;
|
|
6
|
+
private stateListeners;
|
|
7
|
+
private eventListeners;
|
|
8
|
+
private eventHistory;
|
|
9
|
+
constructor(initialState: OfficeState);
|
|
10
|
+
/**
|
|
11
|
+
* Subscribe to state changes
|
|
12
|
+
*/
|
|
13
|
+
onStateChange(listener: StateListener): () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Subscribe to individual events
|
|
16
|
+
*/
|
|
17
|
+
onEvent(listener: EventListener): () => void;
|
|
18
|
+
/**
|
|
19
|
+
* Emit an event and update state
|
|
20
|
+
*/
|
|
21
|
+
emit(event: OfficeEvent): void;
|
|
22
|
+
/**
|
|
23
|
+
* Update state and notify listeners
|
|
24
|
+
*/
|
|
25
|
+
setState(newState: OfficeState): void;
|
|
26
|
+
/**
|
|
27
|
+
* Get current state snapshot
|
|
28
|
+
*/
|
|
29
|
+
getState(): OfficeState;
|
|
30
|
+
/**
|
|
31
|
+
* Get event history
|
|
32
|
+
*/
|
|
33
|
+
getHistory(): OfficeEvent[];
|
|
34
|
+
/**
|
|
35
|
+
* Replay events from a timestamp
|
|
36
|
+
*/
|
|
37
|
+
replay(fromTimestamp: number): OfficeEvent[];
|
|
38
|
+
/**
|
|
39
|
+
* Clear all listeners (for cleanup)
|
|
40
|
+
*/
|
|
41
|
+
dispose(): void;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const MAX_HISTORY_SIZE = 1000;
|
|
2
|
+
export class OfficeEventEmitter {
|
|
3
|
+
state;
|
|
4
|
+
stateListeners = new Set();
|
|
5
|
+
eventListeners = new Set();
|
|
6
|
+
eventHistory = [];
|
|
7
|
+
constructor(initialState) {
|
|
8
|
+
this.state = initialState;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Subscribe to state changes
|
|
12
|
+
*/
|
|
13
|
+
onStateChange(listener) {
|
|
14
|
+
this.stateListeners.add(listener);
|
|
15
|
+
// Immediately notify with current state
|
|
16
|
+
listener(this.state);
|
|
17
|
+
return () => this.stateListeners.delete(listener);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Subscribe to individual events
|
|
21
|
+
*/
|
|
22
|
+
onEvent(listener) {
|
|
23
|
+
this.eventListeners.add(listener);
|
|
24
|
+
return () => this.eventListeners.delete(listener);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Emit an event and update state
|
|
28
|
+
*/
|
|
29
|
+
emit(event) {
|
|
30
|
+
// Store in history
|
|
31
|
+
this.eventHistory.push(event);
|
|
32
|
+
if (this.eventHistory.length > MAX_HISTORY_SIZE) {
|
|
33
|
+
this.eventHistory.shift();
|
|
34
|
+
}
|
|
35
|
+
// Notify event listeners
|
|
36
|
+
for (const listener of this.eventListeners) {
|
|
37
|
+
try {
|
|
38
|
+
listener(event);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
console.error('Event listener error:', e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Update state and notify listeners
|
|
47
|
+
*/
|
|
48
|
+
setState(newState) {
|
|
49
|
+
this.state = newState;
|
|
50
|
+
// Notify state listeners
|
|
51
|
+
for (const listener of this.stateListeners) {
|
|
52
|
+
try {
|
|
53
|
+
listener(this.state);
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
console.error('State listener error:', e);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get current state snapshot
|
|
62
|
+
*/
|
|
63
|
+
getState() {
|
|
64
|
+
return this.state;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get event history
|
|
68
|
+
*/
|
|
69
|
+
getHistory() {
|
|
70
|
+
return [...this.eventHistory];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Replay events from a timestamp
|
|
74
|
+
*/
|
|
75
|
+
replay(fromTimestamp) {
|
|
76
|
+
return this.eventHistory.filter(e => e.timestamp >= fromTimestamp);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Clear all listeners (for cleanup)
|
|
80
|
+
*/
|
|
81
|
+
dispose() {
|
|
82
|
+
this.stateListeners.clear();
|
|
83
|
+
this.eventListeners.clear();
|
|
84
|
+
this.eventHistory = [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { GkSession } from '../session/types.js';
|
|
2
|
+
import { OfficeEvent } from './types.js';
|
|
3
|
+
export interface FileWatcherOptions {
|
|
4
|
+
onSessionChange: (session: GkSession) => void;
|
|
5
|
+
onEvent: (event: OfficeEvent) => void;
|
|
6
|
+
onError: (error: Error) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare class SessionFileWatcher {
|
|
9
|
+
private pollInterval;
|
|
10
|
+
private previousSession;
|
|
11
|
+
private previousMtime;
|
|
12
|
+
private options;
|
|
13
|
+
private sessionPath;
|
|
14
|
+
constructor(options: FileWatcherOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Start watching the active session file
|
|
17
|
+
* Uses polling for reliable cross-platform support (fs.watch is unreliable on Windows)
|
|
18
|
+
*/
|
|
19
|
+
start(): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Stop watching
|
|
22
|
+
*/
|
|
23
|
+
stop(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Check if file has changed by comparing modification time
|
|
26
|
+
*/
|
|
27
|
+
private checkForChanges;
|
|
28
|
+
/**
|
|
29
|
+
* Load session and emit events for changes
|
|
30
|
+
*/
|
|
31
|
+
private loadSession;
|
|
32
|
+
/**
|
|
33
|
+
* Diff two sessions and generate events
|
|
34
|
+
*/
|
|
35
|
+
private diffSessions;
|
|
36
|
+
/**
|
|
37
|
+
* Create event from agent
|
|
38
|
+
*/
|
|
39
|
+
private createEvent;
|
|
40
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'fs';
|
|
2
|
+
import { getSessionPath } from '../../utils/paths.js';
|
|
3
|
+
import { readEnv } from '../session/env.js';
|
|
4
|
+
import { getCharacterType } from './session-bridge.js';
|
|
5
|
+
// Polling interval - 200ms for fast detection
|
|
6
|
+
const POLL_INTERVAL_MS = 200;
|
|
7
|
+
export class SessionFileWatcher {
|
|
8
|
+
pollInterval = null;
|
|
9
|
+
previousSession = null;
|
|
10
|
+
previousMtime = 0;
|
|
11
|
+
options;
|
|
12
|
+
sessionPath = null;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Start watching the active session file
|
|
18
|
+
* Uses polling for reliable cross-platform support (fs.watch is unreliable on Windows)
|
|
19
|
+
*/
|
|
20
|
+
start() {
|
|
21
|
+
const env = readEnv();
|
|
22
|
+
const projectDir = env.PROJECT_DIR;
|
|
23
|
+
const gkSessionId = env.ACTIVE_GK_SESSION_ID;
|
|
24
|
+
if (!projectDir || !gkSessionId) {
|
|
25
|
+
this.options.onError(new Error('No active session found'));
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
this.sessionPath = getSessionPath(projectDir, gkSessionId);
|
|
29
|
+
if (!existsSync(this.sessionPath)) {
|
|
30
|
+
this.options.onError(new Error(`Session file not found: ${this.sessionPath}`));
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
// Load initial session and store mtime
|
|
34
|
+
try {
|
|
35
|
+
const stat = statSync(this.sessionPath);
|
|
36
|
+
this.previousMtime = stat.mtimeMs;
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
// Ignore stat errors on initial load
|
|
40
|
+
}
|
|
41
|
+
this.loadSession();
|
|
42
|
+
// Start polling for file changes (more reliable than fs.watch on Windows)
|
|
43
|
+
this.pollInterval = setInterval(() => {
|
|
44
|
+
this.checkForChanges();
|
|
45
|
+
}, POLL_INTERVAL_MS);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Stop watching
|
|
50
|
+
*/
|
|
51
|
+
stop() {
|
|
52
|
+
if (this.pollInterval) {
|
|
53
|
+
clearInterval(this.pollInterval);
|
|
54
|
+
this.pollInterval = null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if file has changed by comparing modification time
|
|
59
|
+
*/
|
|
60
|
+
checkForChanges() {
|
|
61
|
+
if (!this.sessionPath)
|
|
62
|
+
return;
|
|
63
|
+
try {
|
|
64
|
+
const stat = statSync(this.sessionPath);
|
|
65
|
+
const currentMtime = stat.mtimeMs;
|
|
66
|
+
// Only reload if file was modified
|
|
67
|
+
if (currentMtime > this.previousMtime) {
|
|
68
|
+
this.previousMtime = currentMtime;
|
|
69
|
+
this.loadSession();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
// File might be temporarily unavailable during write
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Load session and emit events for changes
|
|
78
|
+
*/
|
|
79
|
+
loadSession() {
|
|
80
|
+
if (!this.sessionPath)
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
83
|
+
const content = readFileSync(this.sessionPath, 'utf-8');
|
|
84
|
+
const session = JSON.parse(content);
|
|
85
|
+
// Generate events from diff BEFORE updating state
|
|
86
|
+
const events = this.previousSession
|
|
87
|
+
? this.diffSessions(this.previousSession, session)
|
|
88
|
+
: [];
|
|
89
|
+
// Update state FIRST so events can reference the new state
|
|
90
|
+
this.previousSession = session;
|
|
91
|
+
this.options.onSessionChange(session);
|
|
92
|
+
// Then emit events (now state.agents will have the agent)
|
|
93
|
+
for (const event of events) {
|
|
94
|
+
this.options.onEvent(event);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
this.options.onError(e);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Diff two sessions and generate events
|
|
103
|
+
*/
|
|
104
|
+
diffSessions(prev, curr) {
|
|
105
|
+
const events = [];
|
|
106
|
+
const timestamp = Date.now();
|
|
107
|
+
// Map previous agents by ID
|
|
108
|
+
const prevAgents = new Map(prev.agents.map(a => [a.gkSessionId, a]));
|
|
109
|
+
for (const agent of curr.agents) {
|
|
110
|
+
const prevAgent = prevAgents.get(agent.gkSessionId);
|
|
111
|
+
if (!prevAgent) {
|
|
112
|
+
// New agent added
|
|
113
|
+
events.push(this.createEvent('received_work', agent, timestamp));
|
|
114
|
+
if (agent.injected?.skills?.length) {
|
|
115
|
+
events.push(this.createEvent('skill_activated', agent, timestamp, agent.injected.skills[0]));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// Check for status changes
|
|
120
|
+
if (prevAgent.status === 'active' && agent.status === 'completed') {
|
|
121
|
+
events.push(this.createEvent('task_complete', agent, timestamp));
|
|
122
|
+
events.push(this.createEvent('delivering', agent, timestamp));
|
|
123
|
+
}
|
|
124
|
+
// Check for skill changes
|
|
125
|
+
const prevSkills = prevAgent.injected?.skills || [];
|
|
126
|
+
const currSkills = agent.injected?.skills || [];
|
|
127
|
+
const newSkills = currSkills.filter(s => !prevSkills.includes(s));
|
|
128
|
+
for (const skill of newSkills) {
|
|
129
|
+
events.push(this.createEvent('skill_activated', agent, timestamp, skill));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Check for session completion
|
|
134
|
+
const allCompleted = curr.agents.every(a => a.status === 'completed' || a.status === 'failed');
|
|
135
|
+
const wasActive = prev.agents.some(a => a.status === 'active');
|
|
136
|
+
if (allCompleted && wasActive && curr.agents.length > 0) {
|
|
137
|
+
events.push({
|
|
138
|
+
type: 'session_complete',
|
|
139
|
+
agentId: curr.gkSessionId,
|
|
140
|
+
targetAgentId: null,
|
|
141
|
+
skill: null,
|
|
142
|
+
message: 'All tasks completed',
|
|
143
|
+
timestamp,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return events;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Create event from agent
|
|
150
|
+
*/
|
|
151
|
+
createEvent(type, agent, timestamp, skill) {
|
|
152
|
+
const messages = {
|
|
153
|
+
agent_idle: 'Waiting for work',
|
|
154
|
+
agent_working: 'Working...',
|
|
155
|
+
skill_activated: `Activated skill: ${skill || 'unknown'}`,
|
|
156
|
+
handoff_start: 'Passing work...',
|
|
157
|
+
handoff_complete: 'Handoff complete!',
|
|
158
|
+
received_work: 'Received work',
|
|
159
|
+
delivering: 'Delivering results...',
|
|
160
|
+
task_complete: 'Task complete!',
|
|
161
|
+
session_complete: 'All tasks completed',
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
type,
|
|
165
|
+
agentId: agent.gkSessionId,
|
|
166
|
+
targetAgentId: agent.parentGkSessionId,
|
|
167
|
+
skill: skill || agent.injected?.skills?.[0] || null,
|
|
168
|
+
message: messages[type],
|
|
169
|
+
timestamp,
|
|
170
|
+
characterType: getCharacterType(agent.agentRole || 'coder'),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon mapping utilities for Agent Office
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get icon for agent role
|
|
6
|
+
*/
|
|
7
|
+
export declare function getIconForRole(role: string, isOrchestrator?: boolean): string;
|
|
8
|
+
/**
|
|
9
|
+
* Format role name for display
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatDisplayName(role: string): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon mapping utilities for Agent Office
|
|
3
|
+
*/
|
|
4
|
+
// Icon mapping by role keywords
|
|
5
|
+
const ROLE_ICONS = [
|
|
6
|
+
[/research|search|analyze/i, '\uD83D\uDD0D'], // magnifier
|
|
7
|
+
[/code|implement|execute|develop/i, '\uD83D\uDCBB'], // laptop
|
|
8
|
+
[/plan|architect/i, '\uD83D\uDCCB'], // clipboard
|
|
9
|
+
[/debug|fix|troubleshoot/i, '\uD83D\uDC1B'], // bug
|
|
10
|
+
[/test|qa|quality/i, '\uD83E\uDDEA'], // test tube
|
|
11
|
+
[/design|ui|ux/i, '\uD83C\uDFA8'], // palette
|
|
12
|
+
[/review|audit|check/i, '\u2705'], // checkmark
|
|
13
|
+
[/doc|write|content/i, '\uD83D\uDCDD'], // memo
|
|
14
|
+
];
|
|
15
|
+
const DEFAULT_ICON = '\uD83E\uDD16'; // robot
|
|
16
|
+
const ORCHESTRATOR_ICON = '\uD83D\uDC51'; // crown
|
|
17
|
+
/**
|
|
18
|
+
* Get icon for agent role
|
|
19
|
+
*/
|
|
20
|
+
export function getIconForRole(role, isOrchestrator = false) {
|
|
21
|
+
if (isOrchestrator)
|
|
22
|
+
return ORCHESTRATOR_ICON;
|
|
23
|
+
for (const [pattern, icon] of ROLE_ICONS) {
|
|
24
|
+
if (pattern.test(role))
|
|
25
|
+
return icon;
|
|
26
|
+
}
|
|
27
|
+
return DEFAULT_ICON;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Format role name for display
|
|
31
|
+
*/
|
|
32
|
+
export function formatDisplayName(role) {
|
|
33
|
+
return role
|
|
34
|
+
.replace(/-/g, ' ')
|
|
35
|
+
.replace(/\b\w/g, c => c.toUpperCase());
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Office domain exports
|
|
3
|
+
* Gamified visualization for multi-agent workflows
|
|
4
|
+
*/
|
|
5
|
+
export * from './types.js';
|
|
6
|
+
export { createInitialState, isValidTransition, transitionAgent, processEvent, } from './state-machine.js';
|
|
7
|
+
export { getIconForRole, formatDisplayName, } from './icons.js';
|
|
8
|
+
export { sessionToOfficeState, agentToOfficeAgent, agentToInboxItem, isOrchestrator, } from './session-bridge.js';
|
|
9
|
+
export { scanPlanDocuments, groupDocumentsByType, } from './documents-scanner.js';
|
|
10
|
+
export { OfficeEventEmitter } from './event-emitter.js';
|
|
11
|
+
export { SessionFileWatcher, type FileWatcherOptions } from './file-watcher.js';
|
|
12
|
+
export { WebDashboard, startWebDashboard, type WebDashboardOptions } from './renderer/web.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Office domain exports
|
|
3
|
+
* Gamified visualization for multi-agent workflows
|
|
4
|
+
*/
|
|
5
|
+
// Types
|
|
6
|
+
export * from './types.js';
|
|
7
|
+
// State machine
|
|
8
|
+
export { createInitialState, isValidTransition, transitionAgent, processEvent, } from './state-machine.js';
|
|
9
|
+
// Utilities
|
|
10
|
+
export { getIconForRole, formatDisplayName, } from './icons.js';
|
|
11
|
+
// Session bridge
|
|
12
|
+
export { sessionToOfficeState, agentToOfficeAgent, agentToInboxItem, isOrchestrator, } from './session-bridge.js';
|
|
13
|
+
// Documents
|
|
14
|
+
export { scanPlanDocuments, groupDocumentsByType, } from './documents-scanner.js';
|
|
15
|
+
// Event system
|
|
16
|
+
export { OfficeEventEmitter } from './event-emitter.js';
|
|
17
|
+
// File watcher
|
|
18
|
+
export { SessionFileWatcher } from './file-watcher.js';
|
|
19
|
+
// Renderers
|
|
20
|
+
export { WebDashboard, startWebDashboard } from './renderer/web.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Office Gamified UI Assets
|
|
3
|
+
* Auto-generated by build.js - DO NOT EDIT MANUALLY
|
|
4
|
+
*
|
|
5
|
+
* To modify, edit the source files in:
|
|
6
|
+
* - src/core/ (base HTML, CSS, JS)
|
|
7
|
+
* - integrate-to-assets.js (gamified CSS, HTML, character JS)
|
|
8
|
+
*
|
|
9
|
+
* Then run: node build.js
|
|
10
|
+
*/
|
|
11
|
+
export declare function getIndexHtml(): string;
|