@tt-a1i/hive 1.2.0 → 1.3.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +119 -14
  2. package/README.en.md +63 -8
  3. package/README.md +37 -6
  4. package/assets/hive-team-view.png +0 -0
  5. package/dist/src/cli/hive-update.d.ts +15 -0
  6. package/dist/src/cli/hive-update.js +81 -0
  7. package/dist/src/cli/hive.js +21 -5
  8. package/dist/src/cli/team.d.ts +6 -0
  9. package/dist/src/cli/team.js +48 -0
  10. package/dist/src/server/agent-launch-cache.js +25 -6
  11. package/dist/src/server/agent-manager.d.ts +2 -2
  12. package/dist/src/server/agent-run-store.d.ts +1 -1
  13. package/dist/src/server/agent-runtime-contract.d.ts +3 -0
  14. package/dist/src/server/agent-runtime.js +3 -0
  15. package/dist/src/server/agent-startup-instructions.js +1 -1
  16. package/dist/src/server/agent-stdin-dispatcher.d.ts +4 -0
  17. package/dist/src/server/agent-stdin-dispatcher.js +12 -0
  18. package/dist/src/server/app.js +12 -1
  19. package/dist/src/server/dispatch-ledger-store.d.ts +22 -1
  20. package/dist/src/server/dispatch-ledger-store.js +34 -3
  21. package/dist/src/server/hive-team-guidance.js +3 -1
  22. package/dist/src/server/open-target-commands.js +0 -17
  23. package/dist/src/server/package-version.d.ts +15 -0
  24. package/dist/src/server/package-version.js +15 -0
  25. package/dist/src/server/route-types.d.ts +7 -0
  26. package/dist/src/server/routes-dispatches.js +4 -2
  27. package/dist/src/server/routes-runtime.js +1 -0
  28. package/dist/src/server/routes-team.js +22 -0
  29. package/dist/src/server/runtime-store-helpers.d.ts +2 -1
  30. package/dist/src/server/runtime-store-helpers.js +14 -4
  31. package/dist/src/server/runtime-store.d.ts +5 -8
  32. package/dist/src/server/runtime-store.js +1 -0
  33. package/dist/src/server/tasks-websocket-server.d.ts +2 -1
  34. package/dist/src/server/tasks-websocket-server.js +18 -2
  35. package/dist/src/server/team-authz.d.ts +1 -1
  36. package/dist/src/server/team-authz.js +1 -1
  37. package/dist/src/server/team-operations.d.ts +16 -1
  38. package/dist/src/server/team-operations.js +28 -1
  39. package/dist/src/server/terminal-input-profile.d.ts +10 -0
  40. package/dist/src/server/terminal-input-profile.js +15 -0
  41. package/dist/src/server/terminal-stream-hub.js +10 -2
  42. package/dist/src/server/terminal-ws-server.d.ts +2 -1
  43. package/dist/src/server/terminal-ws-server.js +2 -2
  44. package/dist/src/server/version-service.js +4 -1
  45. package/dist/src/server/workspace-shell-runtime.d.ts +2 -1
  46. package/dist/src/server/workspace-shell-runtime.js +37 -10
  47. package/dist/src/server/workspace-store-contract.d.ts +1 -0
  48. package/dist/src/server/workspace-store-mutations.d.ts +1 -0
  49. package/dist/src/server/workspace-store-mutations.js +1 -0
  50. package/dist/src/server/workspace-store.js +2 -1
  51. package/dist/src/shared/open-targets.d.ts +1 -1
  52. package/dist/src/shared/open-targets.js +9 -16
  53. package/package.json +2 -2
  54. package/web/dist/assets/AddWorkerDialog-D6-K1wJm.js +1 -0
  55. package/web/dist/assets/AddWorkspaceDialog-Du0lndJ0.js +1 -0
  56. package/web/dist/assets/FirstRunWizard-B8k7S5De.js +1 -0
  57. package/web/dist/assets/WorkerModal-B3XhIvAX.js +1 -0
  58. package/web/dist/assets/WorkspaceTaskDrawer-CjEoLJvS.js +1 -0
  59. package/web/dist/assets/chevron-right-BvbSCniy.js +1 -0
  60. package/web/dist/assets/finder-C4Jmsb0B.png +0 -0
  61. package/web/dist/assets/ghostty-D-Js4rdm.png +0 -0
  62. package/web/dist/assets/index-DB5fHAMI.js +81 -0
  63. package/web/dist/assets/index-Sbdu6Se0.css +1 -0
  64. package/web/dist/assets/zed-C5BQT8X3.png +0 -0
  65. package/web/dist/icons/apple-touch-icon-180.png +0 -0
  66. package/web/dist/icons/icon-192.png +0 -0
  67. package/web/dist/icons/icon-32.png +0 -0
  68. package/web/dist/icons/icon-512-maskable.png +0 -0
  69. package/web/dist/icons/icon-512.png +0 -0
  70. package/web/dist/index.html +11 -3
  71. package/web/dist/manifest.webmanifest +60 -0
  72. package/web/dist/screenshots/wide-overview.png +0 -0
  73. package/web/dist/sw.js +99 -0
  74. package/web/dist/assets/index-BgXxFsKj.css +0 -1
  75. package/web/dist/assets/index-VeKhgpe_.js +0 -66
