copilotoffice 1.0.0
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 +190 -0
- package/bin/copilotoffice.js +39 -0
- package/dist/electron/cli-bridge.js +96 -0
- package/dist/electron/events-watcher.js +198 -0
- package/dist/electron/main.js +481 -0
- package/dist/electron/preload.js +90 -0
- package/dist/electron/terminal/events-watcher.js +230 -0
- package/dist/electron/terminal/ipc-relay.js +305 -0
- package/dist/electron/terminal/preload.js +149 -0
- package/dist/electron/terminal/protocol.js +18 -0
- package/dist/electron/terminal/server.js +6330 -0
- package/dist/electron/terminal-protocol.js +18 -0
- package/dist/electron/terminal-server.js +485 -0
- package/dist/game.bundle.js +156372 -0
- package/package.json +56 -0
- package/src/index.html +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Agency Office 🏢
|
|
2
|
+
|
|
3
|
+
A 2D pixel-art RPG-style game where you walk around a virtual office and interact with AI agents. Each NPC runs a real Copilot CLI session with full coding capabilities — plan tasks, debug code, and orchestrate multi-agent workflows from inside a game.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Pixel-art office environment** — all sprites procedurally generated in code, no external image assets
|
|
8
|
+
- **4 active NPC agents**, each with specialized capabilities:
|
|
9
|
+
- **Gene** (Generalist) — general-purpose coding, debugging, and research
|
|
10
|
+
- **Arthur** (Architect) — orchestrates plans and spins up agents for complex tasks
|
|
11
|
+
- **Dan** (Debugger) — bug investigation and root cause analysis
|
|
12
|
+
- **Alice** (Admin) — has direct access to edit this game's UI code (`workingDir: '.'`)
|
|
13
|
+
- **6 reserve agent slots** — Azure, Val, Rex, Doc, Scout, and Penny have pre-generated sprites ready to activate
|
|
14
|
+
- **Real terminal integration** via xterm.js — agents run actual Copilot CLI sessions through node-pty
|
|
15
|
+
- **Multi-office management** — switch between projects with independent agent state per office
|
|
16
|
+
- **Meeting Mode** — meet with Arthur in a private meeting room to plan and decompose complex tasks into structured subtasks
|
|
17
|
+
- **Fleet execution** — parallel agent spawning for approved task plans
|
|
18
|
+
- **Real-time status badges** — agent states (thinking, waiting, ready, slacking) with animated indicators
|
|
19
|
+
- **Toast & OS notifications** — configurable per-event notifications for agent activity
|
|
20
|
+
- **Session persistence** — terminal sessions and history survive restarts
|
|
21
|
+
- **Player customization** — customizable character colors
|
|
22
|
+
- **Mini-games** — Pong and Basketball (behind feature flags) for breaks
|
|
23
|
+
- **Hot reload** development mode with file watching
|
|
24
|
+
|
|
25
|
+
## Tech Stack
|
|
26
|
+
|
|
27
|
+
- **Phaser 3** — 2D game framework (sole renderer)
|
|
28
|
+
- **Electron 40+** — desktop app with Node.js integration
|
|
29
|
+
- **TypeScript** — strict mode throughout
|
|
30
|
+
- **esbuild** — fast bundling for game and Electron code
|
|
31
|
+
- **xterm.js** — terminal emulator for agent conversations
|
|
32
|
+
- **node-pty** — pseudo-terminal for running CLI processes
|
|
33
|
+
|
|
34
|
+
## Getting Started
|
|
35
|
+
|
|
36
|
+
### Prerequisites
|
|
37
|
+
|
|
38
|
+
- Node.js 18+
|
|
39
|
+
- npm
|
|
40
|
+
|
|
41
|
+
### Install from npm (global command)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm i -g copilotoffice
|
|
45
|
+
copilotoffice
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Install from source (development)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cd AgencyOffice
|
|
52
|
+
npm install
|
|
53
|
+
|
|
54
|
+
# Build and run
|
|
55
|
+
npm start
|
|
56
|
+
|
|
57
|
+
# Development mode (with hot reload)
|
|
58
|
+
npm run dev
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Controls
|
|
62
|
+
|
|
63
|
+
| Key | Action |
|
|
64
|
+
|-----|--------|
|
|
65
|
+
| `WASD` / `Arrow Keys` | Move around the office |
|
|
66
|
+
| `Shift` | Sprint (2x speed) |
|
|
67
|
+
| `E` | Interact with nearby agent or object |
|
|
68
|
+
| `F10` | Close terminal |
|
|
69
|
+
| `Escape` | Close terminal or mini-game |
|
|
70
|
+
| `Ctrl+Shift+N` | New terminal session |
|
|
71
|
+
|
|
72
|
+
## Project Structure
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
AgencyOffice/
|
|
76
|
+
├── electron/ # Electron main process
|
|
77
|
+
│ ├── main.ts # Window, IPC handlers, hot reload
|
|
78
|
+
│ ├── cli-bridge.ts # Mock/placeholder (not used at runtime)
|
|
79
|
+
│ └── terminal/ # Terminal server subsystem
|
|
80
|
+
│ ├── server.ts # PTY owner (forked child process)
|
|
81
|
+
│ ├── ipc-relay.ts # IPC bridge (renderer ↔ main ↔ server)
|
|
82
|
+
│ ├── preload.ts # Context bridge (window.copilotBridge)
|
|
83
|
+
│ ├── protocol.ts # IPC message type definitions
|
|
84
|
+
│ └── events-watcher.ts # Copilot CLI event file parser
|
|
85
|
+
├── src/ # Renderer process (Phaser + DOM)
|
|
86
|
+
│ ├── main.ts # Entry point — DOM layout, Phaser init, IPC wiring
|
|
87
|
+
│ ├── index.html # HTML host page
|
|
88
|
+
│ ├── scenes/ # Phaser scenes
|
|
89
|
+
│ │ ├── BootScene.ts # Procedural sprite generation
|
|
90
|
+
│ │ ├── OfficeScene.ts # Main game scene (layout, NPCs, interactions)
|
|
91
|
+
│ │ └── MeetingScene.ts # Meeting room with Arthur for planning
|
|
92
|
+
│ ├── entities/ # Game entities
|
|
93
|
+
│ │ ├── Player.ts # Player character with movement
|
|
94
|
+
│ │ └── NPC.ts # NPC agents with status badges
|
|
95
|
+
│ ├── sprites/ # Procedural sprite generation
|
|
96
|
+
│ │ ├── SpriteGenerator.ts # Sprite sheet generation
|
|
97
|
+
│ │ └── DirectionalSprite.ts # 4-direction animation utilities
|
|
98
|
+
│ ├── ui/ # UI overlays
|
|
99
|
+
│ │ ├── TerminalOverlay.ts # xterm.js terminal for agent sessions
|
|
100
|
+
│ │ ├── FleetDashboard.ts # Fleet execution dashboard
|
|
101
|
+
│ │ ├── PongGame.ts # Pong mini-game
|
|
102
|
+
│ │ ├── BasketballGame.ts # Basketball mini-game
|
|
103
|
+
│ │ ├── ToastNotification.ts # Toast notification popups
|
|
104
|
+
│ │ ├── NotificationService.ts
|
|
105
|
+
│ │ ├── NotificationSettingsPanel.ts
|
|
106
|
+
│ │ ├── CameraDragController.ts
|
|
107
|
+
│ │ └── DialogBox.ts # Legacy (deprecated)
|
|
108
|
+
│ ├── input/ # Keyboard focus management
|
|
109
|
+
│ │ ├── InputManager.ts # Central coordinator
|
|
110
|
+
│ │ ├── GameInputListener.ts
|
|
111
|
+
│ │ ├── GlobalInputListener.ts
|
|
112
|
+
│ │ └── TerminalInputListener.ts
|
|
113
|
+
│ ├── office/ # Multi-office state management
|
|
114
|
+
│ │ └── officeManager.ts # Office CRUD, agent status tracking
|
|
115
|
+
│ ├── meeting/ # Meeting mode & fleet orchestration
|
|
116
|
+
│ │ ├── types.ts # MeetingPlan, TaskAssignment, FleetStatus
|
|
117
|
+
│ │ ├── planParser.ts # Terminal output → structured plan
|
|
118
|
+
│ │ ├── planApproval.ts # Plan review overlay
|
|
119
|
+
│ │ ├── fleetOrchestrator.ts # Parallel agent spawning
|
|
120
|
+
│ │ ├── fleetTracker.ts # Fleet state machine
|
|
121
|
+
│ │ └── fleetVisualizer.ts # Fleet NPC visualization
|
|
122
|
+
│ ├── layouts/ # Layout system
|
|
123
|
+
│ │ ├── types.ts # DashboardRenderer, LayoutDefinition
|
|
124
|
+
│ │ ├── index.ts # Layout registry
|
|
125
|
+
│ │ ├── default/ # Default office layout
|
|
126
|
+
│ │ └── fleet/ # Fleet v-team layout
|
|
127
|
+
│ └── config/ # Static configuration
|
|
128
|
+
│ ├── agents.ts # Agent definitions & fleet config
|
|
129
|
+
│ ├── depths.ts # Phaser depth layer constants
|
|
130
|
+
│ ├── notifications.ts # Notification event settings
|
|
131
|
+
│ ├── meetingPrompt.ts # Meeting coordinator prompt
|
|
132
|
+
│ └── playerCustomization.ts # Player color customization
|
|
133
|
+
└── dist/ # Build output
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Adding New Agents
|
|
137
|
+
|
|
138
|
+
Edit `src/config/agents.ts` to add new NPCs. Six reserve agent slots (Azure, Val, Rex, Doc, Scout, Penny) already have pre-generated sprites — activate one by adding its config to the `AGENTS` array:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
{
|
|
142
|
+
id: 'unique-id',
|
|
143
|
+
name: 'Display Name',
|
|
144
|
+
skill: 'copilot-skill-name',
|
|
145
|
+
sprite: 'sprite_key',
|
|
146
|
+
color: 0xff0000, // Hex color for procedural sprite
|
|
147
|
+
position: { x: 5, y: 7 }, // Grid position in office
|
|
148
|
+
greeting: "Hello message when player approaches",
|
|
149
|
+
description: 'Short description',
|
|
150
|
+
workingDir: 'optional/path', // Optional custom working directory
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Sprites are auto-generated based on the color — no image assets needed.
|
|
155
|
+
|
|
156
|
+
## Development
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Watch mode with hot reload
|
|
160
|
+
npm run dev
|
|
161
|
+
|
|
162
|
+
# Build only (no run)
|
|
163
|
+
npm run build
|
|
164
|
+
|
|
165
|
+
# Run without rebuilding
|
|
166
|
+
npm run electron
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Release channels
|
|
170
|
+
|
|
171
|
+
- **Stable**: `npm i -g copilotoffice` (uses npm `latest` dist-tag)
|
|
172
|
+
- **Beta**: `npm i -g copilotoffice@beta` (uses npm `beta` dist-tag)
|
|
173
|
+
|
|
174
|
+
For maintainers: pushing to GitHub is not enough for `npm i -g copilotoffice` by name.
|
|
175
|
+
You must publish to npm. Typical flow:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npm run build
|
|
179
|
+
npm test
|
|
180
|
+
npm version patch
|
|
181
|
+
npm publish
|
|
182
|
+
|
|
183
|
+
# Beta example
|
|
184
|
+
npm version prerelease --preid=beta
|
|
185
|
+
npm publish --tag beta
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
ISC
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function run() {
|
|
7
|
+
let electronBinary;
|
|
8
|
+
try {
|
|
9
|
+
electronBinary = require('electron');
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.error('[copilotoffice] Electron is not available. Try reinstalling the package.');
|
|
12
|
+
console.error(error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const appRoot = path.resolve(__dirname, '..');
|
|
17
|
+
const child = spawn(electronBinary, [appRoot], {
|
|
18
|
+
stdio: 'inherit',
|
|
19
|
+
windowsHide: false,
|
|
20
|
+
env: {
|
|
21
|
+
...process.env,
|
|
22
|
+
COPILOT_OFFICE_ENABLE_WATCHER: '0',
|
|
23
|
+
COPILOT_OFFICE_OPEN_DEVTOOLS: '0',
|
|
24
|
+
COPILOT_OFFICE_CLI_MODE: '1',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
child.on('exit', (code) => {
|
|
29
|
+
process.exit(code ?? 0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
child.on('error', (error) => {
|
|
33
|
+
console.error('[copilotoffice] Failed to launch Electron.');
|
|
34
|
+
console.error(error);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
run();
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// electron/cli-bridge.ts
|
|
21
|
+
var cli_bridge_exports = {};
|
|
22
|
+
__export(cli_bridge_exports, {
|
|
23
|
+
CLIBridge: () => CLIBridge
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(cli_bridge_exports);
|
|
26
|
+
var CLIBridge = class {
|
|
27
|
+
constructor() {
|
|
28
|
+
this.availableSkills = [
|
|
29
|
+
{ name: "azure-prepare", description: "Prepare apps for Azure deployment" },
|
|
30
|
+
{ name: "azure-validate", description: "Validate Azure deployment readiness" },
|
|
31
|
+
{ name: "azure-deploy", description: "Deploy to Azure" },
|
|
32
|
+
{ name: "azure-diagnostics", description: "Troubleshoot Azure issues" },
|
|
33
|
+
{ name: "azure-resource-lookup", description: "Find Azure resources" },
|
|
34
|
+
{ name: "azure-cost-optimization", description: "Optimize Azure costs" }
|
|
35
|
+
];
|
|
36
|
+
console.log("CLI Bridge initialized");
|
|
37
|
+
}
|
|
38
|
+
getAvailableSkills() {
|
|
39
|
+
return this.availableSkills.map((s) => s.name);
|
|
40
|
+
}
|
|
41
|
+
async sendToSkill(skillName, message) {
|
|
42
|
+
return this.getMockResponse(skillName, message);
|
|
43
|
+
}
|
|
44
|
+
getMockResponse(skillName, message) {
|
|
45
|
+
const responses = {
|
|
46
|
+
"azure-prepare": [
|
|
47
|
+
"I can help you prepare your application for Azure! I'll analyze your project structure and generate the necessary infrastructure code.",
|
|
48
|
+
"Let me check your project... I can set up Bicep templates, azure.yaml, and Dockerfiles for deployment.",
|
|
49
|
+
"Ready to azurify your app! What kind of hosting do you need - Container Apps, App Service, or Functions?"
|
|
50
|
+
],
|
|
51
|
+
"azure-validate": [
|
|
52
|
+
"I'll run validation checks on your Azure configuration. Give me a moment to inspect your setup...",
|
|
53
|
+
"Checking deployment readiness... Looking at your azure.yaml, Bicep files, and permissions.",
|
|
54
|
+
"Validation complete! Your configuration looks good. Ready to deploy when you are."
|
|
55
|
+
],
|
|
56
|
+
"azure-deploy": [
|
|
57
|
+
"Deployment agent at your service! I can run `azd up` or `azd deploy` for you.",
|
|
58
|
+
"Ready to push to the cloud! Should I provision infrastructure first or just deploy code?",
|
|
59
|
+
"\u{1F680} Let's ship it! I'll handle the deployment pipeline for you."
|
|
60
|
+
],
|
|
61
|
+
"azure-diagnostics": [
|
|
62
|
+
"Dr. Azure here! Tell me what's wrong and I'll help diagnose the issue.",
|
|
63
|
+
"I can analyze logs, check health probes, and troubleshoot common Azure problems.",
|
|
64
|
+
"What seems to be the trouble? Container not starting? Function timing out? Let's investigate."
|
|
65
|
+
],
|
|
66
|
+
"azure-resource-lookup": [
|
|
67
|
+
"Scout reporting! I can find any Azure resources across your subscriptions.",
|
|
68
|
+
"Need to locate something? VMs, storage accounts, databases - I'll track it down.",
|
|
69
|
+
"Just tell me what you're looking for and I'll search your Azure environment."
|
|
70
|
+
],
|
|
71
|
+
"azure-cost-optimization": [
|
|
72
|
+
"Accountant here! I specialize in finding cost savings in your Azure bill.",
|
|
73
|
+
"Let me analyze your spending... I'll find orphaned resources and rightsizing opportunities.",
|
|
74
|
+
"Money talk! I can identify unused resources and recommend cheaper alternatives."
|
|
75
|
+
]
|
|
76
|
+
};
|
|
77
|
+
const skillResponses = responses[skillName] || [
|
|
78
|
+
`I'm the ${skillName} agent. How can I help you today?`,
|
|
79
|
+
`You asked about: "${message}". Let me think about that...`,
|
|
80
|
+
`Interesting question! As the ${skillName} specialist, I'd suggest...`
|
|
81
|
+
];
|
|
82
|
+
return skillResponses[Math.floor(Math.random() * skillResponses.length)];
|
|
83
|
+
}
|
|
84
|
+
// Future: Real CLI integration
|
|
85
|
+
async spawnCLI(skillName, message) {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const timeout = setTimeout(() => {
|
|
88
|
+
resolve(this.getMockResponse(skillName, message));
|
|
89
|
+
}, 1e3);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
94
|
+
0 && (module.exports = {
|
|
95
|
+
CLIBridge
|
|
96
|
+
});
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// electron/events-watcher.ts
|
|
31
|
+
var events_watcher_exports = {};
|
|
32
|
+
__export(events_watcher_exports, {
|
|
33
|
+
EventsWatcher: () => EventsWatcher,
|
|
34
|
+
formatToolStatus: () => formatToolStatus
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(events_watcher_exports);
|
|
37
|
+
var fs = __toESM(require("fs"));
|
|
38
|
+
var path = __toESM(require("path"));
|
|
39
|
+
var os = __toESM(require("os"));
|
|
40
|
+
var EventsWatcher = class _EventsWatcher {
|
|
41
|
+
constructor(sessionId) {
|
|
42
|
+
this.fileOffset = 0;
|
|
43
|
+
this.lineBuffer = "";
|
|
44
|
+
this.watcher = null;
|
|
45
|
+
this.pollTimer = null;
|
|
46
|
+
this.fileExistsTimer = null;
|
|
47
|
+
this.callback = null;
|
|
48
|
+
this.stopped = false;
|
|
49
|
+
this.reading = false;
|
|
50
|
+
this.sessionId = sessionId;
|
|
51
|
+
this.filePath = path.join(
|
|
52
|
+
os.homedir(),
|
|
53
|
+
".copilot",
|
|
54
|
+
"session-state",
|
|
55
|
+
sessionId,
|
|
56
|
+
"events.jsonl"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
static {
|
|
60
|
+
this.POLL_INTERVAL_MS = 500;
|
|
61
|
+
}
|
|
62
|
+
static {
|
|
63
|
+
this.FILE_CHECK_INTERVAL_MS = 200;
|
|
64
|
+
}
|
|
65
|
+
static {
|
|
66
|
+
this.MAX_FILE_WAIT_MS = 6e4;
|
|
67
|
+
}
|
|
68
|
+
getSessionId() {
|
|
69
|
+
return this.sessionId;
|
|
70
|
+
}
|
|
71
|
+
getFilePath() {
|
|
72
|
+
return this.filePath;
|
|
73
|
+
}
|
|
74
|
+
start(onEvent) {
|
|
75
|
+
this.callback = onEvent;
|
|
76
|
+
this.stopped = false;
|
|
77
|
+
fs.promises.access(this.filePath, fs.constants.F_OK).then(() => this.startWatching()).catch(() => {
|
|
78
|
+
console.log(`[EventsWatcher] Waiting for events.jsonl: ${this.filePath}`);
|
|
79
|
+
const startTime = Date.now();
|
|
80
|
+
this.fileExistsTimer = setInterval(() => {
|
|
81
|
+
if (this.stopped) {
|
|
82
|
+
if (this.fileExistsTimer) clearInterval(this.fileExistsTimer);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (Date.now() - startTime > _EventsWatcher.MAX_FILE_WAIT_MS) {
|
|
86
|
+
console.warn(`[EventsWatcher] Timed out waiting for events.jsonl after ${_EventsWatcher.MAX_FILE_WAIT_MS / 1e3}s`);
|
|
87
|
+
if (this.fileExistsTimer) clearInterval(this.fileExistsTimer);
|
|
88
|
+
this.fileExistsTimer = null;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
fs.promises.access(this.filePath, fs.constants.F_OK).then(() => {
|
|
92
|
+
console.log(`[EventsWatcher] Found events.jsonl`);
|
|
93
|
+
if (this.fileExistsTimer) clearInterval(this.fileExistsTimer);
|
|
94
|
+
this.fileExistsTimer = null;
|
|
95
|
+
this.startWatching();
|
|
96
|
+
}).catch(() => {
|
|
97
|
+
});
|
|
98
|
+
}, _EventsWatcher.FILE_CHECK_INTERVAL_MS);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
startWatching() {
|
|
102
|
+
this.readNewLines();
|
|
103
|
+
try {
|
|
104
|
+
this.watcher = fs.watch(this.filePath, () => {
|
|
105
|
+
if (!this.stopped) this.readNewLines();
|
|
106
|
+
});
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.log(`[EventsWatcher] fs.watch failed: ${e}`);
|
|
109
|
+
}
|
|
110
|
+
this.pollTimer = setInterval(() => {
|
|
111
|
+
if (!this.stopped) this.readNewLines();
|
|
112
|
+
}, _EventsWatcher.POLL_INTERVAL_MS);
|
|
113
|
+
}
|
|
114
|
+
async readNewLines() {
|
|
115
|
+
if (this.reading) return;
|
|
116
|
+
this.reading = true;
|
|
117
|
+
try {
|
|
118
|
+
const stat = await fs.promises.stat(this.filePath);
|
|
119
|
+
if (stat.size <= this.fileOffset) return;
|
|
120
|
+
const handle = await fs.promises.open(this.filePath, "r");
|
|
121
|
+
try {
|
|
122
|
+
const buf = Buffer.alloc(stat.size - this.fileOffset);
|
|
123
|
+
await handle.read(buf, 0, buf.length, this.fileOffset);
|
|
124
|
+
this.fileOffset = stat.size;
|
|
125
|
+
const text = this.lineBuffer + buf.toString("utf-8");
|
|
126
|
+
const lines = text.split("\n");
|
|
127
|
+
this.lineBuffer = lines.pop() || "";
|
|
128
|
+
for (const line of lines) {
|
|
129
|
+
if (!line.trim()) continue;
|
|
130
|
+
try {
|
|
131
|
+
const event = JSON.parse(line);
|
|
132
|
+
if (this.callback) {
|
|
133
|
+
this.callback(event);
|
|
134
|
+
}
|
|
135
|
+
} catch (e) {
|
|
136
|
+
console.log(`[EventsWatcher] Failed to parse line: ${e}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} finally {
|
|
140
|
+
await handle.close();
|
|
141
|
+
}
|
|
142
|
+
} catch (e) {
|
|
143
|
+
} finally {
|
|
144
|
+
this.reading = false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
stop() {
|
|
148
|
+
this.stopped = true;
|
|
149
|
+
if (this.fileExistsTimer) {
|
|
150
|
+
clearInterval(this.fileExistsTimer);
|
|
151
|
+
this.fileExistsTimer = null;
|
|
152
|
+
}
|
|
153
|
+
if (this.watcher) {
|
|
154
|
+
this.watcher.close();
|
|
155
|
+
this.watcher = null;
|
|
156
|
+
}
|
|
157
|
+
if (this.pollTimer) {
|
|
158
|
+
clearInterval(this.pollTimer);
|
|
159
|
+
this.pollTimer = null;
|
|
160
|
+
}
|
|
161
|
+
this.callback = null;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
function formatToolStatus(toolName, args) {
|
|
165
|
+
const base = (p) => typeof p === "string" ? path.basename(p) : "";
|
|
166
|
+
switch (toolName) {
|
|
167
|
+
case "view":
|
|
168
|
+
return `Reading ${base(args.path)}`;
|
|
169
|
+
case "edit":
|
|
170
|
+
return `Editing ${base(args.path)}`;
|
|
171
|
+
case "create":
|
|
172
|
+
return `Creating ${base(args.path)}`;
|
|
173
|
+
case "powershell":
|
|
174
|
+
const cmd = args.command || "";
|
|
175
|
+
return `Running: ${cmd.length > 40 ? cmd.slice(0, 40) + "\u2026" : cmd}`;
|
|
176
|
+
case "glob":
|
|
177
|
+
return `Finding files: ${args.pattern || ""}`;
|
|
178
|
+
case "grep":
|
|
179
|
+
return `Searching: ${args.pattern || ""}`;
|
|
180
|
+
case "web_fetch":
|
|
181
|
+
return `Fetching: ${args.url || ""}`;
|
|
182
|
+
case "task":
|
|
183
|
+
return `Subtask: ${args.description || "running"}`;
|
|
184
|
+
case "ask_user":
|
|
185
|
+
return "Waiting for your answer";
|
|
186
|
+
case "report_intent":
|
|
187
|
+
return `${args.intent || "Working"}`;
|
|
188
|
+
case "sql":
|
|
189
|
+
return `Query: ${args.description || "running"}`;
|
|
190
|
+
default:
|
|
191
|
+
return `Using ${toolName}`;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
195
|
+
0 && (module.exports = {
|
|
196
|
+
EventsWatcher,
|
|
197
|
+
formatToolStatus
|
|
198
|
+
});
|