@tt-a1i/hive 2.0.1 → 2.1.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/CHANGELOG.md +42 -0
- package/README.en.md +15 -6
- package/README.md +26 -4
- package/dist/src/cli/hive.d.ts +4 -0
- package/dist/src/cli/hive.js +25 -3
- package/dist/src/cli/team.d.ts +8 -1
- package/dist/src/cli/team.js +111 -11
- package/dist/src/server/action-center-summary.d.ts +193 -0
- package/dist/src/server/action-center-summary.js +188 -0
- package/dist/src/server/agent-command-resolver.d.ts +6 -0
- package/dist/src/server/agent-command-resolver.js +16 -0
- package/dist/src/server/agent-manager.js +11 -1
- package/dist/src/server/agent-run-starter.js +47 -6
- package/dist/src/server/agent-runtime-types.d.ts +4 -0
- package/dist/src/server/agent-startup-instructions.d.ts +4 -0
- package/dist/src/server/agent-startup-instructions.js +35 -9
- package/dist/src/server/agent-stdin-dispatcher.js +17 -9
- package/dist/src/server/diagnostics-support-bundle.d.ts +288 -0
- package/dist/src/server/diagnostics-support-bundle.js +179 -0
- package/dist/src/server/dispatch-ledger-store.d.ts +4 -1
- package/dist/src/server/dispatch-ledger-store.js +46 -6
- package/dist/src/server/hive-envelope-escape.d.ts +2 -0
- package/dist/src/server/hive-envelope-escape.js +2 -0
- package/dist/src/server/hive-team-guidance.d.ts +1 -1
- package/dist/src/server/hive-team-guidance.js +67 -25
- package/dist/src/server/message-log-store.d.ts +1 -1
- package/dist/src/server/post-start-input-writer.js +8 -2
- package/dist/src/server/preset-launch-support.d.ts +2 -0
- package/dist/src/server/preset-launch-support.js +65 -2
- package/dist/src/server/protocol-event-stats.d.ts +39 -0
- package/dist/src/server/protocol-event-stats.js +84 -0
- package/dist/src/server/recovery-summary.js +19 -14
- package/dist/src/server/role-template-store.d.ts +1 -1
- package/dist/src/server/role-templates.d.ts +1 -0
- package/dist/src/server/role-templates.js +43 -29
- package/dist/src/server/routes-action-center.d.ts +2 -0
- package/dist/src/server/routes-action-center.js +37 -0
- package/dist/src/server/routes-diagnostics.d.ts +2 -0
- package/dist/src/server/routes-diagnostics.js +17 -0
- package/dist/src/server/routes-scenarios.d.ts +25 -0
- package/dist/src/server/routes-scenarios.js +89 -0
- package/dist/src/server/routes-settings.js +2 -11
- package/dist/src/server/routes-team-memory.js +52 -0
- package/dist/src/server/routes-team.js +40 -20
- package/dist/src/server/routes-workspace-memory-dreams.js +8 -0
- package/dist/src/server/routes-workspace-uploads.d.ts +2 -0
- package/dist/src/server/routes-workspace-uploads.js +154 -0
- package/dist/src/server/routes-workspaces.js +29 -3
- package/dist/src/server/routes.js +8 -0
- package/dist/src/server/runtime-message-builders.d.ts +0 -1
- package/dist/src/server/runtime-message-builders.js +0 -8
- package/dist/src/server/runtime-store-contract.d.ts +15 -0
- package/dist/src/server/runtime-store-dream.d.ts +14 -1
- package/dist/src/server/runtime-store-dream.js +49 -1
- package/dist/src/server/runtime-store-helpers.d.ts +7 -0
- package/dist/src/server/runtime-store-helpers.js +85 -22
- package/dist/src/server/runtime-store-worker-mutations.d.ts +11 -0
- package/dist/src/server/runtime-store-worker-mutations.js +46 -0
- package/dist/src/server/runtime-store-workflows.js +10 -6
- package/dist/src/server/runtime-store.js +34 -42
- package/dist/src/server/scenario-presets.d.ts +25 -0
- package/dist/src/server/scenario-presets.js +35 -0
- package/dist/src/server/sentinel-heartbeat.d.ts +30 -0
- package/dist/src/server/sentinel-heartbeat.js +145 -0
- package/dist/src/server/spawn-cli-resolver.d.ts +37 -0
- package/dist/src/server/spawn-cli-resolver.js +70 -0
- package/dist/src/server/spawn-worker-defaults.d.ts +13 -0
- package/dist/src/server/spawn-worker-defaults.js +45 -0
- package/dist/src/server/sqlite-schema-v32.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v32.js +17 -0
- package/dist/src/server/sqlite-schema-v33.d.ts +3 -0
- package/dist/src/server/sqlite-schema-v33.js +18 -0
- package/dist/src/server/sqlite-schema-v34.d.ts +11 -0
- package/dist/src/server/sqlite-schema-v34.js +19 -0
- package/dist/src/server/sqlite-schema-v35.d.ts +3 -0
- package/dist/src/server/sqlite-schema-v35.js +23 -0
- package/dist/src/server/sqlite-schema.d.ts +1 -1
- package/dist/src/server/sqlite-schema.js +35 -1
- package/dist/src/server/system-message.d.ts +5 -2
- package/dist/src/server/system-message.js +5 -2
- package/dist/src/server/tasks-file-watcher.d.ts +8 -0
- package/dist/src/server/tasks-file-watcher.js +31 -2
- package/dist/src/server/team-authz.d.ts +9 -1
- package/dist/src/server/team-authz.js +24 -0
- package/dist/src/server/team-list-serializer.d.ts +2 -2
- package/dist/src/server/team-list-serializer.js +2 -1
- package/dist/src/server/team-memory-digest.js +4 -4
- package/dist/src/server/team-memory-dream-applier.js +24 -3
- package/dist/src/server/team-memory-dream-prompt.d.ts +13 -0
- package/dist/src/server/team-memory-dream-prompt.js +91 -0
- package/dist/src/server/team-memory-dream-run-store.d.ts +2 -0
- package/dist/src/server/team-memory-dream-run-store.js +14 -4
- package/dist/src/server/team-memory-dream-runner.d.ts +2 -21
- package/dist/src/server/team-memory-dream-runner.js +3 -148
- package/dist/src/server/team-memory-dream-store.d.ts +1 -1
- package/dist/src/server/team-memory-dream-store.js +1 -1
- package/dist/src/server/team-operations.d.ts +18 -2
- package/dist/src/server/team-operations.js +222 -33
- package/dist/src/server/team-recap.d.ts +10 -0
- package/dist/src/server/team-recap.js +73 -0
- package/dist/src/server/terminal-input-profile.js +95 -6
- package/dist/src/server/upload-limits.d.ts +2 -0
- package/dist/src/server/upload-limits.js +2 -0
- package/dist/src/server/workflow-cli-policy.d.ts +7 -2
- package/dist/src/server/workflow-cli-policy.js +15 -3
- package/dist/src/server/workflow-run-store.d.ts +1 -0
- package/dist/src/server/workflow-run-store.js +11 -1
- package/dist/src/server/workflow-runner.d.ts +4 -1
- package/dist/src/server/workflow-runner.js +418 -118
- package/dist/src/server/workflow-script-loader.d.ts +3 -2
- package/dist/src/server/workflow-script-loader.js +161 -0
- package/dist/src/server/workspace-store-contract.d.ts +2 -0
- package/dist/src/server/workspace-store.d.ts +1 -1
- package/dist/src/server/workspace-store.js +40 -30
- package/dist/src/server/workspace-upload-store.d.ts +40 -0
- package/dist/src/server/workspace-upload-store.js +295 -0
- package/dist/src/shared/scenario-presets.d.ts +32 -0
- package/dist/src/shared/scenario-presets.js +69 -0
- package/dist/src/shared/types.d.ts +12 -1
- package/package.json +1 -1
- package/web/dist/assets/AddWorkerDialog-DBLhwb91.js +2 -0
- package/web/dist/assets/AddWorkspaceFlow-cxvhVAsT.js +1 -0
- package/web/dist/assets/FirstRunWizard-DlEPnWWw.js +1 -0
- package/web/dist/assets/{MarketplaceDrawer-BFfGT8hH.js → MarketplaceDrawer-CfSiRi8e.js} +11 -11
- package/web/dist/assets/TaskGraphDrawer-C2JufcPs.js +1 -0
- package/web/dist/assets/WhatsNewDialog-vP7buLos.js +1 -0
- package/web/dist/assets/WorkerModal-CSorwcdP.js +1 -0
- package/web/dist/assets/{WorkflowsDrawer-CiIdHS6_.js → WorkflowsDrawer-BXS3w9Uq.js} +1 -1
- package/web/dist/assets/WorkspaceMemoryDrawer-D71ivohr.js +1 -0
- package/web/dist/assets/{WorkspaceTaskDrawer-CyhhEB1Z.js → WorkspaceTaskDrawer-CGCTSHKa.js} +1 -1
- package/web/dist/assets/index-BcwN8cCw.js +79 -0
- package/web/dist/assets/index-StXTPHls.css +1 -0
- package/web/dist/assets/{search-BtRkkEmS.js → search-BZw4T67h.js} +1 -1
- package/web/dist/assets/{square-terminal-lEeQUWb3.js → square-terminal-B7E57In1.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +1 -1
- package/dist/src/server/env-sync-message.d.ts +0 -9
- package/dist/src/server/env-sync-message.js +0 -29
- package/web/dist/assets/AddWorkerDialog-C86CwNgQ.js +0 -2
- package/web/dist/assets/AddWorkspaceFlow-Bm2Jz34D.js +0 -1
- package/web/dist/assets/FirstRunWizard-XzBoEpA5.js +0 -1
- package/web/dist/assets/TaskGraphDrawer-_uVH_0C1.js +0 -1
- package/web/dist/assets/WhatsNewDialog-DkJHmkMs.js +0 -1
- package/web/dist/assets/WorkerModal-BtMJEOG9.js +0 -1
- package/web/dist/assets/WorkspaceMemoryDrawer-C6sNocl_.js +0 -1
- package/web/dist/assets/index-BAiLYajK.css +0 -1
- package/web/dist/assets/index-K-GG8UwR.js +0 -73
|
@@ -1,42 +1,54 @@
|
|
|
1
1
|
import { TASKS_RELATIVE_PATH } from './tasks-file.js';
|
|
2
2
|
export const ORCHESTRATOR_ROLE_DESCRIPTION = [
|
|
3
|
-
'
|
|
4
|
-
'
|
|
5
|
-
'- 澄清目标,把需求拆成可派发的小任务。',
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
'You are the Hive Orchestrator. You reply to the user directly and coordinate real workers shown on the right.',
|
|
4
|
+
'工作方式 / How to work:',
|
|
5
|
+
'- Clarify the goal and split it into dispatchable tasks. / 澄清目标,把需求拆成可派发的小任务。',
|
|
6
|
+
'- Default to Hive dispatch: use `team send`, `team spawn`, or Hive `team workflow` for implementation, audit/review, test, validation, multi-file, or parallel work; do not use your CLI built-in subagents/workflows or keep a non-trivial branch for yourself. / 默认走 Hive 派单:实现、审计/复核、测试、验证、多文件或并行任务用 `team send`、`team spawn` 或 Hive `team workflow`;不要用当前 CLI 内建子代理/工作流,也不要把非平凡分支留给自己。',
|
|
7
|
+
`- Maintain ${TASKS_RELATIVE_PATH} so plan, progress, and blockers stay trackable. / 维护 ${TASKS_RELATIVE_PATH},让当前计划、进度和阻塞可追踪。`,
|
|
8
|
+
'- Drive the next step from worker reports; do not bounce choices back to the user unless a real decision is missing. / 根据成员汇报推进下一步,不把选择题无谓丢回给用户。',
|
|
8
9
|
].join('\n');
|
|
9
10
|
export const CODER_ROLE_DESCRIPTION = [
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'- 先阅读相关文件和现有模式,再动手。',
|
|
13
|
-
'- 优先小步修改,避免无关重构和范围扩张。',
|
|
14
|
-
'- 改动后运行能覆盖风险的验证命令;不能验证时说明原因。',
|
|
15
|
-
'
|
|
11
|
+
'You are an implementation Coder. Turn clear tasks into minimal, correct code changes.',
|
|
12
|
+
'工作方式 / How to work:',
|
|
13
|
+
'- Read relevant files and existing patterns before editing. / 先阅读相关文件和现有模式,再动手。',
|
|
14
|
+
'- Prefer small scoped changes; avoid unrelated refactors and scope creep. / 优先小步修改,避免无关重构和范围扩张。',
|
|
15
|
+
'- After editing, run validation commands that cover the risk; if you cannot validate, say why. / 改动后运行能覆盖风险的验证命令;不能验证时说明原因。',
|
|
16
|
+
'Delivery / 交付说明: include changed files, validation results, and remaining risks or blockers.',
|
|
16
17
|
].join('\n');
|
|
17
18
|
export const REVIEWER_ROLE_DESCRIPTION = [
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'- 优先找真实 bug、回归风险、边界条件和测试缺口。',
|
|
21
|
-
'- 发现问题时给出严重度、文件/行号、触发条件和最小修复建议。',
|
|
22
|
-
'- 没有高风险问题时明确说清剩余风险和未验证范围。',
|
|
23
|
-
'
|
|
19
|
+
'You are a Reviewer. Audit quality; do not replace the Orchestrator and do not edit code by default.',
|
|
20
|
+
'工作方式 / How to work:',
|
|
21
|
+
'- Prioritize real bugs, regression risks, edge cases, and test gaps. / 优先找真实 bug、回归风险、边界条件和测试缺口。',
|
|
22
|
+
'- For each issue, give severity, file/line, trigger condition, and the smallest credible fix. / 发现问题时给出严重度、文件/行号、触发条件和最小修复建议。',
|
|
23
|
+
'- If there is no high-risk issue, state residual risk and what was not verified. / 没有高风险问题时明确说清剩余风险和未验证范围。',
|
|
24
|
+
'Delivery / 交付说明: sort by severity and list blocking issues first.',
|
|
24
25
|
].join('\n');
|
|
25
26
|
export const TESTER_ROLE_DESCRIPTION = [
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'- 先明确要验证的行为、入口和失败条件。',
|
|
29
|
-
'- 优先跑真实命令或真实链路;必要时补充最小测试。',
|
|
30
|
-
'- 记录命令、结果、关键输出和不能覆盖的场景。',
|
|
31
|
-
'
|
|
27
|
+
'You are a Tester. Reproduce, test, and produce evidence-backed validation.',
|
|
28
|
+
'工作方式 / How to work:',
|
|
29
|
+
'- First identify the behavior, entry point, and failure condition to validate. / 先明确要验证的行为、入口和失败条件。',
|
|
30
|
+
'- Prefer real commands or real end-to-end paths; add minimal tests only when needed. / 优先跑真实命令或真实链路;必要时补充最小测试。',
|
|
31
|
+
'- Record commands, results, key output, and scenarios you could not cover. / 记录命令、结果、关键输出和不能覆盖的场景。',
|
|
32
|
+
'Delivery / 交付说明: separate passed, failed, unverified, and recommended next steps.',
|
|
33
|
+
].join('\n');
|
|
34
|
+
export const SENTINEL_ROLE_DESCRIPTION = [
|
|
35
|
+
'You are a read-only Sentinel. Observe team health; do not execute tasks or modify anything.',
|
|
36
|
+
'工作方式 / How to work:',
|
|
37
|
+
'- Hive periodically injects workspace snapshots: member states, open dispatches, and possible orphaned work. / 系统会定期向你注入工作区快照。',
|
|
38
|
+
'- Compare snapshots with your own observations and escalate only anomalies worth Orchestrator attention. / 把快照与你自己的观察对照,只上报值得 Orchestrator 注意的异常。',
|
|
39
|
+
'- Long silence is not automatically stuck; large tasks can be slow. / 长时间无汇报不等于卡死。',
|
|
40
|
+
'Hard boundaries / 硬性边界:',
|
|
41
|
+
'- Observe only: do not edit files, run side-effecting commands, or dispatch work. / 只观察:不改文件、不跑有副作用的命令、不派活。',
|
|
42
|
+
'- You take no dispatches and do not have `team report`; use `team status "<finding>"` for findings. / 你不接派单,也没有 `team report`;发现统一用 `team status "<巡检发现>"` 汇报。',
|
|
43
|
+
'- Stay quiet when nothing is abnormal. / 没有异常时保持安静。',
|
|
32
44
|
].join('\n');
|
|
33
45
|
export const CUSTOM_ROLE_DESCRIPTION = [
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'- 目标:这个成员主要负责什么。',
|
|
37
|
-
'- 边界:哪些事可以做,哪些事不要做。',
|
|
38
|
-
'- 工作方式:如何调查、修改、验证或审查。',
|
|
39
|
-
'- 完成标准:交付时需要说明哪些结果、风险和阻塞。',
|
|
46
|
+
'You are a custom Hive member. Replace this with the member-specific behavior contract.',
|
|
47
|
+
'建议包含 / Include:',
|
|
48
|
+
'- Goal: what this member is responsible for. / 目标:这个成员主要负责什么。',
|
|
49
|
+
'- Boundaries: what it may and may not do. / 边界:哪些事可以做,哪些事不要做。',
|
|
50
|
+
'- Working style: how it investigates, edits, validates, or reviews. / 工作方式:如何调查、修改、验证或审查。',
|
|
51
|
+
'- Done criteria: what results, risks, and blockers to report. / 完成标准:交付时需要说明哪些结果、风险和阻塞。',
|
|
40
52
|
].join('\n');
|
|
41
53
|
export const getDefaultRoleDescription = (role) => {
|
|
42
54
|
switch (role) {
|
|
@@ -48,6 +60,8 @@ export const getDefaultRoleDescription = (role) => {
|
|
|
48
60
|
return REVIEWER_ROLE_DESCRIPTION;
|
|
49
61
|
case 'tester':
|
|
50
62
|
return TESTER_ROLE_DESCRIPTION;
|
|
63
|
+
case 'sentinel':
|
|
64
|
+
return SENTINEL_ROLE_DESCRIPTION;
|
|
51
65
|
case 'custom':
|
|
52
66
|
return CUSTOM_ROLE_DESCRIPTION;
|
|
53
67
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { buildActionCenterSummary } from './action-center-summary.js';
|
|
2
|
+
import { getRequiredParam, route, sendJson } from './route-helpers.js';
|
|
3
|
+
import { buildTeamRecapMarkdown } from './team-recap.js';
|
|
4
|
+
import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
|
|
5
|
+
const RECAP_DISPATCH_LIMIT = 12;
|
|
6
|
+
export const actionCenterRoutes = [
|
|
7
|
+
route('GET', '/api/ui/workspaces/:workspaceId/action-center', ({ params, request, response, store }) => {
|
|
8
|
+
const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Workspace id is required');
|
|
9
|
+
if (!workspaceId)
|
|
10
|
+
return;
|
|
11
|
+
requireUiTokenFromRequest(request, store.validateUiToken, store.authorizeRemoteTunnelRequest);
|
|
12
|
+
if (!store.listWorkspaces().some((workspace) => workspace.id === workspaceId)) {
|
|
13
|
+
sendJson(response, 404, { error: 'Workspace not found' });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
sendJson(response, 200, buildActionCenterSummary({ store, workspaceId }));
|
|
17
|
+
}),
|
|
18
|
+
route('GET', '/api/workspaces/:workspaceId/recap', ({ params, request, response, store }) => {
|
|
19
|
+
const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Workspace id is required');
|
|
20
|
+
if (!workspaceId)
|
|
21
|
+
return;
|
|
22
|
+
requireUiTokenFromRequest(request, store.validateUiToken, store.authorizeRemoteTunnelRequest);
|
|
23
|
+
const workspace = store.listWorkspaces().find((entry) => entry.id === workspaceId);
|
|
24
|
+
if (!workspace) {
|
|
25
|
+
sendJson(response, 404, { error: 'Workspace not found' });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const markdown = buildTeamRecapMarkdown({
|
|
30
|
+
dispatches: store.listRecentDispatches(workspaceId, RECAP_DISPATCH_LIMIT),
|
|
31
|
+
now,
|
|
32
|
+
workers: store.listWorkers(workspaceId),
|
|
33
|
+
workspaceName: workspace.name,
|
|
34
|
+
});
|
|
35
|
+
sendJson(response, 200, { generated_at: now, markdown });
|
|
36
|
+
}),
|
|
37
|
+
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { buildDiagnosticsSupportBundle } from './diagnostics-support-bundle.js';
|
|
2
|
+
import { route, sendJson } from './route-helpers.js';
|
|
3
|
+
import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
|
|
4
|
+
export const diagnosticsRoutes = [
|
|
5
|
+
route('GET', '/api/diagnostics/support-bundle', async ({ request, response, store, versionService }) => {
|
|
6
|
+
requireUiTokenFromRequest(request, store.validateUiToken, store.authorizeRemoteTunnelRequest);
|
|
7
|
+
const version = await versionService.getVersionInfo();
|
|
8
|
+
sendJson(response, 200, buildDiagnosticsSupportBundle({ store, version }));
|
|
9
|
+
}),
|
|
10
|
+
/* Local retention signals (issue #23): per-day protocol event counts kept in
|
|
11
|
+
SQLite. Read-only, local-only — this endpoint plus the support bundle is
|
|
12
|
+
the whole consumption surface, nothing is ever transmitted. */
|
|
13
|
+
route('GET', '/api/diagnostics/retention', async ({ request, response, store }) => {
|
|
14
|
+
requireUiTokenFromRequest(request, store.validateUiToken, store.authorizeRemoteTunnelRequest);
|
|
15
|
+
sendJson(response, 200, store.getRetentionSignals());
|
|
16
|
+
}),
|
|
17
|
+
];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WorkerRole } from '../shared/types.js';
|
|
2
|
+
import type { RouteDefinition } from './route-types.js';
|
|
3
|
+
import type { RuntimeStore } from './runtime-store-contract.js';
|
|
4
|
+
import { type ScenarioPreset } from './scenario-presets.js';
|
|
5
|
+
import { type CliAvailabilityProbe } from './spawn-cli-resolver.js';
|
|
6
|
+
export declare const scenarioRoutes: RouteDefinition[];
|
|
7
|
+
/**
|
|
8
|
+
* Materialize the scenario team and hand the goal to the orchestrator.
|
|
9
|
+
* Exported for direct testing (the route harness has no PTY, so the
|
|
10
|
+
* active-run gate above would mask this logic).
|
|
11
|
+
*
|
|
12
|
+
* Workers derive a FRESH launch config from the orchestrator's CLI brand via
|
|
13
|
+
* the same resolver `team spawn` uses — never a clone of the orchestrator's
|
|
14
|
+
* own config, which may carry session-resume arguments that would make every
|
|
15
|
+
* worker resume the orchestrator's session.
|
|
16
|
+
*
|
|
17
|
+
* Worker creation is sequential and NOT transactional across workers: each
|
|
18
|
+
* addWorkerWithLaunch is atomic, but a mid-loop failure (e.g. a name race)
|
|
19
|
+
* leaves earlier workers in place and surfaces the error to the caller.
|
|
20
|
+
*/
|
|
21
|
+
export declare const applyScenario: (store: RuntimeStore, workspaceId: string, scenario: ScenarioPreset, goal: string, isCommandAvailable?: CliAvailabilityProbe) => Array<{
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
role: WorkerRole;
|
|
25
|
+
}>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { BadRequestError, ConflictError } from './http-errors.js';
|
|
2
|
+
import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
|
|
3
|
+
import { buildScenarioKickoffMessage, buildScenarioWorkerName, getScenarioPreset, } from './scenario-presets.js';
|
|
4
|
+
import { resolveDefaultSpawnCliLaunchConfig, } from './spawn-cli-resolver.js';
|
|
5
|
+
import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
|
|
6
|
+
import { getOrchestratorId } from './workspace-store-support.js';
|
|
7
|
+
export const scenarioRoutes = [
|
|
8
|
+
/**
|
|
9
|
+
* One-click team assembly: materialize a scenario preset's workers (each
|
|
10
|
+
* inheriting the orchestrator's launch config) and hand the user's goal to
|
|
11
|
+
* the orchestrator as a normal user message. Deliberately NOT dispatching —
|
|
12
|
+
* splitting the goal and calling `team send` stays the orchestrator's job.
|
|
13
|
+
*/
|
|
14
|
+
route('POST', '/api/workspaces/:workspaceId/scenarios/:scenarioId/apply', async ({ params, request, response, store }) => {
|
|
15
|
+
const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Workspace id and scenario id are required');
|
|
16
|
+
const scenarioId = getRequiredParam(response, params, 'scenarioId', 'Workspace id and scenario id are required');
|
|
17
|
+
if (!workspaceId || !scenarioId) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
requireUiTokenFromRequest(request, store.validateUiToken, store.authorizeRemoteTunnelRequest);
|
|
21
|
+
if (!store.listWorkspaces().some((workspace) => workspace.id === workspaceId)) {
|
|
22
|
+
sendJson(response, 404, { error: 'Workspace not found' });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const scenario = getScenarioPreset(scenarioId);
|
|
26
|
+
if (!scenario) {
|
|
27
|
+
sendJson(response, 404, { error: `Unknown scenario: ${scenarioId}` });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const body = await readJsonBody(request);
|
|
31
|
+
const goal = typeof body.goal === 'string' ? body.goal.trim() : '';
|
|
32
|
+
if (!goal) {
|
|
33
|
+
throw new BadRequestError('Missing goal');
|
|
34
|
+
}
|
|
35
|
+
// The kickoff is delivered to the orchestrator's live terminal and is NOT
|
|
36
|
+
// persisted for replay, so an offline orchestrator would silently lose the
|
|
37
|
+
// goal. Gate up front: no live run, no team assembly.
|
|
38
|
+
if (!store.getActiveRunByAgentId(workspaceId, getOrchestratorId(workspaceId))) {
|
|
39
|
+
throw new ConflictError('Start the Orchestrator first — the scenario goal is handed to its terminal');
|
|
40
|
+
}
|
|
41
|
+
const created = applyScenario(store, workspaceId, scenario, goal);
|
|
42
|
+
sendJson(response, 201, {
|
|
43
|
+
created_workers: created,
|
|
44
|
+
injected: true,
|
|
45
|
+
});
|
|
46
|
+
}),
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* Materialize the scenario team and hand the goal to the orchestrator.
|
|
50
|
+
* Exported for direct testing (the route harness has no PTY, so the
|
|
51
|
+
* active-run gate above would mask this logic).
|
|
52
|
+
*
|
|
53
|
+
* Workers derive a FRESH launch config from the orchestrator's CLI brand via
|
|
54
|
+
* the same resolver `team spawn` uses — never a clone of the orchestrator's
|
|
55
|
+
* own config, which may carry session-resume arguments that would make every
|
|
56
|
+
* worker resume the orchestrator's session.
|
|
57
|
+
*
|
|
58
|
+
* Worker creation is sequential and NOT transactional across workers: each
|
|
59
|
+
* addWorkerWithLaunch is atomic, but a mid-loop failure (e.g. a name race)
|
|
60
|
+
* leaves earlier workers in place and surfaces the error to the caller.
|
|
61
|
+
*/
|
|
62
|
+
export const applyScenario = (store, workspaceId, scenario, goal, isCommandAvailable) => {
|
|
63
|
+
const ports = {
|
|
64
|
+
getCommandPreset: (id) => store.settings.getCommandPreset(id),
|
|
65
|
+
getOrchestratorLaunchConfig: () => store.peekAgentLaunchConfig(workspaceId, getOrchestratorId(workspaceId)),
|
|
66
|
+
...(isCommandAvailable ? { isCommandAvailable } : {}),
|
|
67
|
+
};
|
|
68
|
+
const launchConfig = resolveDefaultSpawnCliLaunchConfig(ports);
|
|
69
|
+
const takenNames = new Set(store.listWorkers(workspaceId).map((worker) => worker.name));
|
|
70
|
+
const created = [];
|
|
71
|
+
for (const spec of scenario.workers) {
|
|
72
|
+
const name = buildScenarioWorkerName(spec, (candidate) => takenNames.has(candidate));
|
|
73
|
+
takenNames.add(name);
|
|
74
|
+
const worker = store.addWorkerWithLaunch(workspaceId, {
|
|
75
|
+
name,
|
|
76
|
+
role: spec.role,
|
|
77
|
+
...(spec.descriptionOverride !== undefined
|
|
78
|
+
? { description: spec.descriptionOverride }
|
|
79
|
+
: {}),
|
|
80
|
+
}, launchConfig);
|
|
81
|
+
created.push({ id: worker.id, name: worker.name, role: spec.role });
|
|
82
|
+
}
|
|
83
|
+
store.recordUserInput(workspaceId, getOrchestratorId(workspaceId), buildScenarioKickoffMessage({
|
|
84
|
+
scenarioId: scenario.id,
|
|
85
|
+
goal,
|
|
86
|
+
workers: created.map((worker) => ({ name: worker.name, role: worker.role })),
|
|
87
|
+
}));
|
|
88
|
+
return created;
|
|
89
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isCommandAvailableOnPath } from './agent-command-resolver.js';
|
|
2
2
|
import { readFeatureFlags } from './feature-flags.js';
|
|
3
3
|
import { BadRequestError } from './http-errors.js';
|
|
4
4
|
import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
|
|
@@ -8,16 +8,7 @@ import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
|
|
|
8
8
|
import { assertValidWorkflowCliPolicy, CANONICAL_WORKFLOW_CLIS, readWorkflowCliPolicy, WORKFLOW_CLI_POLICY_KEY, } from './workflow-cli-policy.js';
|
|
9
9
|
import { readWorkflowEnabled, serializeWorkflowEnabled, WORKFLOW_ENABLED_KEY, } from './workflow-feature.js';
|
|
10
10
|
const serializeCommandPreset = (preset) => {
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
if (preset.command.trim()) {
|
|
14
|
-
resolveCommandPath(preset.command, process.cwd(), { ...process.env, ...preset.env });
|
|
15
|
-
available = true;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
available = false;
|
|
20
|
-
}
|
|
11
|
+
const available = isCommandAvailableOnPath(preset.command, preset.env);
|
|
21
12
|
return {
|
|
22
13
|
id: preset.id,
|
|
23
14
|
display_name: preset.displayName,
|
|
@@ -2,6 +2,8 @@ import { isMemoryKind, MEMORY_BODY_MAX_CHARS, MEMORY_KINDS, MEMORY_QUERY_MAX_CHA
|
|
|
2
2
|
import { BadRequestError } from './http-errors.js';
|
|
3
3
|
import { readJsonBody, route, sendJson } from './route-helpers.js';
|
|
4
4
|
import { authenticateCliAgent, requireCommandForRole } from './team-authz.js';
|
|
5
|
+
import { serializeDreamRun } from './team-memory-dream-http-serializers.js';
|
|
6
|
+
import { DreamRunNotFoundError, DreamRunValidationError } from './team-memory-dream-store.js';
|
|
5
7
|
import { serializeMemoryEntry, serializeMemorySearchResult, } from './team-memory-http-serializers.js';
|
|
6
8
|
import { MemoryEntryStatusError } from './team-memory-store.js';
|
|
7
9
|
const requireNonEmptyString = (value, field) => {
|
|
@@ -125,6 +127,56 @@ export const teamMemoryRoutes = [
|
|
|
125
127
|
results: results.map(serializeMemorySearchResult),
|
|
126
128
|
});
|
|
127
129
|
}),
|
|
130
|
+
route('POST', '/api/team/memory/dream/show', async ({ request, response, store }) => {
|
|
131
|
+
const body = await readJsonBody(request);
|
|
132
|
+
const { agent, workspaceId } = authenticateMemoryRequest(body, store);
|
|
133
|
+
requireCommandForRole(agent, 'memory_dream_show');
|
|
134
|
+
const runId = requireNonEmptyString(body.run_id, 'run_id');
|
|
135
|
+
try {
|
|
136
|
+
const input = store.getMemoryDreamInput(workspaceId, runId);
|
|
137
|
+
sendJson(response, 200, {
|
|
138
|
+
ok: true,
|
|
139
|
+
prompt: input.prompt,
|
|
140
|
+
run: serializeDreamRun(input.run),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (error instanceof DreamRunNotFoundError) {
|
|
145
|
+
sendJson(response, 404, { error: `Dream run not found: ${error.runId}` });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (error instanceof DreamRunValidationError) {
|
|
149
|
+
sendJson(response, 409, { error: error.message });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}),
|
|
155
|
+
route('POST', '/api/team/memory/apply', async ({ request, response, store }) => {
|
|
156
|
+
const body = await readJsonBody(request);
|
|
157
|
+
const { agent, workspaceId } = authenticateMemoryRequest(body, store);
|
|
158
|
+
requireCommandForRole(agent, 'memory_apply');
|
|
159
|
+
const runId = requireNonEmptyString(body.run_id, 'run_id');
|
|
160
|
+
if (body.ops === undefined)
|
|
161
|
+
throw new BadRequestError('Missing ops');
|
|
162
|
+
try {
|
|
163
|
+
sendJson(response, 200, {
|
|
164
|
+
ok: true,
|
|
165
|
+
run: serializeDreamRun(store.applyMemoryDreamRun(workspaceId, runId, body.ops)),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error instanceof DreamRunNotFoundError) {
|
|
170
|
+
sendJson(response, 404, { error: `Dream run not found: ${error.runId}` });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (error instanceof DreamRunValidationError) {
|
|
174
|
+
sendJson(response, 409, { error: error.message });
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}),
|
|
128
180
|
route('POST', '/api/team/memory/forget', async ({ request, response, store }) => {
|
|
129
181
|
const body = await readJsonBody(request);
|
|
130
182
|
const { agent, workspaceId } = authenticateMemoryRequest(body, store);
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { resolveCommandPresetLaunchConfig } from './agent-launch-resolver.js';
|
|
3
1
|
import { validateCronNextRunAt } from './cron-util.js';
|
|
4
2
|
import { BadRequestError, ForbiddenError } from './http-errors.js';
|
|
5
3
|
import { readJsonBody, route, sendJson } from './route-helpers.js';
|
|
4
|
+
import { resolveDefaultSpawnCliLaunchConfig, resolveExplicitSpawnCliLaunchConfig, } from './spawn-cli-resolver.js';
|
|
5
|
+
import { resolveSpawnWorkerDefaults } from './spawn-worker-defaults.js';
|
|
6
6
|
import { authenticateCliAgent, requireCommandForRole } from './team-authz.js';
|
|
7
7
|
import { readWorkflowEnabled, WORKFLOW_ENABLED_KEY } from './workflow-feature.js';
|
|
8
|
-
|
|
9
|
-
const toWorkerRole = (value) => value && WORKER_ROLES.has(value) ? value : 'coder';
|
|
8
|
+
import { getOrchestratorId } from './workspace-store-support.js';
|
|
10
9
|
// Experimental gate: `team workflow run` / `team workflow schedule` only work
|
|
11
10
|
// when the user has enabled workflows in Settings. Off by default. The PTY
|
|
12
11
|
// runner itself isn't gated (internal API + the scheduler gate handle that) —
|
|
@@ -35,16 +34,20 @@ const getReportStatus = (value) => {
|
|
|
35
34
|
}
|
|
36
35
|
return value;
|
|
37
36
|
};
|
|
38
|
-
|
|
37
|
+
/* P0-B2 (growth research 2026-06-11): when `team spawn` omits `--cli`, the
|
|
38
|
+
default used to be a hardcoded 'claude' — instantly broken for non-Claude
|
|
39
|
+
users. Now it inherits the orchestrator's CLI, falls back to the first
|
|
40
|
+
built-in CLI visible on PATH, and only then to 'claude'. An explicit
|
|
41
|
+
`--cli` that is missing from PATH is rejected (400) with a suggestion. */
|
|
42
|
+
const resolveSpawnLaunchConfig = (store, workspaceId, cli) => {
|
|
43
|
+
const ports = {
|
|
44
|
+
getCommandPreset: (id) => store.settings.getCommandPreset(id),
|
|
45
|
+
getOrchestratorLaunchConfig: () => store.peekAgentLaunchConfig(workspaceId, getOrchestratorId(workspaceId)),
|
|
46
|
+
};
|
|
39
47
|
if (cli === undefined || cli === null || cli === '') {
|
|
40
|
-
return (
|
|
48
|
+
return resolveDefaultSpawnCliLaunchConfig(ports);
|
|
41
49
|
}
|
|
42
|
-
|
|
43
|
-
const launchConfig = resolveCommandPresetLaunchConfig(store.settings, cliId);
|
|
44
|
-
if (!launchConfig) {
|
|
45
|
-
throw new BadRequestError(`Unsupported cli '${cliId}'`);
|
|
46
|
-
}
|
|
47
|
-
return launchConfig;
|
|
50
|
+
return resolveExplicitSpawnCliLaunchConfig(ports, requireNonEmptyString(cli, 'cli'));
|
|
48
51
|
};
|
|
49
52
|
export const teamRoutes = [
|
|
50
53
|
route('POST', '/api/team/send', async ({ request, response, store }) => {
|
|
@@ -68,11 +71,16 @@ export const teamRoutes = [
|
|
|
68
71
|
});
|
|
69
72
|
/* The user-facing team send route queues work for stopped workers without
|
|
70
73
|
auto-starting them; internal workflow dispatches may still surface
|
|
71
|
-
restarted_worker when they intentionally wake a worker.
|
|
74
|
+
restarted_worker when they intentionally wake a worker. A parked
|
|
75
|
+
dispatch is flagged so the orchestrator can tell the user the worker
|
|
76
|
+
needs a start (replay delivers it automatically on that start). */
|
|
72
77
|
sendJson(response, 202, {
|
|
73
78
|
dispatch_id: dispatch.id,
|
|
74
79
|
ok: true,
|
|
75
80
|
restarted_worker: dispatch.restartedWorker,
|
|
81
|
+
...(dispatch.queuedForStoppedWorker === true
|
|
82
|
+
? { queued: true, worker_status: 'stopped' }
|
|
83
|
+
: {}),
|
|
76
84
|
});
|
|
77
85
|
}),
|
|
78
86
|
route('POST', '/api/team/spawn', async ({ request, response, store }) => {
|
|
@@ -87,19 +95,26 @@ export const teamRoutes = [
|
|
|
87
95
|
workspaceId: projectId,
|
|
88
96
|
});
|
|
89
97
|
requireCommandForRole(agent, 'spawn');
|
|
90
|
-
const role =
|
|
91
|
-
|
|
92
|
-
? body.name
|
|
93
|
-
:
|
|
94
|
-
|
|
98
|
+
const { role, name, description } = resolveSpawnWorkerDefaults({
|
|
99
|
+
requestedRole: typeof body.role === 'string' ? body.role : undefined,
|
|
100
|
+
requestedName: typeof body.name === 'string' ? body.name : undefined,
|
|
101
|
+
takenNames: new Set(store.listWorkers(projectId).map((worker) => worker.name)),
|
|
102
|
+
});
|
|
103
|
+
const launchConfig = resolveSpawnLaunchConfig(store, projectId, body.cli);
|
|
95
104
|
// M11: default is persistent (acts like a normal member); --ephemeral
|
|
96
105
|
// opts into auto-dismiss-after-first-report (mirrors workflow workers,
|
|
97
106
|
// but orchestrator-spawned). Existing M1-D cascade-on-orchestrator-exit
|
|
98
107
|
// still applies to whatever ephemeral workers remain.
|
|
99
108
|
const ephemeral = body.ephemeral === true;
|
|
100
109
|
const workerInput = ephemeral
|
|
101
|
-
? {
|
|
102
|
-
|
|
110
|
+
? {
|
|
111
|
+
name,
|
|
112
|
+
role,
|
|
113
|
+
...(description !== undefined ? { description } : {}),
|
|
114
|
+
ephemeral: true,
|
|
115
|
+
spawnedBy: 'orchestrator',
|
|
116
|
+
}
|
|
117
|
+
: { name, role, ...(description !== undefined ? { description } : {}) };
|
|
103
118
|
const worker = store.addWorkerWithLaunch(projectId, workerInput, launchConfig);
|
|
104
119
|
sendJson(response, 201, {
|
|
105
120
|
name: worker.name,
|
|
@@ -191,6 +206,11 @@ export const teamRoutes = [
|
|
|
191
206
|
workspaceId: projectId,
|
|
192
207
|
});
|
|
193
208
|
requireCommandForRole(agent, 'workflow');
|
|
209
|
+
const run = store.getWorkflowRun(runId);
|
|
210
|
+
if (!run || run.workspaceId !== projectId) {
|
|
211
|
+
sendJson(response, 404, { error: `Workflow run not found: ${runId}` });
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
194
214
|
const stopped = store.stopWorkflowRun(runId);
|
|
195
215
|
sendJson(response, stopped ? 202 : 409, { ok: stopped, run_id: runId });
|
|
196
216
|
}),
|
|
@@ -56,6 +56,14 @@ export const workspaceMemoryDreamRoutes = [
|
|
|
56
56
|
await readJsonBody(request);
|
|
57
57
|
try {
|
|
58
58
|
const run = await store.runMemoryDream(workspaceId);
|
|
59
|
+
if (run.status === 'failed') {
|
|
60
|
+
sendJson(response, 409, {
|
|
61
|
+
error: run.error ?? 'Dream run failed before it could be applied',
|
|
62
|
+
ok: false,
|
|
63
|
+
run: serializeDreamRun(run),
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
59
67
|
sendJson(response, 200, { ok: true, run: serializeDreamRun(run) });
|
|
60
68
|
}
|
|
61
69
|
catch (error) {
|