@tt-a1i/hive 2.1.1 → 2.1.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 (93) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.en.md +5 -460
  3. package/README.md +338 -187
  4. package/README.zh.md +389 -0
  5. package/dist/src/cli/hive-update.d.ts +3 -2
  6. package/dist/src/cli/hive-update.js +20 -60
  7. package/dist/src/cli/hive.js +5 -1
  8. package/dist/src/server/agent-manager-support.js +16 -9
  9. package/dist/src/server/agent-runtime-contract.d.ts +4 -0
  10. package/dist/src/server/agent-runtime.js +27 -0
  11. package/dist/src/server/agent-stdin-dispatcher.d.ts +3 -0
  12. package/dist/src/server/agent-stdin-dispatcher.js +3 -0
  13. package/dist/src/server/diagnostics-support-bundle.d.ts +3 -0
  14. package/dist/src/server/diagnostics-support-bundle.js +3 -0
  15. package/dist/src/server/hive-team-guidance.js +34 -4
  16. package/dist/src/server/message-log-store.d.ts +1 -0
  17. package/dist/src/server/message-log-store.js +13 -0
  18. package/dist/src/server/orchestrator-autostart.d.ts +2 -0
  19. package/dist/src/server/orchestrator-autostart.js +30 -11
  20. package/dist/src/server/post-start-input-writer.d.ts +1 -0
  21. package/dist/src/server/post-start-input-writer.js +78 -12
  22. package/dist/src/server/role-templates.d.ts +8 -1
  23. package/dist/src/server/role-templates.js +102 -48
  24. package/dist/src/server/route-types.d.ts +6 -0
  25. package/dist/src/server/routes-scenarios.d.ts +2 -1
  26. package/dist/src/server/routes-scenarios.js +11 -6
  27. package/dist/src/server/routes-team.js +21 -15
  28. package/dist/src/server/routes-workspaces.js +22 -2
  29. package/dist/src/server/runtime-store-contract.d.ts +2 -0
  30. package/dist/src/server/runtime-store-helpers.d.ts +11 -2
  31. package/dist/src/server/runtime-store-helpers.js +29 -5
  32. package/dist/src/server/runtime-store-workflows.js +20 -0
  33. package/dist/src/server/runtime-store.js +2 -0
  34. package/dist/src/server/scenario-presets.d.ts +3 -1
  35. package/dist/src/server/scenario-presets.js +12 -1
  36. package/dist/src/server/spawn-worker-defaults.d.ts +2 -0
  37. package/dist/src/server/spawn-worker-defaults.js +10 -5
  38. package/dist/src/server/team-operations.d.ts +3 -0
  39. package/dist/src/server/team-operations.js +97 -29
  40. package/dist/src/server/terminal-input-profile.d.ts +2 -0
  41. package/dist/src/server/update-install-plan.d.ts +20 -0
  42. package/dist/src/server/update-install-plan.js +151 -0
  43. package/dist/src/server/version-service.d.ts +5 -0
  44. package/dist/src/server/version-service.js +17 -13
  45. package/dist/src/server/workflow-agent-call-executor.d.ts +81 -0
  46. package/dist/src/server/workflow-agent-call-executor.js +259 -0
  47. package/dist/src/server/workflow-dag-layer-tracker.d.ts +14 -0
  48. package/dist/src/server/workflow-dag-layer-tracker.js +37 -0
  49. package/dist/src/server/workflow-dag.d.ts +8 -0
  50. package/dist/src/server/workflow-dag.js +65 -0
  51. package/dist/src/server/workflow-dispatch-awaiter.d.ts +7 -1
  52. package/dist/src/server/workflow-dispatch-awaiter.js +60 -9
  53. package/dist/src/server/workflow-runner.d.ts +5 -38
  54. package/dist/src/server/workflow-runner.js +113 -445
  55. package/dist/src/server/workflow-script-host-dispatcher.d.ts +14 -0
  56. package/dist/src/server/workflow-script-host-dispatcher.js +42 -0
  57. package/dist/src/server/workflow-script-loader.d.ts +2 -2
  58. package/dist/src/server/workflow-script-loader.js +2 -2
  59. package/dist/src/server/workflow-script-worker.d.ts +14 -0
  60. package/dist/src/server/workflow-script-worker.js +81 -0
  61. package/dist/src/server/workflow-vm-worker-source.d.ts +1 -0
  62. package/dist/src/server/workflow-vm-worker-source.js +1 -0
  63. package/dist/src/server/workflow-vm-worker.cjs +238 -0
  64. package/dist/src/server/workspace-ui-language.d.ts +7 -0
  65. package/dist/src/server/workspace-ui-language.js +9 -0
  66. package/dist/src/shared/scenario-presets.d.ts +6 -1
  67. package/dist/src/shared/scenario-presets.js +80 -28
  68. package/dist/src/shared/types.d.ts +11 -7
  69. package/dist/src/shared/ui-language.d.ts +4 -0
  70. package/dist/src/shared/ui-language.js +3 -0
  71. package/package.json +6 -3
  72. package/web/dist/assets/{AddWorkerDialog-CTOsPurT.js → AddWorkerDialog-CkCGKa0N.js} +2 -2
  73. package/web/dist/assets/AddWorkspaceFlow-Cd73iChz.js +1 -0
  74. package/web/dist/assets/{FirstRunWizard-C24MUssx.js → FirstRunWizard-Db4HWbBQ.js} +1 -1
  75. package/web/dist/assets/{MarketplaceDrawer-7gVUjgAx.js → MarketplaceDrawer-DHwl1if1.js} +1 -1
  76. package/web/dist/assets/TaskGraphDrawer-B_OibVQO.js +1 -0
  77. package/web/dist/assets/{WhatsNewDialog-vnShQWjg.js → WhatsNewDialog-9QXlLVfy.js} +1 -1
  78. package/web/dist/assets/{WorkerModal-DcxIBPlu.js → WorkerModal-BGdtte3-.js} +1 -1
  79. package/web/dist/assets/{WorkflowsDrawer-C8csID1I.js → WorkflowsDrawer-DRuS0Ao5.js} +1 -1
  80. package/web/dist/assets/WorkspaceMemoryDrawer-C8PuHo0x.js +1 -0
  81. package/web/dist/assets/WorkspaceTaskDrawer-6mbgRoOq.js +1 -0
  82. package/web/dist/assets/index-BiGD5NhO.js +84 -0
  83. package/web/dist/assets/index-CG2TurwT.css +1 -0
  84. package/web/dist/assets/{search-BRQbevR2.js → search-BHvXxsdK.js} +1 -1
  85. package/web/dist/assets/{square-terminal-Co4pmGzn.js → square-terminal-CzkQzjCU.js} +1 -1
  86. package/web/dist/index.html +2 -2
  87. package/web/dist/sw.js +1 -1
  88. package/web/dist/assets/AddWorkspaceFlow-D_fSv-cO.js +0 -1
  89. package/web/dist/assets/TaskGraphDrawer-Beh95yUu.js +0 -1
  90. package/web/dist/assets/WorkspaceMemoryDrawer-C_Aq9-o6.js +0 -1
  91. package/web/dist/assets/WorkspaceTaskDrawer-CIMYzlpt.js +0 -1
  92. package/web/dist/assets/index-3Hc1oXlt.css +0 -1
  93. package/web/dist/assets/index-D9zUE0W6.js +0 -79
