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 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
+ ![Agency Office](https://raw.githubusercontent.com/dan1510123/CopilotOffice/main/assets/game-screenshot.png)
6
+
5
7
  ## Features
6
8
 
7
9
  - **Pixel-art office environment** — all sprites procedurally generated in code, no external image assets
8
- - **4 active NPC agents**, each with specialized capabilities:
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** — 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
+ - **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
@@ -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
- send({ type: "terminal-preload-status", agentId, status: "preloading" });
5847
- const watcher = eventSourceFactory.create(sessionId);
5848
- agentWatchers.set(ck, watcher);
5849
- let hasSignalledReady = false;
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, false);
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 (terminalBackend.name === "copilot-sdk") {
5869
- setTimeout(signalReady, 50);
5870
- }
5871
- const watcherCallback = (event, isHistorical) => {
5872
- if (!hasSignalledReady && (event.type === "assistant.turn_end" || event.type === "user.message")) {
5873
- signalReady();
5874
- }
5875
- if (isHistorical) {
5876
- skippedEventCount++;
5877
- return;
5878
- }
5879
- if (!hasSignalledReady) {
5880
- skippedEventCount++;
5881
- return;
5882
- }
5883
- if (event.type === "tool.execution_start") {
5884
- const d = event.data;
5885
- console.log(`[TermServer] Forwarding tool_start for ${ck}: ${d.toolName}`);
5886
- send({ type: "copilot-tool-start", agentId, toolName: d.toolName, toolId: d.toolCallId, status: formatToolStatus(d.toolName, d.arguments) });
5887
- } else if (event.type === "tool.execution_complete") {
5888
- const d = event.data;
5889
- console.log(`[TermServer] Forwarding tool_complete for ${ck}: ${d.toolCallId}`);
5890
- send({ type: "copilot-tool-complete", agentId, toolId: d.toolCallId, success: d.success });
5891
- }
5892
- if (event.type === "assistant.turn_end") {
5893
- agentInTurn.set(ck, false);
5894
- console.log(`[TermServer] Forwarding turn_end for ${ck}`);
5895
- send({ type: "copilot-turn-end", agentId });
5896
- } else if (event.type === "assistant.turn_start") {
5897
- agentInTurn.set(ck, true);
5898
- console.log(`[TermServer] Forwarding turn_start for ${ck}`);
5899
- send({ type: "copilot-turn-start", agentId });
5900
- } else if (event.type === "user.message") {
5901
- console.log(`[TermServer] Forwarding user_message for ${ck}, data keys: ${JSON.stringify(Object.keys(event.data || {}))}`);
5902
- send({ type: "copilot-user-message", agentId });
5903
- if (!hasAutoTitled.has(ck)) {
5904
- hasAutoTitled.add(ck);
5905
- const existing = officeData.sessionMeta.get(agentId);
5906
- if (!existing?.title) {
5907
- const d = event.data;
5908
- const msgText = d?.content || d?.message || d?.text || d?.input || d?.prompt || d?.body || "";
5909
- const raw = String(msgText).trim();
5910
- if (raw) {
5911
- const title = raw.length > 80 ? raw.slice(0, 77) + "..." : raw;
5912
- const meta = existing || { title: "" };
5913
- meta.title = title;
5914
- officeData.sessionMeta.set(agentId, meta);
5915
- saveOfficeSessionFile(officeId);
5916
- console.log(`[TermServer] Auto-titled ${ck}: "${title}"`);
5917
- send({ type: "session-meta-updated", agentId, meta: { ...meta } });
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
- } else if (event.type === "subagent.started") {
5922
- const d = event.data;
5923
- console.log(`[TermServer] Subagent started for ${ck}: ${d.agentName ?? "unknown"} (toolCallId: ${d.toolCallId ?? "?"})`);
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
  }