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 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
+ });