@@ -0,0 +1,42 @@
1
+ import { planWorkflowDag } from './workflow-dag.js';
2
+ export const dispatchWorkflowScriptHostCall = async (name, callArgs, handlers) => {
3
+ switch (name) {
4
+ case 'agent': {
5
+ const [prompt, opts] = callArgs;
6
+ return {
7
+ value: await handlers.agent(typeof prompt === 'string' ? prompt : String(prompt), (opts ?? {})),
8
+ };
9
+ }
10
+ case 'phase': {
11
+ const [title] = callArgs;
12
+ handlers.phase(typeof title === 'string' ? title : String(title ?? ''));
13
+ return {};
14
+ }
15
+ case 'log': {
16
+ const [message] = callArgs;
17
+ handlers.log(typeof message === 'string' ? message : String(message));
18
+ return {};
19
+ }
20
+ case 'workflow': {
21
+ const [scriptName, childArgs] = callArgs;
22
+ return {
23
+ value: await handlers.workflow(typeof scriptName === 'string' ? scriptName : String(scriptName), childArgs),
24
+ };
25
+ }
26
+ case 'catchPerItem': {
27
+ const [item] = callArgs;
28
+ return { value: handlers.catchPerItem(item) };
29
+ }
30
+ case 'cancelDagLayerAgents': {
31
+ const [layerId, reason] = callArgs;
32
+ handlers.cancelDagLayerAgents(layerId, reason);
33
+ return { skipPostActiveAssert: true };
34
+ }
35
+ case 'planDag': {
36
+ const [nodes] = callArgs;
37
+ return { value: planWorkflowDag(nodes) };
38
+ }
39
+ default:
40
+ throw new Error(`Unknown workflow host call: ${name}`);
41
+ }
42
+ };
@@ -20,8 +20,8 @@ export interface LoadedWorkflow {
20
20
  meta: WorkflowMeta;
21
21
  scriptPath: string;
22
22
  scriptHash: string;
23
- /** Transpiled `async function __wf(...) {…}` — the runner executes it in a
24
- * locked-down VM context and calls it with the Hive DSL bridge functions. */
23
+ /** Transpiled `async function __wf(dsl) {…}` — the runner executes it in a
24
+ * locked-down VM context and calls it with the Hive DSL bridge object. */
25
25
  compiledFunctionSource: string;
26
26
  }