@@ -1,8 +1,11 @@
1
1
  export const createAgentLaunchCache = (store) => {
2
2
  const launchConfigs = new Map();
3
3
  const workspaceByAgentId = new Map();
4
+ const missingLaunchConfigs = new Set();
4
5
  const cacheKey = (workspaceId, agentId) => `${workspaceId}:${agentId}`;
5
6
  const load = () => {
7
+ launchConfigs.clear();
8
+ workspaceByAgentId.clear();
6
9
  for (const row of store.listLaunchConfigs()) {
7
10
  launchConfigs.set(cacheKey(row.workspaceId, row.agentId), row.config);
8
11
  workspaceByAgentId.set(row.agentId, row.workspaceId);
@@ -11,21 +14,33 @@ export const createAgentLaunchCache = (store) => {
11
14
  load();
12
15
  return {
13
16
  get(workspaceId, agentId) {
14
- const config = launchConfigs.get(cacheKey(workspaceId, agentId));
17
+ const key = cacheKey(workspaceId, agentId);
18
+ const config = launchConfigs.get(key);
15
19
  if (config)
16
20
  return config;
21
+ if (missingLaunchConfigs.has(key)) {
22
+ throw new Error(`Agent launch config not found: ${agentId}`);
23
+ }
17
24
  load();
18
- const reloadedConfig = launchConfigs.get(cacheKey(workspaceId, agentId));
25
+ const reloadedConfig = launchConfigs.get(key);
19
26
  if (reloadedConfig)
20
27
  return reloadedConfig;
28
+ missingLaunchConfigs.add(key);
21
29
  throw new Error(`Agent launch config not found: ${agentId}`);
22
30
  },
23
31
  peek(workspaceId, agentId) {
24
- const config = launchConfigs.get(cacheKey(workspaceId, agentId));
32
+ const key = cacheKey(workspaceId, agentId);
33
+ const config = launchConfigs.get(key);
25
34
  if (config)
26
35
  return config;
36
+ if (missingLaunchConfigs.has(key))
37
+ return undefined;
27
38
  load();
28
- return launchConfigs.get(cacheKey(workspaceId, agentId));
39
+ const reloadedConfig = launchConfigs.get(key);
40
+ if (reloadedConfig)
41
+ return reloadedConfig;
42
+ missingLaunchConfigs.add(key);
43
+ return undefined;
29
44
  },
30
45
  getWorkspaceId(agentId) {
31
46
  return workspaceByAgentId.get(agentId);
@@ -41,12 +56,16 @@ export const createAgentLaunchCache = (store) => {
41
56
  sessionIdCapture: input.sessionIdCapture ?? null,
42
57
  };
43
58
  store.saveLaunchConfig(workspaceId, agentId, normalized);
44
- launchConfigs.set(cacheKey(workspaceId, agentId), normalized);
59
+ const key = cacheKey(workspaceId, agentId);
60
+ launchConfigs.set(key, normalized);
61
+ missingLaunchConfigs.delete(key);
45
62
  workspaceByAgentId.set(agentId, workspaceId);
46
63
  },
47
64
  remove(workspaceId, agentId) {
48
65
  store.deleteLaunchConfig(workspaceId, agentId);
49
- launchConfigs.delete(cacheKey(workspaceId, agentId));
66
+ const key = cacheKey(workspaceId, agentId);
67
+ launchConfigs.delete(key);
68
+ missingLaunchConfigs.add(key);
50
69
  workspaceByAgentId.delete(agentId);
51
70
  },
52
71
  setWorkspaceId(agentId, workspaceId) {
@@ -27,7 +27,7 @@ interface AgentRunRecord extends AgentRunSnapshot {
27
27
  resize: (cols: number, rows: number) => void;
28
28
  resume: () => void;
29
29
  stop: () => void;
30
- write: (text: string) => void;
30
+ write: (input: Buffer | string) => void;
31
31
  };
32
32
  onExit?: (event: {
33
33
  runId: string;
@@ -40,7 +40,7 @@ interface AgentManager {
40
40
  resizeRun: (runId: string, cols: number, rows: number) => void;
41
41
  resumeRun: (runId: string) => void;
42
42
  startAgent: (input: StartAgentInput) => Promise<AgentRunSnapshot>;
43
- writeInput: (runId: string, text: string) => void;
43
+ writeInput: (runId: string, input: Buffer | string) => void;
44
44
  getRun: (runId: string) => AgentRunSnapshot;
45
45
  removeRun: (runId: string) => void;
46
46
  stopRun: (runId: string) => void;
@@ -27,7 +27,7 @@ export declare const createAgentRunStore: (db: Database) => {
27
27
  runId: string;
28
28
  agentId: string;
29
29
  pid: number | null;
30
- status: "starting" | "running" | "exited" | "error";
30
+ status: "error" | "starting" | "running" | "exited";
31
31
  exitCode: number | null;
32
32
  startedAt: number;
33
33
  endedAt: number | null;
@@ -29,6 +29,9 @@ export interface AgentRuntime {
29
29
  requireActiveRun?: boolean;
30
30
  }) => void;
31
31
  writeSendPrompt: (workspaceId: string, workerId: string, dispatchId: string, fromAgentName: string, workerDescription: string, text: string) => void;
32
+ writeCancelPrompt: (workspaceId: string, workerId: string, dispatchId: string, reason: string, input?: {
33
+ requireActiveRun?: boolean;
34
+ }) => void;
32
35
  writeUserInputPrompt: (workspaceId: string, text: string) => void;
33
36
  }
34
37
  export type { StartAgentOptions };
@@ -115,6 +115,9 @@ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, ge
115
115
  writeSendPrompt(workspaceId, workerId, dispatchId, fromAgentName, workerDescription, text) {
116
116
  stdinDispatcher.writeSendPrompt(workspaceId, workerId, dispatchId, fromAgentName, workerDescription, text);
117
117
  },
118
+ writeCancelPrompt(workspaceId, workerId, dispatchId, reason, input = {}) {
119
+ stdinDispatcher.writeCancelPrompt(workspaceId, workerId, dispatchId, reason, input);
120
+ },
118
121
  writeUserInputPrompt(workspaceId, text) {
119
122
  stdinDispatcher.writeUserInputPrompt(workspaceId, text);
120
123
  },
@@ -15,7 +15,7 @@ export const buildAgentStartupInstructions = ({ agent, workspace, }) => {
15
15
  '',
16
16
  ];
17
17
  if (agent.role === 'orchestrator') {
18
- lines.push('你的职责:', '- 直接响应 user,澄清需求并拆解任务', `- 维护 ${TASKS_RELATIVE_PATH}`, '- 按 worker 名称派单,并根据汇报推进下一步', '', '可用 team 命令:', '- team list', '- team send <worker-name> "<task>"', '', '派单时必须使用 worker name,不要使用 worker id。', '', 'Hive worker 派单规则:', ...getHiveTeamRules(agent));
18
+ lines.push('你的职责:', '- 直接响应 user,澄清需求并拆解任务', `- 维护 ${TASKS_RELATIVE_PATH}`, '- 按 worker 名称派单,并根据汇报推进下一步', '', '可用 team 命令:', '- team list', '- team send <worker-name> "<task>"', '- team cancel --dispatch <id> "<reason>"', '', '派单时必须使用 worker name,不要使用 worker id。', '取消未完成派单时必须使用 dispatch id。', '', 'Hive worker 派单规则:', ...getHiveTeamRules(agent));
19
19
  }
20
20
  else {
21
21
  lines.push('可用 team 命令:', '- team report "<完整汇报>" [--dispatch <id>] [--artifact <path>] 完成/失败/阻塞汇报', '- team report --stdin [--dispatch <id>] [--artifact <path>] 同上,从 stdin 读正文(适合多行/含引号/特殊字符)', '- team status "<当前状态>" [--artifact <path>] 中段进度/待命/接入状态', '- team status --stdin [--artifact <path>] 同上,从 stdin 读正文', '- team list 查看 workspace 内的 worker(含状态)', '- team --help 仅查命令用法;**不是**汇报手段', '', '语法要点:', '- 正文是第一个 positional argument,flag 顺序任意:`team report "结论" --dispatch X` 和 `team report --dispatch X "结论"` 都成立。', "- 长正文(多行 / 含引号 / shell 特殊字符 / heredoc)一律走 `--stdin`,并用 *quoted* heredoc(`<<'EOF'`)防止 shell 展开 $vars / 反引号 / 命令替换:", " 例:`team report --stdin --dispatch <id> <<'EOF'`", ' `... 长报告(含 $VAR、`backtick`、"引号" 都按字面量保留)...`', ' `EOF`', '- CLI 报错会同时打印 USAGE,可直接对照修正参数。', '', '完成任务后必须执行 `team report "<结论>"`。', '失败、阻塞或部分完成也用 `team report "<当前状态与原因>"` 汇报。', '没有进行中的任务时,用 `team status "<当前状态>"` 汇报接入、待命或阻塞状态。', '不要调用 team send;worker 之间不能直接派单。', '', 'Hive worker 边界:', ...getHiveTeamRules(agent));
@@ -13,6 +13,7 @@ export declare const buildOrchestratorReportPayload: (workerName: string, text:
13
13
  export declare const buildOrchestratorStatusPayload: (workerName: string, text: string, artifacts: string[]) => string;
14
14
  export declare const buildOrchestratorUserInputPayload: (text: string) => string;
15
15
  export declare const buildWorkerDispatchPayload: (fromAgentName: string, workerDescription: string, dispatchId: string, text: string) => string;
16
+ export declare const buildWorkerCancelPayload: (dispatchId: string, reason: string) => string;
16
17
  export declare const createAgentStdinDispatcher: ({ agentManager, getLaunchConfig, getWorkspaceId, registry, syncRun, }: AgentStdinDispatcherInput) => {
17
18
  writeReportPrompt(workspaceId: string, workerName: string, text: string, artifacts: string[], input?: {
18
19
  requireActiveRun?: boolean;
@@ -21,6 +22,9 @@ export declare const createAgentStdinDispatcher: ({ agentManager, getLaunchConfi
21
22
  requireActiveRun?: boolean;
22
23
  }): void;
23
24
  writeSendPrompt(workspaceId: string, workerId: string, dispatchId: string, fromAgentName: string, workerDescription: string, text: string): void;
25
+ writeCancelPrompt(workspaceId: string, workerId: string, dispatchId: string, reason: string, input?: {
26
+ requireActiveRun?: boolean;
27
+ }): void;
24
28
  writeUserInputPrompt(workspaceId: string, text: string): void;
25
29
  };
26
30
  export {};
@@ -33,6 +33,15 @@ export const buildWorkerDispatchPayload = (fromAgentName, workerDescription, dis
33
33
  buildWorkerReminderTail(dispatchId),
34
34
  '',
35
35
  ].join('\n');
36
+ export const buildWorkerCancelPayload = (dispatchId, reason) => [
37
+ `[Hive 系统消息:dispatch ${dispatchId} 已取消]`,
38
+ '',
39
+ '请停止执行这条派单,不要再为它调用 team report。',
40
+ '',
41
+ '取消原因:',
42
+ reason,
43
+ '',
44
+ ].join('\n');
36
45
  export const createAgentStdinDispatcher = ({ agentManager, getLaunchConfig, getWorkspaceId, registry, syncRun, }) => {
37
46
  const writeToActiveAgentRun = (workspaceId, agentId, text, input = {}) => {
38
47
  const run = registry
@@ -72,6 +81,9 @@ export const createAgentStdinDispatcher = ({ agentManager, getLaunchConfig, getW
72
81
  writeSendPrompt(workspaceId, workerId, dispatchId, fromAgentName, workerDescription, text) {
73
82
  writeToActiveAgentRun(workspaceId, workerId, buildWorkerDispatchPayload(fromAgentName, workerDescription, dispatchId, text), { requireActiveRun: true });
74
83
  },
84
+ writeCancelPrompt(workspaceId, workerId, dispatchId, reason, input = {}) {
85
+ writeToActiveAgentRun(workspaceId, workerId, buildWorkerCancelPayload(dispatchId, reason), input);
86
+ },
75
87
  writeUserInputPrompt(workspaceId, text) {
76
88
  writeToActiveAgentRun(workspaceId, `${workspaceId}:orchestrator`, buildOrchestratorUserInputPayload(text));
77
89
  },
@@ -54,6 +54,14 @@ const CONTENT_TYPES = {
54
54
  '.webp': 'image/webp',
55
55
  '.woff2': 'font/woff2',
56
56
  };
57
+ // PWA boot files must bypass HTTP caching: `sw.js` because the browser does its
58
+ // own byte-diff update check, and the manifest because Chrome consults it on
59
+ // every install/uninstall transition. Without these, SW updates can stall on a
60
+ // stale cached copy and the install prompt won't reflect a renamed app.
61
+ const PWA_BOOT_CACHE_CONTROL = {
62
+ '/manifest.webmanifest': 'max-age=0, must-revalidate',
63
+ '/sw.js': 'no-store',
64
+ };
57
65
  const sendStatic = async (response, staticDir, pathname, request) => {
58
66
  if (request.method !== 'GET' && request.method !== 'HEAD')
59
67
  return false;
@@ -63,6 +71,9 @@ const sendStatic = async (response, staticDir, pathname, request) => {
63
71
  try {
64
72
  const content = await readFile(filePath);
65
73
  response.setHeader('content-type', CONTENT_TYPES[extname(filePath)] ?? 'application/octet-stream');
74
+ const cacheControl = PWA_BOOT_CACHE_CONTROL[pathname];
75
+ if (cacheControl !== undefined)
76
+ response.setHeader('cache-control', cacheControl);
66
77
  response.statusCode = 200;
67
78
  response.end(request.method === 'HEAD' ? undefined : content);
68
79
  return true;
@@ -118,6 +129,6 @@ export const createApp = ({ store, pickFolderService = pickFolder, openWorkspace
118
129
  sendJson(response, 500, { error: message });
119
130
  }
120
131
  });
121
- createTerminalWebSocketServer(server, store);
132
+ createTerminalWebSocketServer(server, store, tasksFileService);
122
133
  return { server, store };
123
134
  };
@@ -1,5 +1,5 @@
1
1
  import type { Database } from 'better-sqlite3';
2
- export type DispatchStatus = 'queued' | 'submitted' | 'reported';
2
+ export type DispatchStatus = 'queued' | 'submitted' | 'reported' | 'cancelled';
3
3
  export interface DispatchRecord {
4
4
  artifacts: string[];
5
5
  createdAt: number;
@@ -28,6 +28,11 @@ interface ReportDispatchInput {
28
28
  toAgentId: string;
29
29
  workspaceId: string;
30
30
  }
31
+ interface CancelDispatchInput {
32
+ dispatchId: string;
33
+ reason: string;
34
+ workspaceId: string;
35
+ }
31
36
  export interface ListDispatchesOptions {
32
37
  limit?: number;
33
38
  offset?: number;
@@ -39,12 +44,28 @@ export declare const createDispatchLedgerStore: (db: Database) => {
39
44
  deleteWorkerDispatches: (workspaceId: string, workerId: string) => void;
40
45
  deleteWorkspaceDispatches: (workspaceId: string) => void;
41
46
  findOpenDispatch: (workspaceId: string, toAgentId: string, dispatchId?: string) => DispatchRecord | undefined;
47
+ findOpenDispatchById: (workspaceId: string, dispatchId: string) => DispatchRecord | undefined;
42
48
  listOpenDispatchKinds: () => Array<{
43
49
  type: "send";
44
50
  worker_id: string;
45
51
  workspace_id: string;
46
52
  }>;
47
53
  listWorkspaceDispatches: (workspaceId: string, options?: ListDispatchesOptions) => DispatchRecord[];
54
+ markCancelled: (input: CancelDispatchInput) => {
55
+ reportedAt: number;
56
+ reportText: string;
57
+ status: "cancelled";
58
+ artifacts: string[];
59
+ createdAt: number;
60
+ deliveredAt: number | null;
61
+ fromAgentId: string | null;
62
+ id: string;
63
+ sequence: number | null;
64
+ submittedAt: number | null;
65
+ text: string;
66
+ toAgentId: string;
67
+ workspaceId: string;
68
+ } | undefined;
48
69
  markReportedByWorker: (input: ReportDispatchInput) => {
49
70
  artifacts: string[];
50
71
  reportedAt: number;
@@ -77,7 +77,7 @@ export const createDispatchLedgerStore = (db) => {
77
77
  WHERE id = ?
78
78
  AND workspace_id = ?
79
79
  AND to_agent_id = ?
80
- AND status != 'reported'
80
+ AND status IN ('queued', 'submitted')
81
81
  LIMIT 1`)
82
82
  .get(dispatchId, workspaceId, toAgentId);
83
83
  return row ? toRecord(row) : undefined;
@@ -87,12 +87,23 @@ export const createDispatchLedgerStore = (db) => {
87
87
  FROM dispatches
88
88
  WHERE workspace_id = ?
89
89
  AND to_agent_id = ?
90
- AND status != 'reported'
90
+ AND status IN ('queued', 'submitted')
91
91
  ORDER BY sequence ASC
92
92
  LIMIT 1`)
93
93
  .get(workspaceId, toAgentId);
94
94
  return row ? toRecord(row) : undefined;
95
95
  };
96
+ const findOpenDispatchById = (workspaceId, dispatchId) => {
97
+ const row = db
98
+ .prepare(`SELECT *
99
+ FROM dispatches
100
+ WHERE id = ?
101
+ AND workspace_id = ?
102
+ AND status IN ('queued', 'submitted')
103
+ LIMIT 1`)
104
+ .get(dispatchId, workspaceId);
105
+ return row ? toRecord(row) : undefined;
106
+ };
96
107
  const markReportedByWorker = (input) => {
97
108
  const dispatch = findOpenDispatch(input.workspaceId, input.toAgentId, input.dispatchId);
98
109
  if (!dispatch) {
@@ -113,6 +124,24 @@ export const createDispatchLedgerStore = (db) => {
113
124
  status: 'reported',
114
125
  };
115
126
  };
127
+ const markCancelled = (input) => {
128
+ const dispatch = findOpenDispatchById(input.workspaceId, input.dispatchId);
129
+ if (!dispatch) {
130
+ return undefined;
131
+ }
132
+ const cancelledAt = Date.now();
133
+ db.prepare(`UPDATE dispatches
134
+ SET status = ?,
135
+ reported_at = ?,
136
+ report_text = ?
137
+ WHERE id = ?`).run('cancelled', cancelledAt, input.reason, dispatch.id);
138
+ return {
139
+ ...dispatch,
140
+ reportedAt: cancelledAt,
141
+ reportText: input.reason,
142
+ status: 'cancelled',
143
+ };
144
+ };
116
145
  const listWorkspaceDispatches = (workspaceId, options = {}) => {
117
146
  const offset = options.offset ?? 0;
118
147
  const limit = options.limit ?? 100;
@@ -138,7 +167,7 @@ export const createDispatchLedgerStore = (db) => {
138
167
  return db
139
168
  .prepare(`SELECT workspace_id, to_agent_id AS worker_id, 'send' AS type
140
169
  FROM dispatches
141
- WHERE status != 'reported'
170
+ WHERE status IN ('queued', 'submitted')
142
171
  ORDER BY sequence ASC`)
143
172
  .all();
144
173
  };
@@ -154,8 +183,10 @@ export const createDispatchLedgerStore = (db) => {
154
183
  deleteWorkerDispatches,
155
184
  deleteWorkspaceDispatches,
156
185
  findOpenDispatch,
186
+ findOpenDispatchById,
157
187
  listOpenDispatchKinds,
158
188
  listWorkspaceDispatches,
189
+ markCancelled,
159
190
  markReportedByWorker,
160
191
  markSubmitted,
161
192
  };
@@ -14,7 +14,7 @@
14
14
  * abstract identity restatement.
15
15
  */
16
16
  export const ORCHESTRATOR_REMINDER_TAIL = '<hive-system-reminder>\n' +
17
- 'You are the Hive Orchestrator. Reply by either: (a) `team send "<worker-name>" "<task>"` to dispatch follow-up work to a Hive worker, or (b) plain text to the user. Never call your CLI\'s built-in subagent tools (Task / Explore / etc.) — they bypass Hive and will not appear in the UI.\n' +
17
+ 'You are the Hive Orchestrator. Reply by either: (a) `team send "<worker-name>" "<task>"` to dispatch follow-up work to a Hive worker, (b) `team cancel --dispatch <id> "<reason>"` to cancel an obsolete dispatch, or (c) plain text to the user. Never call your CLI\'s built-in subagent tools (Task / Explore / etc.) — they bypass Hive and will not appear in the UI.\n' +
18
18
  '</hive-system-reminder>';
19
19
  /**
20
20
  * Tail reminder appended to dispatches sent TO a worker. Reinforces the
@@ -31,6 +31,7 @@ const ORCHESTRATOR_RULES = [
31
31
  '普通、低风险、几分钟内能直接完成的小任务可以自己做;不要为了形式感派 worker。需要并行、长时间执行、独立 review/test、专门角色,或 user 明确要求 worker/成员处理时,再用 `team send`。',
32
32
  '如果只有一个可用 worker,直接用 `team send <worker-name> "<task>"` 派给它;不要把选择题丢回给 user。',
33
33
  '当 user 要你“让 worker ...”时,必须用 `team send <worker-name> "<task>"` 派给 Hive worker。',
34
+ '方向变更或 user 明确取消某个未完成派单时,使用 `team cancel --dispatch <id> "<reason>"` 显式关闭旧 dispatch;不要只用自然语言说“取消”。',
34
35
  '不要使用你所在 CLI 的内置 subagent / 子代理工具(如 Task / Explore 等)来代替 Hive worker;它们不会出现在 Hive UI,也不会更新 Hive 调度状态。',
35
36
  '`team list` 返回的 `last_pty_line` 是该 worker PTY 终端的最后一行原始输出(含任意 stdout / help / 控制序列噪声),**不是** worker 的正式汇报。正式汇报只来自 stdin 注入的 `[Hive 系统消息:来自 @<name> 的汇报]` 或 `[Hive 系统消息:来自 @<name> 的状态更新]`——只把这两种来源当作 reply。',
36
37
  ];
@@ -74,6 +75,7 @@ export const buildProtocolDoc = () => [
74
75
  '',
75
76
  '- `team list` — show workspace members and their status',
76
77
  '- `team send "<worker-name>" "<task>"` — dispatch to a worker by name (never id)',
78
+ '- `team cancel --dispatch <id> "<reason>"` — cancel an obsolete open dispatch',
77
79
  '',
78
80
  '## `team` CLI — worker',
79
81
  '',
@@ -20,23 +20,10 @@ const macAttempts = (targetId, path) => {
20
20
  return [{ command: 'open', args: ['-a', 'Visual Studio Code - Insiders', path] }];
21
21
  case 'cursor':
22
22
  return [{ command: 'open', args: ['-a', 'Cursor', path] }];
23
- case 'windsurf':
24
- return [{ command: 'open', args: ['-a', 'Windsurf', path] }];
25
23
  case 'terminal':
26
24
  return [{ command: 'open', args: ['-a', 'Terminal', path] }];
27
- case 'iterm2':
28
- // Bundle name has always been `iTerm.app` even after the iTerm2 rename;
29
- // the `iTerm2` fallback in earlier ports is cargo-cult.
30
- return [{ command: 'open', args: ['-a', 'iTerm', path] }];
31
25
  case 'ghostty':
32
26
  return [{ command: 'open', args: ['-a', 'Ghostty', path] }];
33
- case 'intellijidea':
34
- // 2025.3 unified the editions, but installs predating that still ship
35
- // `IntelliJ IDEA CE.app` — retry once on the CE bundle.
36
- return [
37
- { command: 'open', args: ['-a', 'IntelliJ IDEA', path] },
38
- { command: 'open', args: ['-a', 'IntelliJ IDEA CE', path] },
39
- ];
40
27
  case 'zed':
41
28
  return [{ command: 'open', args: ['-a', 'Zed', path] }];
42
29
  }
@@ -51,8 +38,6 @@ const linuxAttempts = (targetId, path) => {
51
38
  return [{ command: 'code-insiders', args: [path] }];
52
39
  case 'cursor':
53
40
  return [{ command: 'cursor', args: [path] }];
54
- case 'windsurf':
55
- return [{ command: 'windsurf', args: [path] }];
56
41
  case 'zed':
57
42
  return [{ command: 'zed', args: [path] }];
58
43
  default:
@@ -69,8 +54,6 @@ const windowsAttempts = (targetId, path) => {
69
54
  return [{ command: 'code-insiders', args: [path] }];
70
55
  case 'cursor':
71
56
  return [{ command: 'cursor', args: [path] }];
72
- case 'windsurf':
73
- return [{ command: 'windsurf', args: [path] }];
74
57
  case 'zed':
75
58
  return [{ command: 'zed', args: [path] }];
76
59
  default:
@@ -1,2 +1,17 @@
1
1
  export declare const PACKAGE_NAME = "@tt-a1i/hive";
2
+ /**
3
+ * Canonical argv for the upgrade command. Sharing one source between the
4
+ * server's install hint (`version-service.ts`) and the CLI upgrade path
5
+ * (`hive-update.ts`) keeps the two from drifting if the package name ever
6
+ * moves.
7
+ */
8
+ export declare const INSTALL_COMMAND_ARGS: readonly ["install", "-g", "@tt-a1i/hive@latest"];
9
+ export declare const INSTALL_COMMAND_DISPLAY: string;
10
+ /**
11
+ * Windows ships npm as `npm.cmd` (a batch shim); Node's `child_process.spawn`
12
+ * will not resolve `.cmd` without `shell: true` or an explicit suffix, so the
13
+ * default `'npm'` produces ENOENT on Windows. Use this helper any time you
14
+ * spawn npm directly.
15
+ */
16
+ export declare const getNpmCommand: (platform?: NodeJS.Platform) => string;
2
17
  export declare const readPackageVersion: () => string;
@@ -2,6 +2,21 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  export const PACKAGE_NAME = '@tt-a1i/hive';
5
+ /**
6
+ * Canonical argv for the upgrade command. Sharing one source between the
7
+ * server's install hint (`version-service.ts`) and the CLI upgrade path
8
+ * (`hive-update.ts`) keeps the two from drifting if the package name ever
9
+ * moves.
10
+ */
11
+ export const INSTALL_COMMAND_ARGS = ['install', '-g', `${PACKAGE_NAME}@latest`];
12
+ export const INSTALL_COMMAND_DISPLAY = `npm ${INSTALL_COMMAND_ARGS.join(' ')}`;
13
+ /**
14
+ * Windows ships npm as `npm.cmd` (a batch shim); Node's `child_process.spawn`
15
+ * will not resolve `.cmd` without `shell: true` or an explicit suffix, so the
16
+ * default `'npm'` produces ENOENT on Windows. Use this helper any time you
17
+ * spawn npm directly.
18
+ */
19
+ export const getNpmCommand = (platform = process.platform) => platform === 'win32' ? 'npm.cmd' : 'npm';
5
20
  export const readPackageVersion = () => {
6
21
  let dir = dirname(fileURLToPath(import.meta.url));
7
22
  for (let depth = 0; depth < 8; depth += 1) {
@@ -22,6 +22,13 @@ export interface ReportTaskBody {
22
22
  status?: string;
23
23
  artifacts?: unknown[];
24
24
  }
25
+ export interface CancelTaskBody {
26
+ dispatch_id?: string;
27
+ project_id: string;
28
+ from_agent_id: string;
29
+ token?: string;
30
+ reason?: string;
31
+ }
25
32
  export interface CreateWorkspaceBody {
26
33
  path: string;
27
34
  name: string;
@@ -1,7 +1,7 @@
1
1
  import { serializeDispatchRecord } from './dispatch-ledger-serializer.js';
2
2
  import { getRequiredParam, route, sendJson } from './route-helpers.js';
3
3
  import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
4
- const DISPATCH_STATUSES = new Set(['queued', 'submitted', 'reported']);
4
+ const DISPATCH_STATUSES = new Set(['queued', 'submitted', 'reported', 'cancelled']);
5
5
  const MAX_DISPATCH_LIMIT = 100;
6
6
  const MAX_DISPATCH_OFFSET = 100_000;
7
7
  const readBoundedInt = (response, value, name, fallback, max) => {
@@ -33,7 +33,9 @@ export const dispatchRoutes = [
33
33
  }
34
34
  const state = url.searchParams.get('state');
35
35
  if (state !== null && !isDispatchStatus(state)) {
36
- sendJson(response, 400, { error: 'state must be queued, submitted, or reported' });
36
+ sendJson(response, 400, {
37
+ error: 'state must be queued, submitted, reported, or cancelled',
38
+ });
37
39
  return;
38
40
  }
39
41
  const limit = readBoundedInt(response, url.searchParams.get('limit'), 'limit', MAX_DISPATCH_LIMIT, MAX_DISPATCH_LIMIT);
@@ -25,6 +25,7 @@ export const runtimeRoutes = [
25
25
  agent_name: summary?.agent_name ?? 'Shell',
26
26
  run_id: run.runId,
27
27
  status: run.status,
28
+ terminal_input_profile: summary?.terminal_input_profile ?? 'default',
28
29
  });
29
30
  }),
30
31
  route('DELETE', '/api/workspaces/:workspaceId/shell/:runId', ({ params, request, response, store }) => {
@@ -29,6 +29,28 @@ export const teamRoutes = [
29
29
  });
30
30
  sendJson(response, 202, { dispatch_id: dispatch.id, ok: true });
31
31
  }),
32
+ route('POST', '/api/team/cancel', async ({ request, response, store }) => {
33
+ const body = await readJsonBody(request);
34
+ const projectId = requireNonEmptyString(body.project_id, 'project_id');
35
+ const fromAgentId = requireNonEmptyString(body.from_agent_id, 'from_agent_id');
36
+ const dispatchId = requireNonEmptyString(body.dispatch_id, 'dispatch_id');
37
+ const reason = requireNonEmptyString(body.reason, 'reason');
38
+ const agent = authenticateCliAgent({
39
+ fromAgentId,
40
+ getAgent: store.getAgent,
41
+ token: body.token,
42
+ validateToken: store.validateAgentToken,
43
+ workspaceId: projectId,
44
+ });
45
+ requireCommandForRole(agent, 'cancel');
46
+ const result = store.cancelTask(projectId, dispatchId, { fromAgentId, reason });
47
+ sendJson(response, 202, {
48
+ dispatch_id: result.dispatch?.id ?? null,
49
+ forward_error: result.forwardError,
50
+ forwarded: result.forwarded,
51
+ ok: true,
52
+ });
53
+ }),
32
54
  route('POST', '/api/team/report', async ({ request, response, store }) => {
33
55
  const body = await readJsonBody(request);
34
56
  const projectId = requireNonEmptyString(body.project_id, 'project_id');
@@ -52,6 +52,7 @@ export declare const createRuntimeStoreLifecycle: ({ agentManager, services, }:
52
52
  agent_name: string;
53
53
  run_id: string;
54
54
  status: import("./agent-manager.js").RunStatus;
55
+ terminal_input_profile: import("./terminal-input-profile.js").TerminalInputProfile;
55
56
  }[];
56
57
  startAgent: (workspaceId: string, agentId: string, input: {
57
58
  hivePort: string;
@@ -74,7 +75,7 @@ export declare const createRuntimeStoreLifecycle: ({ agentManager, services, }:
74
75
  })[]>;
75
76
  registerTasksListener: (listener: (workspaceId: string, content: string) => void) => () => void;
76
77
  startWorkspaceWatch: (workspaceId: string) => Promise<void>;
77
- writeRunInput: (runId: string, text: string) => void;
78
+ writeRunInput: (runId: string, input: Buffer | string) => void;
78
79
  pauseTerminalRun: (runId: string) => void;
79
80
  resizeTerminalRun: (runId: string, cols: number, rows: number) => void;
80
81
  resumeTerminalRun: (runId: string) => void;
@@ -10,6 +10,7 @@ import { createSettingsStore } from './settings-store.js';
10
10
  import { createTasksFileService } from './tasks-file.js';
11
11
  import { createTasksFileWatcher } from './tasks-file-watcher.js';
12
12
  import { createTeamOperations } from './team-operations.js';
13
+ import { resolveTerminalInputProfile } from './terminal-input-profile.js';
13
14
  import { createUiAuth } from './ui-auth.js';
14
15
  import { createWorkerOutputTracker } from './worker-output-tracker.js';
15
16
  import { createWorkspaceShellRuntime } from './workspace-shell-runtime.js';
@@ -63,7 +64,9 @@ export const createRuntimeStoreServices = (options = {}) => {
63
64
  deleteDispatch: dispatchLedgerStore.deleteDispatch,
64
65
  deleteMessage: messageLogStore.deleteMessage,
65
66
  findOpenDispatch: dispatchLedgerStore.findOpenDispatch,
67
+ findOpenDispatchById: dispatchLedgerStore.findOpenDispatchById,
66
68
  insertMessage: messageLogStore.insertMessage,
69
+ markDispatchCancelled: dispatchLedgerStore.markCancelled,
67
70
  markDispatchReportedByWorker: dispatchLedgerStore.markReportedByWorker,
68
71
  markDispatchSubmitted: dispatchLedgerStore.markSubmitted,
69
72
  workspaceStore,
@@ -167,8 +170,15 @@ export const createRuntimeStoreLifecycle = ({ agentManager, services, }) => {
167
170
  const run = services.agentRuntime.getActiveRunByAgentId(workspaceId, agent.id);
168
171
  if (!run)
169
172
  return [];
173
+ const launchConfig = services.agentRuntime.peekAgentLaunchConfig(workspaceId, agent.id);
170
174
  return [
171
- { agent_id: agent.id, agent_name: agent.name, run_id: run.runId, status: run.status },
175
+ {
176
+ agent_id: agent.id,
177
+ agent_name: agent.name,
178
+ run_id: run.runId,
179
+ status: run.status,
180
+ terminal_input_profile: resolveTerminalInputProfile(launchConfig),
181
+ },
172
182
  ];
173
183
  }),
174
184
  ...services.shellRuntime.listTerminalRuns(workspaceId),
@@ -186,14 +196,14 @@ export const createRuntimeStoreLifecycle = ({ agentManager, services, }) => {
186
196
  const workspace = services.workspaceStore.getWorkspaceSnapshot(workspaceId);
187
197
  await services.tasksFileWatcher.start(workspaceId, workspace.summary.path);
188
198
  },
189
- writeRunInput: (runId, text) => {
199
+ writeRunInput: (runId, input) => {
190
200
  if (!agentManager)
191
201
  throw new Error('Agent manager is required for PTY stdin writes');
192
202
  if (services.shellRuntime.hasRun(runId)) {
193
- services.shellRuntime.writeInput(runId, text);
203
+ services.shellRuntime.writeInput(runId, input);
194
204
  return;
195
205
  }
196
- agentManager.writeInput(runId, text);
206
+ agentManager.writeInput(runId, input);
197
207
  },
198
208
  pauseTerminalRun: (runId) => {
199
209
  if (services.shellRuntime.hasRun(runId))
@@ -6,7 +6,8 @@ import type { DispatchRecord, ListDispatchesOptions } from './dispatch-ledger-st
6
6
  import type { RecoveryMessage } from './message-log-store.js';
7
7
  import type { PtyOutputBus } from './pty-output-bus.js';
8
8
  import type { SettingsStore } from './settings-store.js';
9
- import type { DispatchTaskInput, ReportTaskInput, ReportTaskResult, StatusTaskInput } from './team-operations.js';
9
+ import type { CancelTaskInput, DispatchTaskInput, ReportTaskInput, ReportTaskResult, StatusTaskInput } from './team-operations.js';
10
+ import type { TerminalRunSummary } from './terminal-input-profile.js';
10
11
  import type { WorkerInput, WorkspaceRecord } from './workspace-store.js';
11
12
  interface RuntimeStore {
12
13
  close: () => Promise<void>;
@@ -21,6 +22,7 @@ interface RuntimeStore {
21
22
  dispatchTaskByWorkerName: (workspaceId: string, workerName: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
22
23
  reportTask: (workspaceId: string, workerId: string, input?: ReportTaskInput) => ReportTaskResult;
23
24
  statusTask: (workspaceId: string, workerId: string, input?: StatusTaskInput) => ReportTaskResult;
25
+ cancelTask: (workspaceId: string, dispatchId: string, input: CancelTaskInput) => ReportTaskResult;
24
26
  listDispatches: (workspaceId: string, options?: ListDispatchesOptions) => DispatchRecord[];
25
27
  listWorkers: (workspaceId: string) => TeamListItem[];
26
28
  getLastPtyLineForAgent: (workspaceId: string, agentId: string) => string | null;
@@ -28,12 +30,7 @@ interface RuntimeStore {
28
30
  getWorker: (workspaceId: string, workerId: string) => AgentSummary;
29
31
  getAgent: (workspaceId: string, agentId: string) => AgentSummary;
30
32
  getPtyOutputBus: () => PtyOutputBus;
31
- listTerminalRuns: (workspaceId: string) => Array<{
32
- agent_id: string;
33
- agent_name: string;
34
- run_id: string;
35
- status: string;
36
- }>;
33
+ listTerminalRuns: (workspaceId: string) => TerminalRunSummary[];
37
34
  closeWorkspaceShell: (workspaceId: string, runId: string) => boolean;
38
35
  startWorkspaceShell: (workspaceId: string) => Promise<LiveAgentRun>;
39
36
  configureAgentLaunch: (workspaceId: string, agentId: string, input: AgentLaunchConfigInput) => void;
@@ -57,7 +54,7 @@ interface RuntimeStore {
57
54
  resizeAgentRun: (runId: string, cols: number, rows: number) => void;
58
55
  resumeTerminalRun: (runId: string) => void;
59
56
  settings: SettingsStore;
60
- writeRunInput: (runId: string, text: string) => void;
57
+ writeRunInput: (runId: string, input: Buffer | string) => void;
61
58
  getUiToken: () => string;
62
59
  stopAgentRun: (runId: string) => void;
63
60
  validateAgentToken: (agentId: string, token: string | undefined) => boolean;
@@ -48,6 +48,7 @@ export const createRuntimeStore = (options = {}) => {
48
48
  });
49
49
  },
50
50
  recordUserInput: services.teamOps.recordUserInput,
51
+ cancelTask: services.teamOps.cancelTask,
51
52
  dispatchTask: services.teamOps.dispatchTask,
52
53
  dispatchTaskByWorkerName: services.teamOps.dispatchTaskByWorkerName,
53
54
  reportTask: services.teamOps.reportTask,