copilotoffice 1.0.2 → 1.0.4
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 +6 -4
- package/dist/electron/main.js +1 -1
- package/dist/electron/terminal/ipc-relay.js +1 -1
- package/dist/electron/terminal/preload.js +2 -2
- package/dist/electron/terminal/server.js +87 -79
- package/dist/game.bundle.js +438 -36
- package/package.json +1 -1
- package/dist/electron/events-watcher.js +0 -198
- package/dist/electron/preload.js +0 -90
- package/dist/electron/terminal-protocol.js +0 -18
- package/dist/electron/terminal-server.js +0 -485
package/README.md
CHANGED
|
@@ -2,19 +2,21 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
9
|
- **Pixel-art office environment** — all sprites procedurally generated in code, no external image assets
|
|
8
|
-
- **
|
|
10
|
+
- **3 active NPC agents by default**, each with specialized capabilities:
|
|
9
11
|
- **Gene** (Generalist) — general-purpose coding, debugging, and research
|
|
10
|
-
- **Arthur** (Architect) — orchestrates plans and spins up agents for complex tasks
|
|
11
12
|
- **Dan** (Debugger) — bug investigation and root cause analysis
|
|
12
13
|
- **Alice** (Admin) — has direct access to edit this game's UI code (`workingDir: '.'`)
|
|
13
14
|
- **6 reserve agent slots** — Azure, Val, Rex, Doc, Scout, and Penny have pre-generated sprites ready to activate
|
|
15
|
+
- **Arthur (Architect)** appears in fleet v-team offices (and can be toggled into the default office in config)
|
|
14
16
|
- **Real terminal integration** via xterm.js — agents run actual Copilot CLI sessions through node-pty
|
|
15
17
|
- **Multi-office management** — switch between projects with independent agent state per office
|
|
16
|
-
- **Meeting Mode** —
|
|
17
|
-
- **Fleet execution** — parallel agent spawning for approved task plans
|
|
18
|
+
- **Meeting Mode** — private meeting room for planning and decomposing complex tasks into structured subtasks
|
|
19
|
+
- **Fleet execution** — parallel agent spawning in dedicated v-team offices for approved task plans
|
|
18
20
|
- **Real-time status badges** — agent states (thinking, waiting, ready, slacking) with animated indicators
|
|
19
21
|
- **Toast & OS notifications** — configurable per-event notifications for agent activity
|
|
20
22
|
- **Session persistence** — terminal sessions and history survive restarts
|
package/dist/electron/main.js
CHANGED
|
@@ -211,7 +211,7 @@ var TerminalRelay = class {
|
|
|
211
211
|
registerIpc() {
|
|
212
212
|
import_electron.ipcMain.handle(
|
|
213
213
|
"terminal-start",
|
|
214
|
-
(_event, officeId, agentId, workingDir, cols, rows, preseededPrompt) => this.request({ type: "start", requestId: this.id(), officeId, agentId, workingDir, cols, rows, preseededPrompt })
|
|
214
|
+
(_event, officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode) => this.request({ type: "start", requestId: this.id(), officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode })
|
|
215
215
|
);
|
|
216
216
|
import_electron.ipcMain.handle(
|
|
217
217
|
"terminal-attach",
|
|
@@ -215,7 +215,7 @@ var TerminalRelay = class {
|
|
|
215
215
|
registerIpc() {
|
|
216
216
|
import_electron.ipcMain.handle(
|
|
217
217
|
"terminal-start",
|
|
218
|
-
(_event, officeId, agentId, workingDir, cols, rows, preseededPrompt) => this.request({ type: "start", requestId: this.id(), officeId, agentId, workingDir, cols, rows, preseededPrompt })
|
|
218
|
+
(_event, officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode) => this.request({ type: "start", requestId: this.id(), officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode })
|
|
219
219
|
);
|
|
220
220
|
import_electron.ipcMain.handle(
|
|
221
221
|
"terminal-attach",
|
|
@@ -19,8 +19,8 @@ module.exports = __toCommonJS(preload_exports);
|
|
|
19
19
|
var import_electron = require("electron");
|
|
20
20
|
import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
|
|
21
21
|
// Terminal management
|
|
22
|
-
terminalStart: (officeId, agentId, workingDir, cols, rows, preseededPrompt) => {
|
|
23
|
-
return import_electron.ipcRenderer.invoke("terminal-start", officeId, agentId, workingDir, cols, rows, preseededPrompt);
|
|
22
|
+
terminalStart: (officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode) => {
|
|
23
|
+
return import_electron.ipcRenderer.invoke("terminal-start", officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode);
|
|
24
24
|
},
|
|
25
25
|
terminalWrite: (officeId, agentId, data) => {
|
|
26
26
|
return import_electron.ipcRenderer.invoke("terminal-write", officeId, agentId, data);
|
|
@@ -5789,10 +5789,14 @@ function killPtyProcess(proc) {
|
|
|
5789
5789
|
proc.process.kill();
|
|
5790
5790
|
}
|
|
5791
5791
|
var pendingPreseededPrompts = /* @__PURE__ */ new Map();
|
|
5792
|
-
async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows, preseededPrompt) {
|
|
5792
|
+
async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode = "copilot") {
|
|
5793
5793
|
if (!terminalBackend || !terminalBackend.isAvailable()) {
|
|
5794
5794
|
return { success: false, error: "terminal backend not available" };
|
|
5795
5795
|
}
|
|
5796
|
+
const shellOnlyMode = launchMode === "shell";
|
|
5797
|
+
if (shellOnlyMode && terminalBackend.name !== "node-pty") {
|
|
5798
|
+
return { success: false, error: "shell mode requires node-pty backend" };
|
|
5799
|
+
}
|
|
5796
5800
|
const ck = compositeKey(officeId, agentId);
|
|
5797
5801
|
const existingTerminalKey = agentToTerminal.get(ck);
|
|
5798
5802
|
if (existingTerminalKey && ptyProcesses.has(existingTerminalKey)) {
|
|
@@ -5843,17 +5847,17 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
|
|
|
5843
5847
|
workingDir
|
|
5844
5848
|
});
|
|
5845
5849
|
agentToTerminal.set(ck, terminalKey);
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
let hasSignalledReady =
|
|
5850
|
+
if (!shellOnlyMode) {
|
|
5851
|
+
send({ type: "terminal-preload-status", agentId, status: "preloading" });
|
|
5852
|
+
}
|
|
5853
|
+
let hasSignalledReady = shellOnlyMode;
|
|
5850
5854
|
let skippedEventCount = 0;
|
|
5851
|
-
agentReadyState.set(ck,
|
|
5852
|
-
if (preseededPrompt) {
|
|
5855
|
+
agentReadyState.set(ck, shellOnlyMode);
|
|
5856
|
+
if (!shellOnlyMode && preseededPrompt) {
|
|
5853
5857
|
pendingPreseededPrompts.set(ck, preseededPrompt);
|
|
5854
5858
|
}
|
|
5855
5859
|
const signalReady = () => {
|
|
5856
|
-
if (hasSignalledReady) return;
|
|
5860
|
+
if (shellOnlyMode || hasSignalledReady) return;
|
|
5857
5861
|
hasSignalledReady = true;
|
|
5858
5862
|
agentReadyState.set(ck, true);
|
|
5859
5863
|
console.log(`[TermServer] Agent ${ck} signalled READY at ${Date.now()} (skipped ${skippedEventCount} startup events)`);
|
|
@@ -5865,77 +5869,81 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
|
|
|
5865
5869
|
proc.write(prompt + "\r");
|
|
5866
5870
|
}
|
|
5867
5871
|
};
|
|
5868
|
-
if (
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
if (
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
if (!
|
|
5907
|
-
|
|
5908
|
-
const
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
const
|
|
5912
|
-
const
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5872
|
+
if (!shellOnlyMode) {
|
|
5873
|
+
const watcher = eventSourceFactory.create(sessionId);
|
|
5874
|
+
agentWatchers.set(ck, watcher);
|
|
5875
|
+
if (terminalBackend.name === "copilot-sdk") {
|
|
5876
|
+
setTimeout(signalReady, 50);
|
|
5877
|
+
}
|
|
5878
|
+
const watcherCallback = (event, isHistorical) => {
|
|
5879
|
+
if (!hasSignalledReady && (event.type === "assistant.turn_end" || event.type === "user.message")) {
|
|
5880
|
+
signalReady();
|
|
5881
|
+
}
|
|
5882
|
+
if (isHistorical) {
|
|
5883
|
+
skippedEventCount++;
|
|
5884
|
+
return;
|
|
5885
|
+
}
|
|
5886
|
+
if (!hasSignalledReady) {
|
|
5887
|
+
skippedEventCount++;
|
|
5888
|
+
return;
|
|
5889
|
+
}
|
|
5890
|
+
if (event.type === "tool.execution_start") {
|
|
5891
|
+
const d = event.data;
|
|
5892
|
+
console.log(`[TermServer] Forwarding tool_start for ${ck}: ${d.toolName}`);
|
|
5893
|
+
send({ type: "copilot-tool-start", agentId, toolName: d.toolName, toolId: d.toolCallId, status: formatToolStatus(d.toolName, d.arguments) });
|
|
5894
|
+
} else if (event.type === "tool.execution_complete") {
|
|
5895
|
+
const d = event.data;
|
|
5896
|
+
console.log(`[TermServer] Forwarding tool_complete for ${ck}: ${d.toolCallId}`);
|
|
5897
|
+
send({ type: "copilot-tool-complete", agentId, toolId: d.toolCallId, success: d.success });
|
|
5898
|
+
}
|
|
5899
|
+
if (event.type === "assistant.turn_end") {
|
|
5900
|
+
agentInTurn.set(ck, false);
|
|
5901
|
+
console.log(`[TermServer] Forwarding turn_end for ${ck}`);
|
|
5902
|
+
send({ type: "copilot-turn-end", agentId });
|
|
5903
|
+
} else if (event.type === "assistant.turn_start") {
|
|
5904
|
+
agentInTurn.set(ck, true);
|
|
5905
|
+
console.log(`[TermServer] Forwarding turn_start for ${ck}`);
|
|
5906
|
+
send({ type: "copilot-turn-start", agentId });
|
|
5907
|
+
} else if (event.type === "user.message") {
|
|
5908
|
+
console.log(`[TermServer] Forwarding user_message for ${ck}, data keys: ${JSON.stringify(Object.keys(event.data || {}))}`);
|
|
5909
|
+
send({ type: "copilot-user-message", agentId });
|
|
5910
|
+
if (!hasAutoTitled.has(ck)) {
|
|
5911
|
+
hasAutoTitled.add(ck);
|
|
5912
|
+
const existing = officeData.sessionMeta.get(agentId);
|
|
5913
|
+
if (!existing?.title) {
|
|
5914
|
+
const d = event.data;
|
|
5915
|
+
const msgText = d?.content || d?.message || d?.text || d?.input || d?.prompt || d?.body || "";
|
|
5916
|
+
const raw = String(msgText).trim();
|
|
5917
|
+
if (raw) {
|
|
5918
|
+
const title = raw.length > 80 ? raw.slice(0, 77) + "..." : raw;
|
|
5919
|
+
const meta = existing || { title: "" };
|
|
5920
|
+
meta.title = title;
|
|
5921
|
+
officeData.sessionMeta.set(agentId, meta);
|
|
5922
|
+
saveOfficeSessionFile(officeId);
|
|
5923
|
+
console.log(`[TermServer] Auto-titled ${ck}: "${title}"`);
|
|
5924
|
+
send({ type: "session-meta-updated", agentId, meta: { ...meta } });
|
|
5925
|
+
}
|
|
5918
5926
|
}
|
|
5919
5927
|
}
|
|
5928
|
+
} else if (event.type === "subagent.started") {
|
|
5929
|
+
const d = event.data;
|
|
5930
|
+
console.log(`[TermServer] Subagent started for ${ck}: ${d.agentName ?? "unknown"} (toolCallId: ${d.toolCallId ?? "?"})`);
|
|
5931
|
+
} else if (event.type === "subagent.completed") {
|
|
5932
|
+
const d = event.data;
|
|
5933
|
+
console.log(`[TermServer] Subagent completed for ${ck}: ${d.agentName ?? "unknown"} (toolCallId: ${d.toolCallId ?? "?"})`);
|
|
5934
|
+
} else if (event.type === "subagent.failed") {
|
|
5935
|
+
const d = event.data;
|
|
5936
|
+
console.log(`[TermServer] Subagent FAILED for ${ck}: ${d.agentName ?? "unknown"} (toolCallId: ${d.toolCallId ?? "?"}, error: ${d.error ?? "unknown"})`);
|
|
5937
|
+
}
|
|
5938
|
+
const isFleetCriticalEvent = event.type.startsWith("subagent.") || event.type === "system.notification" || event.type === "tool.execution_start" && event.data?.toolName === "task";
|
|
5939
|
+
if (isFleetCriticalEvent || hasActiveViewer(ck)) {
|
|
5940
|
+
send({ type: "copilot-event", agentId, event });
|
|
5941
|
+
} else {
|
|
5942
|
+
console.warn(`[TermServer] Dropped copilot-event ${event.type} for ${ck} \u2014 no active viewer (viewers: [${[...activeAgentViewers].join(", ")}])`);
|
|
5920
5943
|
}
|
|
5921
|
-
}
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
} else if (event.type === "subagent.completed") {
|
|
5925
|
-
const d = event.data;
|
|
5926
|
-
console.log(`[TermServer] Subagent completed for ${ck}: ${d.agentName ?? "unknown"} (toolCallId: ${d.toolCallId ?? "?"})`);
|
|
5927
|
-
} else if (event.type === "subagent.failed") {
|
|
5928
|
-
const d = event.data;
|
|
5929
|
-
console.log(`[TermServer] Subagent FAILED for ${ck}: ${d.agentName ?? "unknown"} (toolCallId: ${d.toolCallId ?? "?"}, error: ${d.error ?? "unknown"})`);
|
|
5930
|
-
}
|
|
5931
|
-
const isFleetCriticalEvent = event.type.startsWith("subagent.") || event.type === "system.notification" || event.type === "tool.execution_start" && event.data?.toolName === "task";
|
|
5932
|
-
if (isFleetCriticalEvent || hasActiveViewer(ck)) {
|
|
5933
|
-
send({ type: "copilot-event", agentId, event });
|
|
5934
|
-
} else {
|
|
5935
|
-
console.warn(`[TermServer] Dropped copilot-event ${event.type} for ${ck} \u2014 no active viewer (viewers: [${[...activeAgentViewers].join(", ")}])`);
|
|
5936
|
-
}
|
|
5937
|
-
};
|
|
5938
|
-
setTimeout(() => watcher.start(watcherCallback), 100);
|
|
5944
|
+
};
|
|
5945
|
+
setTimeout(() => watcher.start(watcherCallback), 100);
|
|
5946
|
+
}
|
|
5939
5947
|
const MAX_PENDING_BYTES = 65536;
|
|
5940
5948
|
let pendingData = "";
|
|
5941
5949
|
let flushTimer = null;
|
|
@@ -5948,7 +5956,7 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
|
|
|
5948
5956
|
};
|
|
5949
5957
|
proc.onData((data) => {
|
|
5950
5958
|
appendToScrollback(ck, data);
|
|
5951
|
-
if (terminalBackend?.name === "node-pty" && !hasSignalledReady && data.includes("Environment loaded")) {
|
|
5959
|
+
if (!shellOnlyMode && terminalBackend?.name === "node-pty" && !hasSignalledReady && data.includes("Environment loaded")) {
|
|
5952
5960
|
console.log(`[TermServer] Primary ready signal for ${ck}: "Environment loaded" detected`);
|
|
5953
5961
|
signalReady();
|
|
5954
5962
|
}
|
|
@@ -5975,7 +5983,7 @@ async function startTerminalForAgent(officeId, agentId, workingDir, cols, rows,
|
|
|
5975
5983
|
agentWatchers.delete(ck);
|
|
5976
5984
|
}
|
|
5977
5985
|
});
|
|
5978
|
-
if (terminalBackend.name === "node-pty") {
|
|
5986
|
+
if (!shellOnlyMode && terminalBackend.name === "node-pty") {
|
|
5979
5987
|
setTimeout(() => {
|
|
5980
5988
|
console.log(`[TermServer] Starting copilot --resume for ${ck}: ${sessionId}`);
|
|
5981
5989
|
proc.write(`copilot --resume ${sessionId}\r`);
|
|
@@ -5991,7 +5999,7 @@ async function handleMessage(msg) {
|
|
|
5991
5999
|
case "start": {
|
|
5992
6000
|
const ck = compositeKey(msg.officeId, msg.agentId);
|
|
5993
6001
|
activeAgentViewers.add(ck);
|
|
5994
|
-
const result = await startTerminalForAgent(msg.officeId, msg.agentId, msg.workingDir, msg.cols, msg.rows, msg.preseededPrompt);
|
|
6002
|
+
const result = await startTerminalForAgent(msg.officeId, msg.agentId, msg.workingDir, msg.cols, msg.rows, msg.preseededPrompt, msg.launchMode);
|
|
5995
6003
|
send({ type: "response", requestId: msg.requestId, result });
|
|
5996
6004
|
break;
|
|
5997
6005
|
}
|