@tt-a1i/hive 1.4.4 → 1.5.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.
Files changed (164) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +8 -0
  3. package/assets/qq-group.jpg +0 -0
  4. package/dist/bin/team.cmd +1 -0
  5. package/dist/src/cli/hive-update.d.ts +45 -17
  6. package/dist/src/cli/hive-update.js +63 -25
  7. package/dist/src/cli/hive.d.ts +25 -0
  8. package/dist/src/cli/hive.js +41 -3
  9. package/dist/src/cli/team.d.ts +1 -0
  10. package/dist/src/cli/team.js +199 -3
  11. package/dist/src/server/agent-command-resolver.js +3 -19
  12. package/dist/src/server/agent-manager-support.d.ts +2 -2
  13. package/dist/src/server/agent-manager-support.js +98 -24
  14. package/dist/src/server/agent-run-starter.d.ts +7 -1
  15. package/dist/src/server/agent-run-starter.js +9 -2
  16. package/dist/src/server/agent-run-store.d.ts +1 -1
  17. package/dist/src/server/agent-runtime-close.d.ts +1 -0
  18. package/dist/src/server/agent-runtime-close.js +25 -1
  19. package/dist/src/server/agent-runtime-contract.d.ts +2 -1
  20. package/dist/src/server/agent-runtime.d.ts +1 -1
  21. package/dist/src/server/agent-runtime.js +8 -2
  22. package/dist/src/server/agent-startup-instructions.d.ts +8 -1
  23. package/dist/src/server/agent-startup-instructions.js +15 -9
  24. package/dist/src/server/agent-stdin-dispatcher.d.ts +12 -5
  25. package/dist/src/server/agent-stdin-dispatcher.js +129 -40
  26. package/dist/src/server/cron-util.d.ts +7 -0
  27. package/dist/src/server/cron-util.js +19 -0
  28. package/dist/src/server/dispatch-ledger-store.d.ts +22 -0
  29. package/dist/src/server/dispatch-ledger-store.js +51 -3
  30. package/dist/src/server/env-sync-message.js +9 -9
  31. package/dist/src/server/fs-pick-folder.js +4 -0
  32. package/dist/src/server/fs-sandbox.js +36 -7
  33. package/dist/src/server/hive-team-guidance.d.ts +11 -6
  34. package/dist/src/server/hive-team-guidance.js +252 -71
  35. package/dist/src/server/live-run-registry.d.ts +1 -0
  36. package/dist/src/server/live-run-registry.js +1 -1
  37. package/dist/src/server/open-target-commands.js +5 -6
  38. package/dist/src/server/orchestrator-autostart.d.ts +12 -0
  39. package/dist/src/server/orchestrator-autostart.js +15 -13
  40. package/dist/src/server/path-canonicalization.d.ts +3 -0
  41. package/dist/src/server/path-canonicalization.js +29 -0
  42. package/dist/src/server/platform-path.d.ts +3 -0
  43. package/dist/src/server/platform-path.js +13 -0
  44. package/dist/src/server/post-start-input-writer.d.ts +1 -1
  45. package/dist/src/server/post-start-input-writer.js +110 -13
  46. package/dist/src/server/preset-launch-support.d.ts +1 -1
  47. package/dist/src/server/preset-launch-support.js +33 -2
  48. package/dist/src/server/recovery-summary.d.ts +6 -1
  49. package/dist/src/server/recovery-summary.js +17 -17
  50. package/dist/src/server/restart-policy-support.d.ts +6 -1
  51. package/dist/src/server/restart-policy-support.js +9 -1
  52. package/dist/src/server/restart-policy.d.ts +2 -2
  53. package/dist/src/server/restart-policy.js +3 -1
  54. package/dist/src/server/role-template-store.d.ts +1 -0
  55. package/dist/src/server/role-template-store.js +11 -1
  56. package/dist/src/server/route-types.d.ts +43 -0
  57. package/dist/src/server/routes-runtime.js +2 -1
  58. package/dist/src/server/routes-settings.js +76 -0
  59. package/dist/src/server/routes-team.js +211 -1
  60. package/dist/src/server/routes-workflow-schedules.d.ts +2 -0
  61. package/dist/src/server/routes-workflow-schedules.js +58 -0
  62. package/dist/src/server/routes-workflows.d.ts +2 -0
  63. package/dist/src/server/routes-workflows.js +83 -0
  64. package/dist/src/server/routes.js +4 -0
  65. package/dist/src/server/runtime-restart-policy.d.ts +3 -1
  66. package/dist/src/server/runtime-restart-policy.js +3 -1
  67. package/dist/src/server/runtime-store-contract.d.ts +122 -0
  68. package/dist/src/server/runtime-store-contract.js +1 -0
  69. package/dist/src/server/runtime-store-helpers.d.ts +9 -0
  70. package/dist/src/server/runtime-store-helpers.js +101 -2
  71. package/dist/src/server/runtime-store-workflows.d.ts +6 -0
  72. package/dist/src/server/runtime-store-workflows.js +100 -0
  73. package/dist/src/server/runtime-store.d.ts +3 -72
  74. package/dist/src/server/runtime-store.js +70 -4
  75. package/dist/src/server/session-capture-codex.d.ts +3 -3
  76. package/dist/src/server/session-capture-codex.js +9 -7
  77. package/dist/src/server/session-capture-gemini.d.ts +1 -1
  78. package/dist/src/server/session-capture-gemini.js +6 -3
  79. package/dist/src/server/settings-store.d.ts +3 -0
  80. package/dist/src/server/settings-store.js +1 -0
  81. package/dist/src/server/sqlite-schema-v19.d.ts +2 -0
  82. package/dist/src/server/sqlite-schema-v19.js +17 -0
  83. package/dist/src/server/sqlite-schema-v20.d.ts +2 -0
  84. package/dist/src/server/sqlite-schema-v20.js +20 -0
  85. package/dist/src/server/sqlite-schema-v21.d.ts +2 -0
  86. package/dist/src/server/sqlite-schema-v21.js +20 -0
  87. package/dist/src/server/sqlite-schema.d.ts +1 -1
  88. package/dist/src/server/sqlite-schema.js +97 -1
  89. package/dist/src/server/system-message.d.ts +7 -0
  90. package/dist/src/server/system-message.js +8 -1
  91. package/dist/src/server/tasks-file-watcher.d.ts +13 -1
  92. package/dist/src/server/tasks-file-watcher.js +127 -23
  93. package/dist/src/server/tasks-file.d.ts +2 -1
  94. package/dist/src/server/tasks-file.js +32 -9
  95. package/dist/src/server/tasks-websocket-server.js +13 -14
  96. package/dist/src/server/team-authz.d.ts +1 -1
  97. package/dist/src/server/team-authz.js +9 -1
  98. package/dist/src/server/team-autostaff.d.ts +16 -0
  99. package/dist/src/server/team-autostaff.js +16 -0
  100. package/dist/src/server/team-list-serializer.d.ts +1 -1
  101. package/dist/src/server/team-list-serializer.js +3 -1
  102. package/dist/src/server/team-operations.d.ts +15 -1
  103. package/dist/src/server/team-operations.js +116 -11
  104. package/dist/src/server/terminal-protocol.js +9 -3
  105. package/dist/src/server/terminal-stream-hub.js +16 -10
  106. package/dist/src/server/terminal-ws-server.js +10 -8
  107. package/dist/src/server/websocket-upgrade-safety.d.ts +10 -0
  108. package/dist/src/server/websocket-upgrade-safety.js +35 -0
  109. package/dist/src/server/windows-command-line.d.ts +3 -0
  110. package/dist/src/server/windows-command-line.js +9 -0
  111. package/dist/src/server/windows-filename.d.ts +2 -0
  112. package/dist/src/server/windows-filename.js +33 -0
  113. package/dist/src/server/workflow-cli-policy.d.ts +60 -0
  114. package/dist/src/server/workflow-cli-policy.js +110 -0
  115. package/dist/src/server/workflow-dispatch-awaiter.d.ts +12 -0
  116. package/dist/src/server/workflow-dispatch-awaiter.js +80 -0
  117. package/dist/src/server/workflow-feature.d.ts +15 -0
  118. package/dist/src/server/workflow-feature.js +15 -0
  119. package/dist/src/server/workflow-http-serializers.d.ts +64 -0
  120. package/dist/src/server/workflow-http-serializers.js +58 -0
  121. package/dist/src/server/workflow-run-log-store.d.ts +19 -0
  122. package/dist/src/server/workflow-run-log-store.js +45 -0
  123. package/dist/src/server/workflow-run-store.d.ts +50 -0
  124. package/dist/src/server/workflow-run-store.js +103 -0
  125. package/dist/src/server/workflow-runner.d.ts +147 -0
  126. package/dist/src/server/workflow-runner.js +401 -0
  127. package/dist/src/server/workflow-schedule-create.d.ts +14 -0
  128. package/dist/src/server/workflow-schedule-create.js +41 -0
  129. package/dist/src/server/workflow-schedule-store.d.ts +43 -0
  130. package/dist/src/server/workflow-schedule-store.js +112 -0
  131. package/dist/src/server/workflow-scheduler.d.ts +36 -0
  132. package/dist/src/server/workflow-scheduler.js +97 -0
  133. package/dist/src/server/workflow-script-loader.d.ts +34 -0
  134. package/dist/src/server/workflow-script-loader.js +106 -0
  135. package/dist/src/server/workspace-path-validation.js +16 -4
  136. package/dist/src/server/workspace-shell-runtime.d.ts +5 -0
  137. package/dist/src/server/workspace-shell-runtime.js +24 -2
  138. package/dist/src/server/workspace-store-contract.d.ts +4 -1
  139. package/dist/src/server/workspace-store-hydration.js +23 -7
  140. package/dist/src/server/workspace-store-mutations.js +2 -5
  141. package/dist/src/server/workspace-store-support.d.ts +4 -0
  142. package/dist/src/server/workspace-store-support.js +13 -1
  143. package/dist/src/server/workspace-store.js +38 -4
  144. package/dist/src/shared/types.d.ts +16 -1
  145. package/package.json +4 -2
  146. package/web/dist/assets/{AddWorkerDialog-DeZhTQLi.js → AddWorkerDialog-CcC-7kgG.js} +2 -2
  147. package/web/dist/assets/AddWorkspaceDialog-BDpOTfmt.js +1 -0
  148. package/web/dist/assets/{FirstRunWizard-B5wLcat5.js → FirstRunWizard-BYX_ocQn.js} +1 -1
  149. package/web/dist/assets/{MarketplaceDrawer-BC0eBOEW.js → MarketplaceDrawer-DUxSk7db.js} +1 -1
  150. package/web/dist/assets/WhatsNewDialog-B_RlCXcV.js +1 -0
  151. package/web/dist/assets/WorkerModal-D9-7YfZZ.js +1 -0
  152. package/web/dist/assets/WorkspaceTaskDrawer-BCKoF7qc.js +1 -0
  153. package/web/dist/assets/{WorkspaceTerminalPanels-CvibsPSd.js → WorkspaceTerminalPanels-Dq8y91t2.js} +1 -1
  154. package/web/dist/assets/index-BiOvKIVw.css +1 -0
  155. package/web/dist/assets/index-DMRUklT3.js +73 -0
  156. package/web/dist/assets/path-join-7MR1s7b1.js +1 -0
  157. package/web/dist/index.html +2 -2
  158. package/web/dist/sw.js +1 -1
  159. package/web/dist/assets/AddWorkspaceDialog-DDpXNEKf.js +0 -1
  160. package/web/dist/assets/WorkerModal-BwMHq-Bi.js +0 -1
  161. package/web/dist/assets/WorkspaceTaskDrawer-CxvT4nqs.js +0 -1
  162. package/web/dist/assets/index-BEsTmfrO.css +0 -1
  163. package/web/dist/assets/index-Ddb7bDN5.js +0 -75
  164. package/web/dist/assets/path-join-S7qkXQtP.js +0 -1
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Workflow CLI policy — controls which CLI a workflow's `agent()` spawns.
3
+ *
4
+ * Before this, the runner hard-coded `opts.cli ?? 'claude'`: a user who only
5
+ * had Codex set up would have every workflow agent that omitted `cli` spawn a
6
+ * `claude` it can't run. The policy makes the default configurable and lets
7
+ * the user constrain which CLIs workflow agents may use.
8
+ *
9
+ * Stored GLOBALLY in `app_state` (CLI availability is a machine-level fact, not
10
+ * per-workspace) under WORKFLOW_CLI_POLICY_KEY as a JSON `{default, allowed}`.
11
+ * An absent/malformed value reads back as DEFAULT_WORKFLOW_CLI_POLICY, which is
12
+ * unrestricted and defaults to `claude` — i.e. exactly the old behavior, so
13
+ * upgrading without configuring anything changes nothing.
14
+ */
15
+ /** Canonical CLI set — mirrors the built-in command preset ids
16
+ * (see command-preset-defaults.ts). The allowlist is a subset of these. */
17
+ export const CANONICAL_WORKFLOW_CLIS = ['claude', 'codex', 'opencode', 'gemini'];
18
+ export const WORKFLOW_CLI_POLICY_KEY = 'workflow.cli-policy';
19
+ export const DEFAULT_WORKFLOW_CLI_POLICY = {
20
+ default: 'claude',
21
+ allowed: [...CANONICAL_WORKFLOW_CLIS],
22
+ };
23
+ const isCanonical = (value) => typeof value === 'string' && CANONICAL_WORKFLOW_CLIS.includes(value);
24
+ /** Canonical entries from `input`, deduped and in canonical order. */
25
+ const sanitizeAllowed = (input) => {
26
+ if (!Array.isArray(input))
27
+ return [];
28
+ const set = new Set(input.filter(isCanonical));
29
+ return CANONICAL_WORKFLOW_CLIS.filter((cli) => set.has(cli));
30
+ };
31
+ /**
32
+ * Lenient coercion used by the runtime reader: turn arbitrary stored data into
33
+ * a usable policy, never throwing. Junk in `allowed` is dropped; an empty
34
+ * result falls back to the full canonical default; a `default` outside the
35
+ * sanitized `allowed` is pulled back to the first allowed entry.
36
+ */
37
+ export const normalizeWorkflowCliPolicy = (input) => {
38
+ if (typeof input !== 'object' || input === null)
39
+ return DEFAULT_WORKFLOW_CLI_POLICY;
40
+ const record = input;
41
+ const allowed = sanitizeAllowed(record.allowed);
42
+ if (allowed.length === 0)
43
+ return DEFAULT_WORKFLOW_CLI_POLICY;
44
+ const requestedDefault = record.default;
45
+ const fallback = allowed[0];
46
+ const resolvedDefault = typeof requestedDefault === 'string' && allowed.includes(requestedDefault)
47
+ ? requestedDefault
48
+ : fallback;
49
+ return { default: resolvedDefault, allowed };
50
+ };
51
+ /** Parse the raw `app_state` string. Absent / malformed → canonical default. */
52
+ export const readWorkflowCliPolicy = (raw) => {
53
+ if (raw === null || raw === undefined)
54
+ return DEFAULT_WORKFLOW_CLI_POLICY;
55
+ try {
56
+ return normalizeWorkflowCliPolicy(JSON.parse(raw));
57
+ }
58
+ catch {
59
+ return DEFAULT_WORKFLOW_CLI_POLICY;
60
+ }
61
+ };
62
+ /**
63
+ * Strict validation for the settings API: reject bad input so a malformed
64
+ * policy can never be persisted (the reader tolerates junk, but we'd rather
65
+ * fail the write than silently store something the user didn't intend).
66
+ */
67
+ export const assertValidWorkflowCliPolicy = (input) => {
68
+ if (typeof input !== 'object' || input === null) {
69
+ throw new Error('workflow cli policy must be an object { default, allowed }');
70
+ }
71
+ const record = input;
72
+ if (!Array.isArray(record.allowed)) {
73
+ throw new Error('workflow cli policy `allowed` must be an array');
74
+ }
75
+ if (record.allowed.length === 0) {
76
+ throw new Error('workflow cli policy `allowed` must list at least one CLI');
77
+ }
78
+ const bad = record.allowed.find((entry) => !isCanonical(entry));
79
+ if (bad !== undefined) {
80
+ throw new Error(`workflow cli policy "allowed" has an unsupported CLI: ${JSON.stringify(bad)}. Supported: ${CANONICAL_WORKFLOW_CLIS.join(', ')}`);
81
+ }
82
+ if (typeof record.default !== 'string' || !record.allowed.includes(record.default)) {
83
+ throw new Error(`workflow cli policy "default" (${JSON.stringify(record.default)}) must be one of allowed: ${record.allowed.join(', ')}`);
84
+ }
85
+ // Dedupe + canonical order while preserving the (now-validated) selection.
86
+ return { default: record.default, allowed: sanitizeAllowed(record.allowed) };
87
+ };
88
+ /**
89
+ * Resolve the CLI command a workflow agent should launch with.
90
+ *
91
+ * - An explicit `requestedCli` is ALWAYS validated against `allowed` (throws
92
+ * if disallowed, so the orchestrator gets a clear, fixable error).
93
+ * - When omitted: a custom template keeps its own `defaultCommand` (the user
94
+ * curated that role deliberately — exempt from the allowlist); a built-in
95
+ * role falls back to `policy.default`.
96
+ */
97
+ export const resolveWorkflowCli = ({ requestedCli, isCustomTemplate, templateDefaultCommand, policy, }) => {
98
+ const explicit = requestedCli?.trim();
99
+ if (explicit) {
100
+ if (!policy.allowed.includes(explicit)) {
101
+ throw new Error(`Workflow agent cli '${explicit}' is not allowed in this workspace. ` +
102
+ `Allowed: ${policy.allowed.join(', ')}. ` +
103
+ `Pick an allowed cli, or change the workflow CLI policy in Settings.`);
104
+ }
105
+ return explicit;
106
+ }
107
+ if (isCustomTemplate && templateDefaultCommand)
108
+ return templateDefaultCommand;
109
+ return policy.default;
110
+ };
@@ -0,0 +1,12 @@
1
+ export interface ReportPayload {
2
+ text: string;
3
+ artifacts: string[];
4
+ status?: string;
5
+ }
6
+ export interface WorkflowDispatchAwaiter {
7
+ awaitReport(dispatchId: string, timeoutMs?: number): Promise<ReportPayload>;
8
+ notifyReport(dispatchId: string, payload: ReportPayload): void;
9
+ notifyCancel(dispatchId: string, reason: string): void;
10
+ cancelAll(reason: string): void;
11
+ }
12
+ export declare const createWorkflowDispatchAwaiter: () => WorkflowDispatchAwaiter;
@@ -0,0 +1,80 @@
1
+ // Default 10 minutes — workflows often run long. Callers may shorten via opts.
2
+ const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
3
+ export const createWorkflowDispatchAwaiter = () => {
4
+ const pending = new Map();
5
+ // TIER 2 #10 — deferred-cancel state. The runner creates a dispatch
6
+ // and THEN calls awaitReport, with at least one microtask gap between
7
+ // the two. If notifyCancel(id) fires in that gap, it currently hits
8
+ // an empty `pending` map and no-ops; the subsequent awaitReport(id)
9
+ // then waits the full DEFAULT_TIMEOUT_MS (10 min) on a dispatch that
10
+ // is already cancelled. Remembering the cancel intent here lets
11
+ // awaitReport reject synchronously the instant it gets registered.
12
+ // We also remember any pre-arrived report payload for the same
13
+ // reason — defensive against a future ordering change.
14
+ const cancelledIds = new Map();
15
+ const reportedIds = new Map();
16
+ const take = (dispatchId) => {
17
+ const entry = pending.get(dispatchId);
18
+ if (entry) {
19
+ clearTimeout(entry.timer);
20
+ pending.delete(dispatchId);
21
+ }
22
+ return entry;
23
+ };
24
+ return {
25
+ awaitReport(dispatchId, timeoutMs = DEFAULT_TIMEOUT_MS) {
26
+ return new Promise((resolve, reject) => {
27
+ // Pre-arrived events: drain immediately, no timer / no entry.
28
+ const earlyReport = reportedIds.get(dispatchId);
29
+ if (earlyReport) {
30
+ reportedIds.delete(dispatchId);
31
+ resolve(earlyReport);
32
+ return;
33
+ }
34
+ const earlyCancel = cancelledIds.get(dispatchId);
35
+ if (earlyCancel) {
36
+ cancelledIds.delete(dispatchId);
37
+ reject(new Error(`workflow dispatch ${dispatchId} cancelled: ${earlyCancel}`));
38
+ return;
39
+ }
40
+ const timer = setTimeout(() => {
41
+ pending.delete(dispatchId);
42
+ reject(new Error(`workflow dispatch ${dispatchId} timeout after ${timeoutMs}ms`));
43
+ }, timeoutMs);
44
+ pending.set(dispatchId, { resolve, reject, timer });
45
+ });
46
+ },
47
+ notifyReport(dispatchId, payload) {
48
+ const entry = take(dispatchId);
49
+ if (entry) {
50
+ entry.resolve(payload);
51
+ }
52
+ else {
53
+ // Pre-arrival: stash for the eventual awaitReport call. Cap
54
+ // stash size implicitly by overwriting on duplicate id.
55
+ reportedIds.set(dispatchId, payload);
56
+ }
57
+ },
58
+ notifyCancel(dispatchId, reason) {
59
+ const entry = take(dispatchId);
60
+ if (entry) {
61
+ entry.reject(new Error(`workflow dispatch ${dispatchId} cancelled: ${reason}`));
62
+ }
63
+ else {
64
+ cancelledIds.set(dispatchId, reason);
65
+ }
66
+ },
67
+ cancelAll(reason) {
68
+ for (const [id, entry] of pending) {
69
+ clearTimeout(entry.timer);
70
+ entry.reject(new Error(`workflow dispatch ${id} cancelled: ${reason}`));
71
+ }
72
+ pending.clear();
73
+ // cancelAll happens at runtime close — anything waiting at that
74
+ // moment should fail loudly, but the deferred state can be
75
+ // safely dropped since no future awaitReport will run.
76
+ cancelledIds.clear();
77
+ reportedIds.clear();
78
+ },
79
+ };
80
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Workflow experimental feature gate.
3
+ *
4
+ * Workflows (`team workflow run`, the scheduler, the UI drawer, and the chunk
5
+ * of orchestrator guidance that teaches them) are a power feature with sharp
6
+ * edges — runaway fan-outs, authoring footguns, no run-resume yet. They ship
7
+ * OFF by default; a user opts in from Settings. While off, the orchestrator is
8
+ * not even taught about workflows, which also keeps its always-on prompt lean.
9
+ *
10
+ * Stored GLOBALLY in `app_state` under WORKFLOW_ENABLED_KEY. Absent / anything
11
+ * other than the exact string "true" reads back as DISABLED.
12
+ */
13
+ export declare const WORKFLOW_ENABLED_KEY = "workflow.enabled";
14
+ export declare const readWorkflowEnabled: (raw: string | null | undefined) => boolean;
15
+ export declare const serializeWorkflowEnabled: (enabled: boolean) => string;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Workflow experimental feature gate.
3
+ *
4
+ * Workflows (`team workflow run`, the scheduler, the UI drawer, and the chunk
5
+ * of orchestrator guidance that teaches them) are a power feature with sharp
6
+ * edges — runaway fan-outs, authoring footguns, no run-resume yet. They ship
7
+ * OFF by default; a user opts in from Settings. While off, the orchestrator is
8
+ * not even taught about workflows, which also keeps its always-on prompt lean.
9
+ *
10
+ * Stored GLOBALLY in `app_state` under WORKFLOW_ENABLED_KEY. Absent / anything
11
+ * other than the exact string "true" reads back as DISABLED.
12
+ */
13
+ export const WORKFLOW_ENABLED_KEY = 'workflow.enabled';
14
+ export const readWorkflowEnabled = (raw) => raw === 'true';
15
+ export const serializeWorkflowEnabled = (enabled) => (enabled ? 'true' : 'false');
@@ -0,0 +1,64 @@
1
+ import type { LiveAgentRun } from './agent-runtime-types.js';
2
+ import type { DispatchRecord } from './dispatch-ledger-store.js';
3
+ import type { WorkflowRunRecord } from './workflow-run-store.js';
4
+ import type { WorkflowScheduleRecord } from './workflow-schedule-store.js';
5
+ export declare const serializeLiveAgentRun: (run: LiveAgentRun) => {
6
+ agent_id: string;
7
+ exit_code: number | null;
8
+ output: string;
9
+ pid: number | null;
10
+ run_id: string;
11
+ started_at: number;
12
+ status: import("./agent-manager.js").RunStatus;
13
+ };
14
+ export declare const serializeWorkflowRun: (run: WorkflowRunRecord) => {
15
+ agent_count: number;
16
+ args: unknown;
17
+ created_at: number;
18
+ error: string | null;
19
+ finished_at: number | null;
20
+ id: string;
21
+ name: string;
22
+ parent_run_id: string | null;
23
+ phase: string | null;
24
+ result: unknown;
25
+ script_hash: string | null;
26
+ script_path: string;
27
+ started_at: number;
28
+ status: import("./workflow-run-store.js").WorkflowRunStatus;
29
+ workspace_id: string;
30
+ };
31
+ export declare const serializeWorkflowDispatch: (dispatch: DispatchRecord & {
32
+ lastPtyLine?: string;
33
+ }) => {
34
+ artifacts: string[];
35
+ created_at: number;
36
+ delivered_at: number | null;
37
+ from_agent_id: string | null;
38
+ id: string;
39
+ label: string | null;
40
+ last_pty_line: string | null;
41
+ phase: string | null;
42
+ reported_at: number | null;
43
+ report_text: string | null;
44
+ sequence: number | null;
45
+ status: import("./dispatch-ledger-store.js").DispatchStatus;
46
+ step_index: number | null;
47
+ submitted_at: number | null;
48
+ text: string;
49
+ to_agent_id: string;
50
+ workflow_run_id: string | null;
51
+ workspace_id: string;
52
+ };
53
+ export declare const serializeWorkflowSchedule: (schedule: WorkflowScheduleRecord) => {
54
+ args: unknown;
55
+ created_at: number;
56
+ cron: string;
57
+ enabled: boolean;
58
+ id: string;
59
+ last_run_at: number | null;
60
+ next_run_at: number;
61
+ script_path: string;
62
+ updated_at: number;
63
+ workspace_id: string;
64
+ };
@@ -0,0 +1,58 @@
1
+ export const serializeLiveAgentRun = (run) => ({
2
+ agent_id: run.agentId,
3
+ exit_code: run.exitCode,
4
+ output: run.output,
5
+ pid: run.pid,
6
+ run_id: run.runId,
7
+ started_at: run.startedAt,
8
+ status: run.status,
9
+ });
10
+ export const serializeWorkflowRun = (run) => ({
11
+ agent_count: run.agentCount,
12
+ args: run.args,
13
+ created_at: run.createdAt,
14
+ error: run.error,
15
+ finished_at: run.finishedAt,
16
+ id: run.id,
17
+ name: run.name,
18
+ parent_run_id: run.parentRunId,
19
+ phase: run.phase,
20
+ result: run.result,
21
+ script_hash: run.scriptHash,
22
+ script_path: run.scriptPath,
23
+ started_at: run.startedAt,
24
+ status: run.status,
25
+ workspace_id: run.workspaceId,
26
+ });
27
+ export const serializeWorkflowDispatch = (dispatch) => ({
28
+ artifacts: dispatch.artifacts,
29
+ created_at: dispatch.createdAt,
30
+ delivered_at: dispatch.deliveredAt,
31
+ from_agent_id: dispatch.fromAgentId,
32
+ id: dispatch.id,
33
+ label: dispatch.label,
34
+ last_pty_line: dispatch.lastPtyLine ?? null,
35
+ phase: dispatch.phase,
36
+ reported_at: dispatch.reportedAt,
37
+ report_text: dispatch.reportText,
38
+ sequence: dispatch.sequence,
39
+ status: dispatch.status,
40
+ step_index: dispatch.stepIndex,
41
+ submitted_at: dispatch.submittedAt,
42
+ text: dispatch.text,
43
+ to_agent_id: dispatch.toAgentId,
44
+ workflow_run_id: dispatch.workflowRunId,
45
+ workspace_id: dispatch.workspaceId,
46
+ });
47
+ export const serializeWorkflowSchedule = (schedule) => ({
48
+ args: schedule.args,
49
+ created_at: schedule.createdAt,
50
+ cron: schedule.cron,
51
+ enabled: schedule.enabled,
52
+ id: schedule.id,
53
+ last_run_at: schedule.lastRunAt,
54
+ next_run_at: schedule.nextRunAt,
55
+ script_path: schedule.scriptPath,
56
+ updated_at: schedule.updatedAt,
57
+ workspace_id: schedule.workspaceId,
58
+ });
@@ -0,0 +1,19 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export interface WorkflowRunLogRecord {
3
+ id: number;
4
+ runId: string;
5
+ ts: number;
6
+ message: string;
7
+ }
8
+ export declare const createWorkflowRunLogStore: (db: Database) => {
9
+ append(runId: string, message: string, ts?: number): void;
10
+ listForRun(runId: string): WorkflowRunLogRecord[];
11
+ /** Last `n` log messages for the run, oldest-first. Used by the
12
+ * onRunFinished reminder to include a short narrator tail in the
13
+ * orchestrator's completion notification without overwhelming it. */
14
+ tailForRun(runId: string, n: number): string[];
15
+ deleteForRun(runId: string): void;
16
+ /** Cascade hook: deleted alongside workflow_runs when a workspace
17
+ * is removed (TIER 1 #4 cascade). */
18
+ deleteForWorkspace(workspaceId: string): void;
19
+ };
@@ -0,0 +1,45 @@
1
+ /* TIER 2 #3 — narrator lane storage. `log()` calls from a workflow script
2
+ * append here; the Drawer polls listForRun(runId) alongside the dispatch
3
+ * timeline; the orchestrator's completion reminder splices in the last
4
+ * few lines so a run that finished with a long sequence of `log()` calls
5
+ * can be summarised back to the user.
6
+ *
7
+ * Append-only. No retention policy yet — TIER 3 work if log volume grows
8
+ * to where it matters; CC's Workflow tool keeps logs for the run's
9
+ * lifetime so we mirror that. */
10
+ export const createWorkflowRunLogStore = (db) => {
11
+ const insert = db.prepare('INSERT INTO workflow_run_logs (run_id, ts, message) VALUES (?, ?, ?)');
12
+ const listStmt = db.prepare('SELECT id, run_id, ts, message FROM workflow_run_logs WHERE run_id = ? ORDER BY id');
13
+ const tailStmt = db.prepare('SELECT message FROM workflow_run_logs WHERE run_id = ? ORDER BY id DESC LIMIT ?');
14
+ const deleteForRunStmt = db.prepare('DELETE FROM workflow_run_logs WHERE run_id = ?');
15
+ const deleteForWorkspaceStmt = db.prepare(`DELETE FROM workflow_run_logs
16
+ WHERE run_id IN (SELECT id FROM workflow_runs WHERE workspace_id = ?)`);
17
+ return {
18
+ append(runId, message, ts = Date.now()) {
19
+ insert.run(runId, ts, message);
20
+ },
21
+ listForRun(runId) {
22
+ return listStmt.all(runId).map((row) => ({
23
+ id: row.id,
24
+ runId: row.run_id,
25
+ ts: row.ts,
26
+ message: row.message,
27
+ }));
28
+ },
29
+ /** Last `n` log messages for the run, oldest-first. Used by the
30
+ * onRunFinished reminder to include a short narrator tail in the
31
+ * orchestrator's completion notification without overwhelming it. */
32
+ tailForRun(runId, n) {
33
+ const rows = tailStmt.all(runId, n);
34
+ return rows.map((r) => r.message).reverse();
35
+ },
36
+ deleteForRun(runId) {
37
+ deleteForRunStmt.run(runId);
38
+ },
39
+ /** Cascade hook: deleted alongside workflow_runs when a workspace
40
+ * is removed (TIER 1 #4 cascade). */
41
+ deleteForWorkspace(workspaceId) {
42
+ deleteForWorkspaceStmt.run(workspaceId);
43
+ },
44
+ };
45
+ };
@@ -0,0 +1,50 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ export type WorkflowRunStatus = 'running' | 'completed' | 'failed' | 'interrupted' | 'stopped';
3
+ export interface WorkflowRunRecord {
4
+ id: string;
5
+ workspaceId: string;
6
+ scriptPath: string;
7
+ scriptHash: string | null;
8
+ name: string;
9
+ status: WorkflowRunStatus;
10
+ phase: string | null;
11
+ args: unknown;
12
+ result: unknown;
13
+ startedAt: number;
14
+ finishedAt: number | null;
15
+ error: string | null;
16
+ createdAt: number;
17
+ /** Count of `agent()` calls dispatched by this run. TIER 1 #14 — lets the
18
+ * Drawer show "12 agents" on the row without an extra round-trip per
19
+ * expand. Cheap subquery against the indexed workflow_run_id column. */
20
+ agentCount: number;
21
+ /** TIER 2 #5: parent workflow run id for nested workflow() calls.
22
+ * Null on top-level runs. The Drawer uses this to render a child run
23
+ * indented under its parent (and to collapse/expand together). */
24
+ parentRunId: string | null;
25
+ }
26
+ interface CreateRunInput {
27
+ workspaceId: string;
28
+ scriptPath: string;
29
+ name: string;
30
+ scriptHash?: string;
31
+ args?: unknown;
32
+ /** TIER 2 #5 — set when this run is being created from a nested
33
+ * workflow() call. Null/omitted for top-level runs. */
34
+ parentRunId?: string | null;
35
+ }
36
+ interface UpdateRunInput {
37
+ status?: WorkflowRunStatus;
38
+ phase?: string;
39
+ finishedAt?: number;
40
+ error?: string;
41
+ result?: unknown;
42
+ }
43
+ export declare const createWorkflowRunStore: (db: Database) => {
44
+ createRun: (input: CreateRunInput) => WorkflowRunRecord;
45
+ updateRun: (id: string, input: UpdateRunInput) => void;
46
+ getRun: (id: string) => WorkflowRunRecord | undefined;
47
+ listWorkspaceRuns: (workspaceId: string) => WorkflowRunRecord[];
48
+ markUnfinishedRunsInterrupted: () => void;
49
+ };
50
+ export {};
@@ -0,0 +1,103 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ const parseArgs = (value) => {
3
+ if (!value)
4
+ return null;
5
+ try {
6
+ return JSON.parse(value);
7
+ }
8
+ catch {
9
+ return null;
10
+ }
11
+ };
12
+ const toRecord = (row) => ({
13
+ id: row.id,
14
+ workspaceId: row.workspace_id,
15
+ scriptPath: row.script_path,
16
+ scriptHash: row.script_hash,
17
+ name: row.name,
18
+ status: row.status,
19
+ phase: row.phase,
20
+ args: parseArgs(row.args),
21
+ result: parseArgs(row.result),
22
+ startedAt: row.started_at,
23
+ finishedAt: row.finished_at,
24
+ error: row.error,
25
+ createdAt: row.created_at,
26
+ agentCount: Number(row.agent_count ?? 0),
27
+ parentRunId: row.parent_run_id ?? null,
28
+ });
29
+ // Selects every workflow_runs column plus the agent_count subquery; used by
30
+ // both getRun and listWorkspaceRuns so the WorkflowRunRecord shape is
31
+ // uniform regardless of which path produced it.
32
+ const SELECT_RUN_COLUMNS = 'workflow_runs.*, ' +
33
+ '(SELECT COUNT(*) FROM dispatches WHERE dispatches.workflow_run_id = workflow_runs.id) AS agent_count';
34
+ export const createWorkflowRunStore = (db) => {
35
+ const createRun = (input) => {
36
+ const now = Date.now();
37
+ const record = {
38
+ id: randomUUID(),
39
+ workspaceId: input.workspaceId,
40
+ scriptPath: input.scriptPath,
41
+ scriptHash: input.scriptHash ?? null,
42
+ name: input.name,
43
+ status: 'running',
44
+ phase: null,
45
+ args: input.args ?? null,
46
+ result: null,
47
+ startedAt: now,
48
+ finishedAt: null,
49
+ error: null,
50
+ createdAt: now,
51
+ agentCount: 0,
52
+ parentRunId: input.parentRunId ?? null,
53
+ };
54
+ db.prepare(`INSERT INTO workflow_runs (
55
+ id, workspace_id, script_path, script_hash, name, status, phase, args,
56
+ started_at, finished_at, error, created_at, parent_run_id
57
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(record.id, record.workspaceId, record.scriptPath, record.scriptHash, record.name, record.status, record.phase, record.args === null ? null : JSON.stringify(record.args), record.startedAt, record.finishedAt, record.error, record.createdAt, record.parentRunId);
58
+ return record;
59
+ };
60
+ const updateRun = (id, input) => {
61
+ const sets = [];
62
+ const values = [];
63
+ if (input.status !== undefined) {
64
+ sets.push('status = ?');
65
+ values.push(input.status);
66
+ }
67
+ if (input.phase !== undefined) {
68
+ sets.push('phase = ?');
69
+ values.push(input.phase);
70
+ }
71
+ if (input.finishedAt !== undefined) {
72
+ sets.push('finished_at = ?');
73
+ values.push(input.finishedAt);
74
+ }
75
+ if (input.error !== undefined) {
76
+ sets.push('error = ?');
77
+ values.push(input.error);
78
+ }
79
+ if (input.result !== undefined) {
80
+ sets.push('result = ?');
81
+ values.push(input.result === null ? null : JSON.stringify(input.result));
82
+ }
83
+ if (sets.length === 0)
84
+ return;
85
+ values.push(id);
86
+ db.prepare(`UPDATE workflow_runs SET ${sets.join(', ')} WHERE id = ?`).run(...values);
87
+ };
88
+ const getRun = (id) => {
89
+ const row = db
90
+ .prepare(`SELECT ${SELECT_RUN_COLUMNS} FROM workflow_runs WHERE id = ?`)
91
+ .get(id);
92
+ return row ? toRecord(row) : undefined;
93
+ };
94
+ const listWorkspaceRuns = (workspaceId) => db
95
+ .prepare(`SELECT ${SELECT_RUN_COLUMNS} FROM workflow_runs WHERE workspace_id = ? ORDER BY created_at DESC, id DESC`)
96
+ .all(workspaceId).map(toRecord);
97
+ // Boot sweep: a run still 'running' after a restart can never resume, so mark
98
+ // it interrupted (spec §13 — the UI offers Resume; we never auto-resume).
99
+ const markUnfinishedRunsInterrupted = () => {
100
+ db.prepare("UPDATE workflow_runs SET status = 'interrupted', finished_at = ? WHERE status = 'running'").run(Date.now());
101
+ };
102
+ return { createRun, updateRun, getRun, listWorkspaceRuns, markUnfinishedRunsInterrupted };
103
+ };