27
27
  interface ExtractedMeta {
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { readFile } from 'node:fs/promises';
3
- const DSL_PARAMS = 'agent, parallel, pipeline, phase, log, workflow, args';
3
+ const DSL_BINDINGS = 'agent, parallel, pipeline, phase, log, workflow, dag, args';
4
4
  const DANGEROUS_WORKFLOW_IDENTIFIERS = new Set([
5
5
  'AsyncFunction',
6
6
  'Bun',
@@ -254,7 +254,7 @@ export const loadWorkflowScriptSource = async (source, scriptPath) => {
254
254
  }
255
255
  const { meta, body } = extractMeta(source);
256
256
  assertWorkflowScriptIsSandboxable(body);
257
- const wrapped = `async function __wf(${DSL_PARAMS}) {\n${body}\n}`;
257
+ const wrapped = `async function __wf(dsl) {\nconst { ${DSL_BINDINGS} } = dsl\n${body}\n}`;
258
258
  // Lazy-load esbuild: its native binary breaks under jsdom/worker contexts
259
259
  // used by the web test suite, so importing it at module-load time would
260
260
  // crash any test file that transitively pulls in the runtime store. The
@@ -0,0 +1,14 @@
1
+ import { Worker } from 'node:worker_threads';
2
+ import { type WorkflowScriptHostHandlers } from './workflow-script-host-dispatcher.js';
3
+ export type WorkflowScriptWorker = Worker;
4
+ export type WorkflowScriptWorkerSource = string | URL;
5
+ export interface RunWorkflowScriptWorkerInput<AgentOptions> {
6
+ args: unknown;
7
+ compiledFunctionSource: string;
8
+ handlers: WorkflowScriptHostHandlers<AgentOptions>;
9
+ onWorkerCreated?: (worker: WorkflowScriptWorker) => void;
10
+ onWorkerSettled?: (worker: WorkflowScriptWorker) => void;
11
+ scriptPath: string;
12
+ workerSource: WorkflowScriptWorkerSource;
13
+ }
14
+ export declare const runWorkflowScriptWorker: <AgentOptions>({ args, compiledFunctionSource, handlers, onWorkerCreated, onWorkerSettled, scriptPath, workerSource, }: RunWorkflowScriptWorkerInput<AgentOptions>) => Promise<unknown>;
@@ -0,0 +1,81 @@
1
+ import { Worker } from 'node:worker_threads';
2
+ import { dispatchWorkflowScriptHostCall, } from './workflow-script-host-dispatcher.js';
3
+ const errorToMessage = (error) => error instanceof Error ? error.message : String(error);
4
+ export const runWorkflowScriptWorker = ({ args, compiledFunctionSource, handlers, onWorkerCreated, onWorkerSettled, scriptPath, workerSource, }) => new Promise((resolve, reject) => {
5
+ const worker = new Worker(workerSource, {
6
+ ...(typeof workerSource === 'string' ? { eval: true } : {}),
7
+ workerData: {
8
+ args,
9
+ compiledFunctionSource,
10
+ scriptPath,
11
+ },
12
+ });
13
+ onWorkerCreated?.(worker);
14
+ let settled = false;
15
+ const activeHostCalls = new Set();
16
+ const settle = (fn, input) => {
17
+ if (settled)
18
+ return;
19
+ settled = true;
20
+ onWorkerSettled?.(worker);
21
+ void (async () => {
22
+ if (input.waitForHostCalls) {
23
+ await Promise.allSettled(activeHostCalls);
24
+ }
25
+ fn();
26
+ void worker.terminate().catch(() => { });
27
+ })();
28
+ };
29
+ const respond = (id, response) => {
30
+ try {
31
+ worker.postMessage({ type: 'hostResponse', id, ...response });
32
+ }
33
+ catch {
34
+ /* worker already terminated */
35
+ }
36
+ };
37
+ worker.on('message', (message) => {
38
+ const record = message;
39
+ if (!record)
40
+ return;
41
+ if (record.type === 'done') {
42
+ settle(() => {
43
+ if (record.ok)
44
+ resolve(record.value);
45
+ else
46
+ reject(new Error(record.error || 'Hive workflow script failed'));
47
+ }, { waitForHostCalls: record.ok === true });
48
+ return;
49
+ }
50
+ if (record.type !== 'hostCall' || typeof record.id !== 'string')
51
+ return;
52
+ const callId = record.id;
53
+ if (settled) {
54
+ respond(callId, { ok: false, error: 'Stopped by user' });
55
+ return;
56
+ }
57
+ const hostCall = (async () => {
58
+ try {
59
+ handlers.assertRunActive();
60
+ const callArgs = Array.isArray(record.args) ? record.args : [];
61
+ const result = await dispatchWorkflowScriptHostCall(record.name, callArgs, handlers);
62
+ if (!result.skipPostActiveAssert)
63
+ handlers.assertRunActive();
64
+ respond(callId, { ok: true, value: result.value });
65
+ }
66
+ catch (error) {
67
+ respond(callId, { ok: false, error: errorToMessage(error) });
68
+ }
69
+ })();
70
+ activeHostCalls.add(hostCall);
71
+ void hostCall.finally(() => activeHostCalls.delete(hostCall));
72
+ });
73
+ worker.on('error', (error) => settle(() => reject(error), { waitForHostCalls: false }));
74
+ worker.on('exit', (code) => {
75
+ if (settled)
76
+ return;
77
+ settled = true;
78
+ onWorkerSettled?.(worker);
79
+ reject(new Error(`Hive workflow VM worker exited before completion (code ${code})`));
80
+ });
81
+ });
@@ -0,0 +1 @@
1
+ export declare const WORKFLOW_VM_WORKER_URL: URL;
@@ -0,0 +1 @@
1
+ export const WORKFLOW_VM_WORKER_URL = new URL('./workflow-vm-worker.cjs', import.meta.url);
@@ -0,0 +1,238 @@
1
+ const { AsyncLocalStorage } = require('node:async_hooks')
2
+ const { parentPort, workerData } = require('node:worker_threads')
3
+ const { Script, createContext } = require('node:vm')
4
+
5
+ const BRIDGE_FACTORY = new Script(`"use strict";
6
+ ((hostCall) => {
7
+ const stringify = JSON.stringify;
8
+ const parse = JSON.parse;
9
+ const promiseResolve = Promise.resolve.bind(Promise);
10
+ const promiseThen = Promise.prototype.then;
11
+ return (...args) =>
12
+ promiseThen.call(promiseResolve(hostCall(stringify(args))), (payloadJson) => {
13
+ const payload = parse(payloadJson);
14
+ if (!payload.ok) throw new Error(payload.error || 'Hive workflow host call failed');
15
+ return payload.hasValue ? payload.value : undefined;
16
+ });
17
+ })`)
18
+
19
+ const FLOW_FACTORY = new Script(`"use strict";
20
+ ((agentBase, catchPerItem, cancelDagLayerAgents, planDag, dagLayerContext) => {
21
+ const promiseAll = Promise.all.bind(Promise);
22
+ const promiseResolve = Promise.resolve.bind(Promise);
23
+ const promiseThen = Promise.prototype.then;
24
+ const promiseCatch = Promise.prototype.catch;
25
+ let dagLayerCounter = 0;
26
+ const agent = (prompt, opts) => {
27
+ const activeDagLayerId = dagLayerContext.getStore();
28
+ if (!activeDagLayerId) return agentBase(prompt, opts);
29
+ const nextOpts = opts && typeof opts === 'object' ? { ...opts } : {};
30
+ nextOpts.__hiveDagLayerId = activeDagLayerId;
31
+ return agentBase(prompt, nextOpts);
32
+ };
33
+ const normalizeDagNodes = (spec) => {
34
+ const rawNodes = Array.isArray(spec)
35
+ ? spec
36
+ : spec && Array.isArray(spec.nodes)
37
+ ? spec.nodes
38
+ : null;
39
+ if (!rawNodes) throw new Error('dag(): expected an array of nodes or { nodes }');
40
+ return rawNodes.map((node, index) => {
41
+ if (!node || typeof node !== 'object') {
42
+ throw new Error('dag(): node #' + (index + 1) + ' must be an object');
43
+ }
44
+ const id = typeof node.id === 'string' ? node.id.trim() : '';
45
+ if (!id) throw new Error('dag(): node #' + (index + 1) + ' requires a non-empty string id');
46
+ const rawDeps = node.needs ?? node.dependsOn ?? node.after ?? [];
47
+ const deps = Array.isArray(rawDeps) ? rawDeps.map((dep) => String(dep).trim()) : [String(rawDeps).trim()];
48
+ for (const dep of deps) {
49
+ if (!dep) throw new Error('dag(' + id + '): dependency ids must be non-empty strings');
50
+ }
51
+ if (typeof node.run !== 'function') {
52
+ throw new Error('dag(' + id + '): node.run must be a function');
53
+ }
54
+ return { deps, id, run: node.run };
55
+ });
56
+ };
57
+ const runDagNode = (node, results) => {
58
+ if (typeof node.run !== 'function') {
59
+ throw new Error('dag(' + node.id + '): node.run must be a function');
60
+ }
61
+ const deps = Object.create(null);
62
+ for (const dep of node.deps) deps[dep] = results[dep];
63
+ return node.run(deps, results);
64
+ };
65
+ const runDagNodeInLayer = (layerId, node, results) =>
66
+ dagLayerContext.run(layerId, () => runDagNode(node, results));
67
+ const parallel = (thunks) =>
68
+ promiseAll(Array.from(thunks).map((thunk) =>
69
+ promiseCatch.call(promiseThen.call(promiseResolve(), () => thunk()), (err) => catchPerItem(err))
70
+ ));
71
+ const pipeline = (items, ...stages) =>
72
+ promiseAll(Array.from(items).map((item, index) => {
73
+ let chain = promiseResolve(item);
74
+ for (const stage of stages) {
75
+ chain = promiseThen.call(chain, (prev) => stage(prev, item, index));
76
+ }
77
+ return promiseCatch.call(chain, (err) => catchPerItem(err));
78
+ }));
79
+ const dag = async (spec) => {
80
+ const nodes = normalizeDagNodes(spec);
81
+ const byId = new Map(nodes.map((node) => [node.id, node]));
82
+ const plan = await planDag(nodes.map((node) => ({ id: node.id, deps: node.deps })));
83
+ const order = [];
84
+ const results = Object.create(null);
85
+ for (const layer of plan.layers) {
86
+ const layerId = 'dag-layer-' + (++dagLayerCounter);
87
+ let values;
88
+ let cancelError;
89
+ try {
90
+ values = await promiseAll(layer.map((id) =>
91
+ promiseThen.call(promiseResolve(), () =>
92
+ runDagNodeInLayer(layerId, byId.get(id), results)
93
+ )
94
+ ));
95
+ } catch (error) {
96
+ const message = error && typeof error.message === 'string' ? error.message : String(error);
97
+ try {
98
+ await cancelDagLayerAgents(layerId, 'DAG node failed: ' + message);
99
+ } catch (cancelFailure) {
100
+ cancelError = cancelFailure;
101
+ }
102
+ if (cancelError) {
103
+ const cancelMessage =
104
+ cancelError && typeof cancelError.message === 'string'
105
+ ? cancelError.message
106
+ : String(cancelError);
107
+ throw new Error(message + '; failed to cancel DAG sibling agents: ' + cancelMessage);
108
+ }
109
+ throw error;
110
+ }
111
+ for (let index = 0; index < layer.length; index += 1) {
112
+ const node = byId.get(layer[index]);
113
+ const value = values[index];
114
+ results[node.id] = value === undefined ? null : value;
115
+ order.push(node.id);
116
+ }
117
+ }
118
+ return { order, results };
119
+ };
120
+ return { agent, dag, parallel, pipeline };
121
+ })`)
122
+
123
+ const JSON_PARSE = new Script('JSON.parse')
124
+ const DAG_LAYER_CONTEXT = new AsyncLocalStorage()
125
+
126
+ const encodeSuccess = (value) => {
127
+ const payload = { ok: true, hasValue: value !== undefined }
128
+ if (value !== undefined) payload.value = value
129
+ return JSON.stringify(payload)
130
+ }
131
+
132
+ const encodeFailure = (error) =>
133
+ JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) })
134
+
135
+ const createSafeHostCall = (fn) =>
136
+ new Proxy(
137
+ (serializedArgs) => {
138
+ let args
139
+ try {
140
+ const parsed = JSON.parse(serializedArgs)
141
+ args = Array.isArray(parsed) ? parsed : []
142
+ } catch (error) {
143
+ return encodeFailure(error)
144
+ }
145
+ try {
146
+ return Promise.resolve(fn(args)).then(encodeSuccess, encodeFailure)
147
+ } catch (error) {
148
+ return encodeFailure(error)
149
+ }
150
+ },
151
+ {
152
+ get: () => undefined,
153
+ getOwnPropertyDescriptor: () => undefined,
154
+ getPrototypeOf: () => null,
155
+ has: () => false,
156
+ ownKeys: () => [],
157
+ set: () => false,
158
+ }
159
+ )
160
+
161
+ let nextCallId = 0
162
+ const pending = new Map()
163
+
164
+ parentPort.on('message', (message) => {
165
+ if (!message || message.type !== 'hostResponse') return
166
+ const entry = pending.get(message.id)
167
+ if (!entry) return
168
+ pending.delete(message.id)
169
+ if (message.ok) entry.resolve(message.value)
170
+ else entry.reject(new Error(message.error || 'Hive workflow host call failed'))
171
+ })
172
+
173
+ const callHost = (name, args) =>
174
+ new Promise((resolve, reject) => {
175
+ const id = String(++nextCallId)
176
+ pending.set(id, { resolve, reject })
177
+ parentPort.postMessage({ type: 'hostCall', id, name, args })
178
+ })
179
+
180
+ const cloneIntoVm = (context, value) => {
181
+ if (value === undefined) return undefined
182
+ const serialized = JSON.stringify(value)
183
+ if (serialized === undefined) return undefined
184
+ return JSON_PARSE.runInContext(context)(serialized)
185
+ }
186
+
187
+ const cloneOutOfVm = (value) => {
188
+ if (value === undefined) return undefined
189
+ const serialized = JSON.stringify(value)
190
+ return serialized === undefined ? undefined : JSON.parse(serialized)
191
+ }
192
+
193
+ ;(async () => {
194
+ try {
195
+ const context = createContext(Object.create(null), {
196
+ codeGeneration: { strings: false, wasm: false },
197
+ })
198
+ const bridgeFactory = BRIDGE_FACTORY.runInContext(context, { timeout: 1000 })
199
+ const bridge = (name) => bridgeFactory(createSafeHostCall((args) => callHost(name, args)))
200
+ const vmAgentBase = bridge('agent')
201
+ const vmPhase = bridge('phase')
202
+ const vmLog = bridge('log')
203
+ const vmWorkflow = bridge('workflow')
204
+ const vmCatchPerItem = bridge('catchPerItem')
205
+ const vmCancelDagLayerAgents = bridge('cancelDagLayerAgents')
206
+ const vmPlanDag = bridge('planDag')
207
+ const {
208
+ agent: vmAgent,
209
+ dag,
210
+ parallel,
211
+ pipeline,
212
+ } = FLOW_FACTORY.runInContext(context, {
213
+ timeout: 1000,
214
+ })(vmAgentBase, vmCatchPerItem, vmCancelDagLayerAgents, vmPlanDag, DAG_LAYER_CONTEXT)
215
+ const fn = new Script(`"use strict";\n${workerData.compiledFunctionSource}\n; __wf`, {
216
+ filename: workerData.scriptPath,
217
+ }).runInContext(context, { timeout: 1000 })
218
+ const value = cloneOutOfVm(
219
+ await fn({
220
+ agent: vmAgent,
221
+ parallel,
222
+ pipeline,
223
+ phase: vmPhase,
224
+ log: vmLog,
225
+ workflow: vmWorkflow,
226
+ dag,
227
+ args: cloneIntoVm(context, workerData.args),
228
+ })
229
+ )
230
+ parentPort.postMessage({ type: 'done', ok: true, value })
231
+ } catch (error) {
232
+ parentPort.postMessage({
233
+ type: 'done',
234
+ ok: false,
235
+ error: error instanceof Error ? error.message : String(error),
236
+ })
237
+ }
238
+ })()
@@ -0,0 +1,7 @@
1
+ import { type UiLanguage } from '../shared/ui-language.js';
2
+ import type { SettingsStore } from './settings-store.js';
3
+ export type { UiLanguage };
4
+ export declare const workspaceUiLanguageKey: (workspaceId: string) => string;
5
+ export declare const readWorkspaceUiLanguage: (value: string | null | undefined) => UiLanguage | null;
6
+ export declare const resolveWorkspaceUiLanguage: (settings: Pick<SettingsStore, "getAppState">, workspaceId: string, requestedLanguage?: unknown) => UiLanguage;
7
+ export declare const writeWorkspaceUiLanguage: (settings: Pick<SettingsStore, "setAppState">, workspaceId: string, language: UiLanguage) => void;
@@ -0,0 +1,9 @@
1
+ import { DEFAULT_UI_LANGUAGE, isUiLanguage } from '../shared/ui-language.js';
2
+ export const workspaceUiLanguageKey = (workspaceId) => `workspace.${workspaceId}.ui_language`;
3
+ export const readWorkspaceUiLanguage = (value) => isUiLanguage(value) ? value : null;
4
+ export const resolveWorkspaceUiLanguage = (settings, workspaceId, requestedLanguage) => readWorkspaceUiLanguage(typeof requestedLanguage === 'string' ? requestedLanguage : null) ??
5
+ readWorkspaceUiLanguage(settings.getAppState(workspaceUiLanguageKey(workspaceId))?.value) ??
6
+ DEFAULT_UI_LANGUAGE;
7
+ export const writeWorkspaceUiLanguage = (settings, workspaceId, language) => {
8
+ settings.setAppState(workspaceUiLanguageKey(workspaceId), language);
9
+ };
@@ -1,4 +1,5 @@
1
1
  import type { WorkerRole } from './types.js';
2
+ import type { UiLanguage } from './ui-language.js';
2
3
  /**
3
4
  * Scenario team presets — the data behind "one-click team assembly".
4
5
  *
@@ -17,7 +18,10 @@ export interface ScenarioWorkerSpec {
17
18
  * (startup prompt + every dispatch). Built-in roles omit this and fall back
18
19
  * to their default description from `role-templates.ts`.
19
20
  */
20
- descriptionOverride?: string;
21
+ descriptionOverride?: {
22
+ en: string;
23
+ zh: string;
24
+ };
21
25
  }
22
26
  export interface ScenarioPreset {
23
27
  id: ScenarioId;
@@ -30,3 +34,4 @@ export interface ScenarioPreset {
30
34
  }
31
35
  export declare const SCENARIO_PRESETS: ScenarioPreset[];
32
36
  export declare const getScenarioPreset: (id: string) => ScenarioPreset | undefined;
37
+ export declare const getScenarioWorkerDescription: (spec: ScenarioWorkerSpec, language: UiLanguage) => string | undefined;
@@ -1,34 +1,66 @@
1
- const RESEARCHER_DESCRIPTION = [
1
+ const RESEARCHER_DESCRIPTION_EN = [
2
2
  'You are a Researcher. Collect facts, data, and sources for the assigned topic.',
3
- '工作方式 / How to work:',
4
- '- Break the topic into key questions first, then investigate each one. / 先拆出要回答的关键问题,再逐项调查。',
5
- '- Prefer primary sources and real project files; attach a source or file path to every conclusion. / 优先一手来源和项目内真实文件;每个结论都附上来源或文件路径。',
6
- '- Separate facts, inferences, and unknowns; never present guesses as facts. / 区分事实、推断和未知,不把猜测写成结论。',
7
- 'Delivery / 交付说明: include findings by topic, source list, and unresolved questions.',
3
+ 'How to work:',
4
+ '- Break the topic into key questions first, then investigate each one.',
5
+ '- Prefer primary sources and real project files; attach a source or file path to every conclusion.',
6
+ '- Separate facts, inferences, and unknowns; never present guesses as facts.',
7
+ 'Delivery: include findings by topic, source list, and unresolved questions.',
8
8
  ].join('\n');
9
- const FACTCHECKER_DESCRIPTION = [
9
+ const RESEARCHER_DESCRIPTION_ZH = [
10
+ '你是 Researcher,负责为指定主题收集事实、数据和来源。',
11
+ '工作方式:',
12
+ '- 先拆出要回答的关键问题,再逐项调查。',
13
+ '- 优先一手来源和项目内真实文件;每个结论都附上来源或文件路径。',
14
+ '- 区分事实、推断和未知,不把猜测写成结论。',
15
+ '交付说明:按主题列出发现、来源清单和未解决问题。',
16
+ ].join('\n');
17
+ const FACTCHECKER_DESCRIPTION_EN = [
10
18
  'You are a Fact-checker. Verify research claims and evidence strength without expanding the scope by default.',
11
- '工作方式 / How to work:',
12
- '- Check every claim against its source and mark supported, uncertain, or unsupported. / 逐条核对结论与其来源,标注支持、存疑或不支持。',
13
- '- Cross-check critical claims with a second source, command, or direct file read. / 对关键结论做交叉验证。',
14
- '- When a claim is wrong, provide the corrected wording and evidence. / 发现错误时给出纠正后的表述和依据。',
15
- 'Delivery / 交付说明: group by confidence and list refuted or uncertain claims first.',
19
+ 'How to work:',
20
+ '- Check every claim against its source and mark supported, uncertain, or unsupported.',
21
+ '- Cross-check critical claims with a second source, command, or direct file read.',
22
+ '- When a claim is wrong, provide the corrected wording and evidence.',
23
+ 'Delivery: group by confidence and list refuted or uncertain claims first.',
24
+ ].join('\n');
25
+ const FACTCHECKER_DESCRIPTION_ZH = [
26
+ '你是 Fact-checker,负责验证研究结论和证据强度,默认不扩范围。',
27
+ '工作方式:',
28
+ '- 逐条核对结论与其来源,标注支持、存疑或不支持。',
29
+ '- 对关键结论用第二来源、命令或直接读文件做交叉验证。',
30
+ '- 发现错误时给出纠正后的表述和依据。',
31
+ '交付说明:按可信度分组,先列被推翻或存疑的结论。',
16
32
  ].join('\n');
17
- const DRAFTER_DESCRIPTION = [
33
+ const DRAFTER_DESCRIPTION_EN = [
18
34
  'You are a Drafter. Turn goals and source material into a clear first-draft document.',
19
- '工作方式 / How to work:',
20
- '- Confirm audience, purpose, and scope before outlining and writing. / 先确认读者、目的和范围,再列提纲,后成文。',
21
- '- Use real project code and files as the source of truth; do not invent behavior or APIs. / 以项目内真实代码和文件为准,不编造行为或接口。',
22
- '- Mark missing material and points that need confirmation. / 标注待确认和缺素材的位置。',
23
- 'Delivery / 交付说明: include document path, structure overview, and confirmation checklist.',
35
+ 'How to work:',
36
+ '- Confirm audience, purpose, and scope before outlining and writing.',
37
+ '- Use real project code and files as the source of truth; do not invent behavior or APIs.',
38
+ '- Mark missing material and points that need confirmation.',
39
+ 'Delivery: include document path, structure overview, and confirmation checklist.',
24
40
  ].join('\n');
25
- const DOC_REVIEWER_DESCRIPTION = [
41
+ const DRAFTER_DESCRIPTION_ZH = [
42
+ '你是 Drafter,负责把目标和素材写成清晰的第一版文档。',
43
+ '工作方式:',
44
+ '- 先确认读者、目的和范围,再列提纲,后成文。',
45
+ '- 以项目内真实代码和文件为准,不编造行为或接口。',
46
+ '- 标注待确认和缺素材的位置。',
47
+ '交付说明:包含文档路径、结构概览和确认清单。',
48
+ ].join('\n');
49
+ const DOC_REVIEWER_DESCRIPTION_EN = [
26
50
  'You are a Document Reviewer. Check a draft for accuracy and readability; do not rewrite the whole document by default.',
27
- '工作方式 / How to work:',
28
- '- Verify technical details against real code and files first. / 先对照项目真实代码和文件核对技术细节。',
29
- '- Check structure, terminology consistency, and whether readers can follow the document. / 检查结构、术语一致性,以及读者能否按文档完成操作。',
30
- '- List issues by severity with concrete edits or rewrite examples. / 问题按严重度列出,给出具体修改建议或改写示例。',
31
- 'Delivery / 交付说明: list factual errors first, then structure and wording issues.',
51
+ 'How to work:',
52
+ '- Verify technical details against real code and files first.',
53
+ '- Check structure, terminology consistency, and whether readers can follow the document.',
54
+ '- List issues by severity with concrete edits or rewrite examples.',
55
+ 'Delivery: list factual errors first, then structure and wording issues.',
56
+ ].join('\n');
57
+ const DOC_REVIEWER_DESCRIPTION_ZH = [
58
+ '你是 Document Reviewer,负责检查草稿的准确性和可读性;默认不要整篇重写。',
59
+ '工作方式:',
60
+ '- 先对照项目真实代码和文件核对技术细节。',
61
+ '- 检查结构、术语一致性,以及读者能否按文档完成操作。',
62
+ '- 问题按严重度列出,给出具体修改建议或改写示例。',
63
+ '交付说明:先列事实错误,再列结构和表达问题。',
32
64
  ].join('\n');
33
65
  export const SCENARIO_PRESETS = [
34
66
  {
@@ -46,8 +78,16 @@ export const SCENARIO_PRESETS = [
46
78
  {
47
79
  id: 'research_factcheck',
48
80
  workers: [
49
- { nameStem: 'researcher', role: 'custom', descriptionOverride: RESEARCHER_DESCRIPTION },
50
- { nameStem: 'factchecker', role: 'custom', descriptionOverride: FACTCHECKER_DESCRIPTION },
81
+ {
82
+ nameStem: 'researcher',
83
+ role: 'custom',
84
+ descriptionOverride: { en: RESEARCHER_DESCRIPTION_EN, zh: RESEARCHER_DESCRIPTION_ZH },
85
+ },
86
+ {
87
+ nameStem: 'factchecker',
88
+ role: 'custom',
89
+ descriptionOverride: { en: FACTCHECKER_DESCRIPTION_EN, zh: FACTCHECKER_DESCRIPTION_ZH },
90
+ },
51
91
  ],
52
92
  goalTemplate: {
53
93
  en: 'Research <topic>: have the Researcher gather evidence and the Factchecker verify it; list questions, trusted sources, and the decision this research should support.',
@@ -57,8 +97,19 @@ export const SCENARIO_PRESETS = [
57
97
  {
58
98
  id: 'docs_pipeline',
59
99
  workers: [
60
- { nameStem: 'drafter', role: 'custom', descriptionOverride: DRAFTER_DESCRIPTION },
61
- { nameStem: 'doc-reviewer', role: 'custom', descriptionOverride: DOC_REVIEWER_DESCRIPTION },
100
+ {
101
+ nameStem: 'drafter',
102
+ role: 'custom',
103
+ descriptionOverride: { en: DRAFTER_DESCRIPTION_EN, zh: DRAFTER_DESCRIPTION_ZH },
104
+ },
105
+ {
106
+ nameStem: 'doc-reviewer',
107
+ role: 'custom',
108
+ descriptionOverride: {
109
+ en: DOC_REVIEWER_DESCRIPTION_EN,
110
+ zh: DOC_REVIEWER_DESCRIPTION_ZH,
111
+ },
112
+ },
62
113
  ],
63
114
  goalTemplate: {
64
115
  en: 'Write <doc>: have the Drafter write and the Doc Reviewer review; state the audience, scope, required sections, and code or files it should describe.',
@@ -67,3 +118,4 @@ export const SCENARIO_PRESETS = [
67
118
  },
68
119
  ];
69
120
  export const getScenarioPreset = (id) => SCENARIO_PRESETS.find((preset) => preset.id === id);
121
+ export const getScenarioWorkerDescription = (spec, language) => spec.descriptionOverride?.[language];
@@ -2,7 +2,8 @@ export declare const agentStatuses: readonly ["idle", "working", "stopped"];
2
2
  export type AgentStatus = (typeof agentStatuses)[number];
3
3
  export type WorkerRole = 'coder' | 'reviewer' | 'tester' | 'sentinel' | 'custom';
4
4
  /** How a worker came to exist: spawned by a workflow run or by the orchestrator
5
- * (via `team spawn`). Absent/null for user-added persistent workers. */
5
+ * (via `team spawn`). Absent/null for user-added workers. `ephemeral`
6
+ * controls lifecycle; `spawnedBy` controls ownership/routing behavior. */
6
7
  export type WorkerSpawnSource = 'workflow' | 'orchestrator';
7
8
  export interface WorkspaceSummary {
8
9
  id: string;
@@ -38,13 +39,16 @@ export interface TeamListItem {
38
39
  * — in that case the UI falls back to the role-letter avatar.
39
40
  */
40
41
  commandPresetId?: string;
41
- /** Lifecycle marker — true for workers spawned by `team spawn` or by the
42
- * workflow runner (auto-dismissed after their dispatch). Drives the team
43
- * panel's visual distinction so workflow-spawned agents read as the live
44
- * workflow fleet, not as members of the user's persistent team (M10). */
42
+ /** Lifecycle marker — true for one-shot workers spawned by `team spawn
43
+ * --ephemeral` or by the workflow runner (auto-dismissed after their
44
+ * dispatch). Drives the team panel's visual distinction so workflow-spawned
45
+ * agents read as the live workflow fleet, not as members of the user's
46
+ * persistent team (M10). */
45
47
  ephemeral?: boolean;
46
- /** When `ephemeral`, identifies the creator: 'orchestrator' (via `team spawn`)
47
- * or 'workflow' (via the workflow runner's `agent()` call). */
48
+ /** Identifies the creator: 'orchestrator' (via `team spawn`) or 'workflow'
49
+ * (via the workflow runner's `agent()` call). Persistent `team spawn`
50
+ * workers carry this too, so `team send` can wake them instead of parking
51
+ * the first dispatch. */
48
52
  spawnedBy?: 'orchestrator' | 'workflow';
49
53
  }
50
54
  /**
@@ -0,0 +1,4 @@
1
+ export type UiLanguage = 'en' | 'zh';
2
+ export declare const DEFAULT_UI_LANGUAGE: UiLanguage;
3
+ export declare const isUiLanguage: (value: unknown) => value is UiLanguage;
4
+ export declare const normalizeUiLanguage: (value: unknown, fallback?: UiLanguage) => UiLanguage;
@@ -0,0 +1,3 @@
1
+ export const DEFAULT_UI_LANGUAGE = 'en';
2
+ export const isUiLanguage = (value) => value === 'en' || value === 'zh';
3
+ export const normalizeUiLanguage = (value, fallback = DEFAULT_UI_LANGUAGE) => (isUiLanguage(value) ? value : fallback);