@tt-a1i/hive 2.0.2 → 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.
Files changed (147) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.en.md +15 -6
  3. package/README.md +26 -4
  4. package/dist/src/cli/hive.d.ts +4 -0
  5. package/dist/src/cli/hive.js +25 -3
  6. package/dist/src/cli/team.d.ts +8 -1
  7. package/dist/src/cli/team.js +111 -11
  8. package/dist/src/server/action-center-summary.d.ts +193 -0
  9. package/dist/src/server/action-center-summary.js +188 -0
  10. package/dist/src/server/agent-command-resolver.d.ts +6 -0
  11. package/dist/src/server/agent-command-resolver.js +16 -0
  12. package/dist/src/server/agent-manager.js +11 -1
  13. package/dist/src/server/agent-run-starter.js +47 -6
  14. package/dist/src/server/agent-runtime-types.d.ts +4 -0
  15. package/dist/src/server/agent-startup-instructions.d.ts +4 -0
  16. package/dist/src/server/agent-startup-instructions.js +35 -9
  17. package/dist/src/server/agent-stdin-dispatcher.js +17 -9
  18. package/dist/src/server/diagnostics-support-bundle.d.ts +288 -0
  19. package/dist/src/server/diagnostics-support-bundle.js +179 -0
  20. package/dist/src/server/dispatch-ledger-store.d.ts +4 -1
  21. package/dist/src/server/dispatch-ledger-store.js +46 -6
  22. package/dist/src/server/hive-envelope-escape.d.ts +2 -0
  23. package/dist/src/server/hive-envelope-escape.js +2 -0
  24. package/dist/src/server/hive-team-guidance.d.ts +1 -1
  25. package/dist/src/server/hive-team-guidance.js +67 -25
  26. package/dist/src/server/message-log-store.d.ts +1 -1
  27. package/dist/src/server/post-start-input-writer.js +8 -2
  28. package/dist/src/server/preset-launch-support.d.ts +2 -0
  29. package/dist/src/server/preset-launch-support.js +65 -2
  30. package/dist/src/server/protocol-event-stats.d.ts +39 -0
  31. package/dist/src/server/protocol-event-stats.js +84 -0
  32. package/dist/src/server/recovery-summary.js +19 -14
  33. package/dist/src/server/role-template-store.d.ts +1 -1
  34. package/dist/src/server/role-templates.d.ts +1 -0
  35. package/dist/src/server/role-templates.js +43 -29
  36. package/dist/src/server/routes-action-center.d.ts +2 -0
  37. package/dist/src/server/routes-action-center.js +37 -0
  38. package/dist/src/server/routes-diagnostics.d.ts +2 -0
  39. package/dist/src/server/routes-diagnostics.js +17 -0
  40. package/dist/src/server/routes-scenarios.d.ts +25 -0
  41. package/dist/src/server/routes-scenarios.js +89 -0
  42. package/dist/src/server/routes-settings.js +2 -11
  43. package/dist/src/server/routes-team-memory.js +52 -0
  44. package/dist/src/server/routes-team.js +40 -20
  45. package/dist/src/server/routes-workspace-memory-dreams.js +8 -0
  46. package/dist/src/server/routes-workspace-uploads.d.ts +2 -0
  47. package/dist/src/server/routes-workspace-uploads.js +154 -0
  48. package/dist/src/server/routes-workspaces.js +29 -3
  49. package/dist/src/server/routes.js +8 -0
  50. package/dist/src/server/runtime-message-builders.d.ts +0 -1
  51. package/dist/src/server/runtime-message-builders.js +0 -8
  52. package/dist/src/server/runtime-store-contract.d.ts +15 -0
  53. package/dist/src/server/runtime-store-dream.d.ts +14 -1
  54. package/dist/src/server/runtime-store-dream.js +49 -1
  55. package/dist/src/server/runtime-store-helpers.d.ts +7 -0
  56. package/dist/src/server/runtime-store-helpers.js +85 -22
  57. package/dist/src/server/runtime-store-worker-mutations.d.ts +11 -0
  58. package/dist/src/server/runtime-store-worker-mutations.js +46 -0
  59. package/dist/src/server/runtime-store-workflows.js +10 -6
  60. package/dist/src/server/runtime-store.js +34 -42
  61. package/dist/src/server/scenario-presets.d.ts +25 -0
  62. package/dist/src/server/scenario-presets.js +35 -0
  63. package/dist/src/server/sentinel-heartbeat.d.ts +30 -0
  64. package/dist/src/server/sentinel-heartbeat.js +145 -0
  65. package/dist/src/server/spawn-cli-resolver.d.ts +37 -0
  66. package/dist/src/server/spawn-cli-resolver.js +70 -0
  67. package/dist/src/server/spawn-worker-defaults.d.ts +13 -0
  68. package/dist/src/server/spawn-worker-defaults.js +45 -0
  69. package/dist/src/server/sqlite-schema-v32.d.ts +2 -0
  70. package/dist/src/server/sqlite-schema-v32.js +17 -0
  71. package/dist/src/server/sqlite-schema-v33.d.ts +3 -0
  72. package/dist/src/server/sqlite-schema-v33.js +18 -0
  73. package/dist/src/server/sqlite-schema-v34.d.ts +11 -0
  74. package/dist/src/server/sqlite-schema-v34.js +19 -0
  75. package/dist/src/server/sqlite-schema-v35.d.ts +3 -0
  76. package/dist/src/server/sqlite-schema-v35.js +23 -0
  77. package/dist/src/server/sqlite-schema.d.ts +1 -1
  78. package/dist/src/server/sqlite-schema.js +35 -1
  79. package/dist/src/server/system-message.d.ts +5 -2
  80. package/dist/src/server/system-message.js +5 -2
  81. package/dist/src/server/tasks-file-watcher.d.ts +8 -0
  82. package/dist/src/server/tasks-file-watcher.js +31 -2
  83. package/dist/src/server/team-authz.d.ts +9 -1
  84. package/dist/src/server/team-authz.js +24 -0
  85. package/dist/src/server/team-list-serializer.d.ts +2 -2
  86. package/dist/src/server/team-list-serializer.js +2 -1
  87. package/dist/src/server/team-memory-digest.js +4 -4
  88. package/dist/src/server/team-memory-dream-applier.js +24 -3
  89. package/dist/src/server/team-memory-dream-prompt.d.ts +13 -0
  90. package/dist/src/server/team-memory-dream-prompt.js +91 -0
  91. package/dist/src/server/team-memory-dream-run-store.d.ts +2 -0
  92. package/dist/src/server/team-memory-dream-run-store.js +14 -4
  93. package/dist/src/server/team-memory-dream-runner.d.ts +2 -21
  94. package/dist/src/server/team-memory-dream-runner.js +3 -148
  95. package/dist/src/server/team-memory-dream-store.d.ts +1 -1
  96. package/dist/src/server/team-memory-dream-store.js +1 -1
  97. package/dist/src/server/team-operations.d.ts +18 -2
  98. package/dist/src/server/team-operations.js +222 -33
  99. package/dist/src/server/team-recap.d.ts +10 -0
  100. package/dist/src/server/team-recap.js +73 -0
  101. package/dist/src/server/terminal-input-profile.js +88 -9
  102. package/dist/src/server/upload-limits.d.ts +2 -0
  103. package/dist/src/server/upload-limits.js +2 -0
  104. package/dist/src/server/workflow-cli-policy.d.ts +7 -2
  105. package/dist/src/server/workflow-cli-policy.js +15 -3
  106. package/dist/src/server/workflow-run-store.d.ts +1 -0
  107. package/dist/src/server/workflow-run-store.js +11 -1
  108. package/dist/src/server/workflow-runner.d.ts +4 -1
  109. package/dist/src/server/workflow-runner.js +418 -118
  110. package/dist/src/server/workflow-script-loader.d.ts +3 -2
  111. package/dist/src/server/workflow-script-loader.js +161 -0
  112. package/dist/src/server/workspace-store-contract.d.ts +2 -0
  113. package/dist/src/server/workspace-store.d.ts +1 -1
  114. package/dist/src/server/workspace-store.js +40 -30
  115. package/dist/src/server/workspace-upload-store.d.ts +40 -0
  116. package/dist/src/server/workspace-upload-store.js +295 -0
  117. package/dist/src/shared/scenario-presets.d.ts +32 -0
  118. package/dist/src/shared/scenario-presets.js +69 -0
  119. package/dist/src/shared/types.d.ts +12 -1
  120. package/package.json +1 -1
  121. package/web/dist/assets/AddWorkerDialog-DBLhwb91.js +2 -0
  122. package/web/dist/assets/AddWorkspaceFlow-cxvhVAsT.js +1 -0
  123. package/web/dist/assets/FirstRunWizard-DlEPnWWw.js +1 -0
  124. package/web/dist/assets/{MarketplaceDrawer-Dd8WIA8T.js → MarketplaceDrawer-CfSiRi8e.js} +11 -11
  125. package/web/dist/assets/TaskGraphDrawer-C2JufcPs.js +1 -0
  126. package/web/dist/assets/WhatsNewDialog-vP7buLos.js +1 -0
  127. package/web/dist/assets/WorkerModal-CSorwcdP.js +1 -0
  128. package/web/dist/assets/{WorkflowsDrawer-Bjf4olbR.js → WorkflowsDrawer-BXS3w9Uq.js} +1 -1
  129. package/web/dist/assets/WorkspaceMemoryDrawer-D71ivohr.js +1 -0
  130. package/web/dist/assets/{WorkspaceTaskDrawer-BIWwISvA.js → WorkspaceTaskDrawer-CGCTSHKa.js} +1 -1
  131. package/web/dist/assets/index-BcwN8cCw.js +79 -0
  132. package/web/dist/assets/index-StXTPHls.css +1 -0
  133. package/web/dist/assets/{search-Bk2HQvO7.js → search-BZw4T67h.js} +1 -1
  134. package/web/dist/assets/{square-terminal-D93m9hfY.js → square-terminal-B7E57In1.js} +1 -1
  135. package/web/dist/index.html +2 -2
  136. package/web/dist/sw.js +1 -1
  137. package/dist/src/server/env-sync-message.d.ts +0 -9
  138. package/dist/src/server/env-sync-message.js +0 -29
  139. package/web/dist/assets/AddWorkerDialog-CbV75qUX.js +0 -2
  140. package/web/dist/assets/AddWorkspaceFlow-CwV-7wPx.js +0 -1
  141. package/web/dist/assets/FirstRunWizard-a6PWIK3x.js +0 -1
  142. package/web/dist/assets/TaskGraphDrawer-Bk5WFIk_.js +0 -1
  143. package/web/dist/assets/WhatsNewDialog-C2VZaip0.js +0 -1
  144. package/web/dist/assets/WorkerModal-DucW-9YT.js +0 -1
  145. package/web/dist/assets/WorkspaceMemoryDrawer-DglCy_5f.js +0 -1
  146. package/web/dist/assets/index-BAiLYajK.css +0 -1
  147. package/web/dist/assets/index-BV2k9Dts.js +0 -73
@@ -0,0 +1,154 @@
1
+ import { BadRequestError, HttpError, PayloadTooLargeError } from './http-errors.js';
2
+ import { HIVE_REMOTE_DEVICE_HEADER } from './remote-loopback-auth.js';
3
+ import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
4
+ import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
5
+ import { WORKSPACE_UPLOAD_JSON_BODY_LIMIT_BYTES, WORKSPACE_UPLOAD_MAX_BYTES, } from './upload-limits.js';
6
+ const BASE64_RE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
7
+ const MAX_UPLOAD_LIST_LIMIT = 100;
8
+ // Bounds the multi-pass sanitizer cost: a body-limit-sized filename string
9
+ // would otherwise burn seconds of CPU before the final 180-char truncation.
10
+ const MAX_FILENAME_INPUT_LENGTH = 1024;
11
+ const UPLOAD_LIMIT_LABEL = `${WORKSPACE_UPLOAD_MAX_BYTES / (1024 * 1024)}MB`;
12
+ const assertWorkspaceExists = (store, workspaceId) => {
13
+ if (!store.listWorkspaces().some((workspace) => workspace.id === workspaceId)) {
14
+ throw new HttpError(404, 'Workspace not found');
15
+ }
16
+ };
17
+ const readUploadBody = async (request) => {
18
+ try {
19
+ return await readJsonBody(request, {
20
+ limitBytes: WORKSPACE_UPLOAD_JSON_BODY_LIMIT_BYTES,
21
+ });
22
+ }
23
+ catch (error) {
24
+ if (error instanceof SyntaxError) {
25
+ throw new BadRequestError('Request body must be valid JSON');
26
+ }
27
+ throw error;
28
+ }
29
+ };
30
+ const decodeUploadData = (value) => {
31
+ if (typeof value !== 'string') {
32
+ throw new BadRequestError('data must be a base64 string');
33
+ }
34
+ const data = value.trim();
35
+ if (data.length === 0)
36
+ return Buffer.alloc(0);
37
+ if (data.length % 4 !== 0 || !BASE64_RE.test(data)) {
38
+ throw new BadRequestError('data must be valid base64');
39
+ }
40
+ const padding = data.endsWith('==') ? 2 : data.endsWith('=') ? 1 : 0;
41
+ const decodedBytes = (data.length / 4) * 3 - padding;
42
+ if (decodedBytes > WORKSPACE_UPLOAD_MAX_BYTES) {
43
+ throw new PayloadTooLargeError(`Upload exceeds the ${UPLOAD_LIMIT_LABEL} limit`);
44
+ }
45
+ const buffer = Buffer.from(data, 'base64');
46
+ if (buffer.byteLength !== decodedBytes) {
47
+ throw new BadRequestError('data must be valid base64');
48
+ }
49
+ return buffer;
50
+ };
51
+ const readFilename = (value) => {
52
+ if (value === undefined || value === null)
53
+ return 'upload';
54
+ if (typeof value !== 'string')
55
+ throw new BadRequestError('filename must be a string');
56
+ return value.slice(0, MAX_FILENAME_INPUT_LENGTH);
57
+ };
58
+ const readMimeType = (value) => {
59
+ if (value === undefined || value === null)
60
+ return null;
61
+ if (typeof value !== 'string')
62
+ throw new BadRequestError('mime_type must be a string');
63
+ return value;
64
+ };
65
+ const readRemoteDeviceId = (request, tunnelRequest) => {
66
+ if (!tunnelRequest)
67
+ return null;
68
+ const raw = request.headers[HIVE_REMOTE_DEVICE_HEADER];
69
+ const value = Array.isArray(raw) ? raw[0] : raw;
70
+ if (typeof value !== 'string')
71
+ return null;
72
+ const trimmed = value.trim();
73
+ if (!trimmed || trimmed.length > 128 || /[\r\n]/.test(trimmed))
74
+ return null;
75
+ return trimmed;
76
+ };
77
+ const serializeUpload = (record) => ({
78
+ created_at: record.createdAt,
79
+ id: record.id,
80
+ mime_type: record.mimeType,
81
+ original_name: record.originalName,
82
+ size_bytes: record.sizeBytes,
83
+ url: `/api/workspaces/${encodeURIComponent(record.workspaceId)}/uploads/${encodeURIComponent(record.id)}`,
84
+ workspace_id: record.workspaceId,
85
+ });
86
+ const readListLimit = (request) => {
87
+ const url = new URL(request.url ?? '/', 'http://127.0.0.1');
88
+ const raw = url.searchParams.get('limit');
89
+ if (raw === null)
90
+ return undefined;
91
+ if (!/^(0|[1-9][0-9]*)$/.test(raw)) {
92
+ throw new BadRequestError('limit must be a non-negative integer');
93
+ }
94
+ const limit = Number(raw);
95
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_UPLOAD_LIST_LIMIT) {
96
+ throw new BadRequestError(`limit must be between 1 and ${MAX_UPLOAD_LIST_LIMIT}`);
97
+ }
98
+ return limit;
99
+ };
100
+ const encodeDispositionValue = (value) => encodeURIComponent(value).replace(/['()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
101
+ const contentDispositionFor = (filename) => {
102
+ const fallback = filename
103
+ .replace(/[^\x20-\x7e]/g, '_')
104
+ .replace(/[\\"]/g, '_')
105
+ .trim()
106
+ .slice(0, 120) || 'upload';
107
+ return `attachment; filename="${fallback}"; filename*=UTF-8''${encodeDispositionValue(filename)}`;
108
+ };
109
+ export const workspaceUploadRoutes = [
110
+ route('GET', '/api/workspaces/:workspaceId/uploads', ({ params, request, response, store }) => {
111
+ const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Workspace id is required');
112
+ if (!workspaceId)
113
+ return;
114
+ requireUiTokenFromRequest(request, store.validateUiToken, store.authorizeRemoteTunnelRequest);
115
+ assertWorkspaceExists(store, workspaceId);
116
+ sendJson(response, 200, store.listWorkspaceUploads(workspaceId, readListLimit(request)).map(serializeUpload));
117
+ }),
118
+ route('POST', '/api/workspaces/:workspaceId/uploads', async ({ params, request, response, store }) => {
119
+ const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Workspace id is required');
120
+ if (!workspaceId)
121
+ return;
122
+ const tunnelRequest = store.authorizeRemoteTunnelRequest(request);
123
+ requireUiTokenFromRequest(request, store.validateUiToken, () => tunnelRequest);
124
+ assertWorkspaceExists(store, workspaceId);
125
+ const body = await readUploadBody(request);
126
+ const record = await store.saveWorkspaceUpload({
127
+ data: decodeUploadData(body.data),
128
+ mimeType: readMimeType(body.mime_type),
129
+ originalName: readFilename(body.filename),
130
+ remoteDeviceId: readRemoteDeviceId(request, tunnelRequest),
131
+ workspaceId,
132
+ });
133
+ sendJson(response, 201, serializeUpload(record));
134
+ }),
135
+ route('GET', '/api/workspaces/:workspaceId/uploads/:uploadId', async ({ params, request, response, store }) => {
136
+ const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Workspace id and upload id are required');
137
+ const uploadId = getRequiredParam(response, params, 'uploadId', 'Workspace id and upload id are required');
138
+ if (!workspaceId || !uploadId)
139
+ return;
140
+ requireUiTokenFromRequest(request, store.validateUiToken, store.authorizeRemoteTunnelRequest);
141
+ assertWorkspaceExists(store, workspaceId);
142
+ const upload = await store.readWorkspaceUpload(workspaceId, uploadId);
143
+ if (!upload) {
144
+ throw new HttpError(404, 'Upload not found');
145
+ }
146
+ response.statusCode = 200;
147
+ response.setHeader('content-type', upload.record.mimeType);
148
+ response.setHeader('content-length', String(upload.data.byteLength));
149
+ response.setHeader('content-disposition', contentDispositionFor(upload.record.originalName));
150
+ response.setHeader('x-content-type-options', 'nosniff');
151
+ response.setHeader('cache-control', 'private, max-age=0, must-revalidate');
152
+ response.end(upload.data);
153
+ }),
154
+ ];
@@ -7,7 +7,33 @@ import { enrichTeamList } from './team-list-enrichment.js';
7
7
  import { serializeTeamListItem } from './team-list-serializer.js';
8
8
  import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
9
9
  import { validateWorkspacePath } from './workspace-path-validation.js';
10
- import { getOrchestratorId } from './workspace-store-support.js';
10
+ import { getOrchestratorId, getWorkflowAgentId } from './workspace-store-support.js';
11
+ /* #35: fold each worker's open dispatches (id, age, status, task preview)
12
+ into the team list payload. Display-only ages — no timeout or heartbeat is
13
+ derived from them; `queued` rows age from createdAt (submittedAt is null
14
+ until delivery). */
15
+ const serializeTeamListWithOpenDispatches = (store, workspaceId) => {
16
+ const now = Date.now();
17
+ const workflowAgentId = getWorkflowAgentId(workspaceId);
18
+ const openByWorker = new Map();
19
+ for (const dispatch of store.listOpenDispatches(workspaceId)) {
20
+ if (dispatch.status !== 'queued' && dispatch.status !== 'submitted')
21
+ continue;
22
+ // Workflow-owned dispatches are the runner's business: handing their ids
23
+ // to the orchestrator invites a `team cancel` that wedges the run.
24
+ if (dispatch.workflowRunId !== null || dispatch.fromAgentId === workflowAgentId)
25
+ continue;
26
+ const list = openByWorker.get(dispatch.toAgentId) ?? [];
27
+ list.push({
28
+ id: dispatch.id,
29
+ status: dispatch.status,
30
+ age_minutes: Math.max(0, Math.floor((now - (dispatch.submittedAt ?? dispatch.createdAt)) / 60_000)),
31
+ task_preview: dispatch.text.slice(0, 60),
32
+ });
33
+ openByWorker.set(dispatch.toAgentId, list);
34
+ }
35
+ return enrichTeamList(workspaceId, store, store.listWorkers(workspaceId)).map((worker) => serializeTeamListItem(worker, openByWorker.get(worker.id)));
36
+ };
11
37
  const getSerializedWorker = (workspaceId, workerId, store) => {
12
38
  const worker = store.listWorkers(workspaceId).find((item) => item.id === workerId);
13
39
  if (!worker) {
@@ -61,7 +87,7 @@ export const workspaceRoutes = [
61
87
  return;
62
88
  }
63
89
  requireUiTokenFromRequest(request, store.validateUiToken, store.authorizeRemoteTunnelRequest);
64
- sendJson(response, 200, enrichTeamList(workspaceId, store, store.listWorkers(workspaceId)).map(serializeTeamListItem));
90
+ sendJson(response, 200, serializeTeamListWithOpenDispatches(store, workspaceId));
65
91
  }),
66
92
  route('GET', '/api/workspaces/:workspaceId/team', ({ params, request, response, store }) => {
67
93
  const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Workspace id is required');
@@ -83,7 +109,7 @@ export const workspaceRoutes = [
83
109
  // it is reachable again. Cheap (indexed, usually empty) and a safe no-op
84
110
  // for non-orchestrator callers.
85
111
  store.drainReportOutbox(workspaceId);
86
- sendJson(response, 200, enrichTeamList(workspaceId, store, store.listWorkers(workspaceId)).map(serializeTeamListItem));
112
+ sendJson(response, 200, serializeTeamListWithOpenDispatches(store, workspaceId));
87
113
  }),
88
114
  route('POST', '/api/workspaces/:workspaceId/workers', async ({ params, request, response, store }) => {
89
115
  const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Workspace id is required');
@@ -1,10 +1,13 @@
1
1
  import { matchPath } from './route-helpers.js';
2
+ import { actionCenterRoutes } from './routes-action-center.js';
3
+ import { diagnosticsRoutes } from './routes-diagnostics.js';
2
4
  import { dispatchRoutes } from './routes-dispatches.js';
3
5
  import { fsRoutes } from './routes-fs.js';
4
6
  import { marketplaceRoutes } from './routes-marketplace.js';
5
7
  import { openWorkspaceRoutes } from './routes-open-workspace.js';
6
8
  import { remoteRoutes } from './routes-remote.js';
7
9
  import { runtimeRoutes } from './routes-runtime.js';
10
+ import { scenarioRoutes } from './routes-scenarios.js';
8
11
  import { settingsRoutes } from './routes-settings.js';
9
12
  import { taskRoutes } from './routes-tasks.js';
10
13
  import { teamRoutes } from './routes-team.js';
@@ -16,10 +19,15 @@ import { workflowScheduleRoutes } from './routes-workflow-schedules.js';
16
19
  import { workflowRoutes } from './routes-workflows.js';
17
20
  import { workspaceMemoryRoutes } from './routes-workspace-memory.js';
18
21
  import { workspaceMemoryDreamRoutes } from './routes-workspace-memory-dreams.js';
22
+ import { workspaceUploadRoutes } from './routes-workspace-uploads.js';
19
23
  import { workspaceRoutes } from './routes-workspaces.js';
20
24
  const routes = [
21
25
  ...workspaceRoutes,
26
+ ...scenarioRoutes,
27
+ ...workspaceUploadRoutes,
22
28
  ...openWorkspaceRoutes,
29
+ ...actionCenterRoutes,
30
+ ...diagnosticsRoutes,
23
31
  ...dispatchRoutes,
24
32
  ...versionRoutes,
25
33
  ...uiRoutes,
@@ -3,5 +3,4 @@ export declare const createUserInputMessage: (workspaceId: string, orchestratorI
3
3
  export declare const createSendMessage: (workspaceId: string, workerId: string, text: string, fromAgentId?: string) => MessageLogRecord;
4
4
  export declare const createReportMessage: (workspaceId: string, workerId: string, text: string, status: string | undefined, artifacts: string[]) => MessageLogRecord;
5
5
  export declare const createStatusMessage: (workspaceId: string, workerId: string, text: string, artifacts: string[]) => MessageLogRecord;
6
- export declare const createSystemEnvSyncMessage: (workspaceId: string, agentId: string, text: string) => MessageLogRecord;
7
6
  export declare const createSystemRecoverySummaryMessage: (workspaceId: string, agentId: string, text: string) => MessageLogRecord;
@@ -42,14 +42,6 @@ export const createStatusMessage = (workspaceId, workerId, text, artifacts) => (
42
42
  workerId,
43
43
  workspaceId,
44
44
  });
45
- export const createSystemEnvSyncMessage = (workspaceId, agentId, text) => ({
46
- createdAt: Date.now(),
47
- text,
48
- toAgentId: agentId,
49
- type: 'system_env_sync',
50
- workerId: agentId,
51
- workspaceId,
52
- });
53
45
  export const createSystemRecoverySummaryMessage = (workspaceId, agentId, text) => ({
54
46
  createdAt: Date.now(),
55
47
  text,
@@ -11,6 +11,7 @@ import type { DeviceSessionProvider } from './remote-device-session.js';
11
11
  import type { RemoteDeviceRecord, RemoteDeviceStore } from './remote-device-store.js';
12
12
  import type { RemotePairing } from './remote-pairing.js';
13
13
  import type { RemoteTunnel, TunnelStatus } from './remote-tunnel.js';
14
+ import type { MemoryDreamInput } from './runtime-store-dream.js';
14
15
  import type { SettingsStore } from './settings-store.js';
15
16
  import type { DreamRunRecord } from './team-memory-dream-store.js';
16
17
  import type { AddMemoryEntryInput, LogMemoryInjectionsInput, MemoryEntryWithSources, MemoryInjectionWithMemory, MemoryListOptions, MemorySearchOptions, MemorySearchResult } from './team-memory-store.js';
@@ -22,6 +23,7 @@ import type { WorkflowRunRecord } from './workflow-run-store.js';
22
23
  import type { RunInlineWorkflowInput, RunWorkflowInput, WorkflowRunner } from './workflow-runner.js';
23
24
  import type { WorkflowScheduleRecord } from './workflow-schedule-store.js';
24
25
  import type { WorkerInput, WorkspaceRecord } from './workspace-store.js';
26
+ import type { SaveWorkspaceUploadInput, WorkspaceUploadRecord } from './workspace-upload-store.js';
25
27
  export interface RuntimeStore {
26
28
  close: () => Promise<void>;
27
29
  createWorkspace: (path: string, name: string) => WorkspaceSummary;
@@ -35,6 +37,7 @@ export interface RuntimeStore {
35
37
  dispatchTask: (workspaceId: string, workerId: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
36
38
  dispatchTaskByWorkerName: (workspaceId: string, workerName: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord & {
37
39
  restartedWorker: boolean;
40
+ queuedForStoppedWorker?: boolean;
38
41
  }>;
39
42
  reportTask: (workspaceId: string, workerId: string, input?: ReportTaskInput) => ReportTaskResult;
40
43
  /** Flush any reports stranded by a prior orchestrator outage. Safe no-op
@@ -43,6 +46,8 @@ export interface RuntimeStore {
43
46
  statusTask: (workspaceId: string, workerId: string, input?: StatusTaskInput) => ReportTaskResult;
44
47
  cancelTask: (workspaceId: string, dispatchId: string, input: CancelTaskInput) => ReportTaskResult;
45
48
  listDispatches: (workspaceId: string, options?: ListDispatchesOptions) => DispatchRecord[];
49
+ listOpenDispatches: (workspaceId: string) => DispatchRecord[];
50
+ listRecentDispatches: (workspaceId: string, limit?: number) => DispatchRecord[];
46
51
  listWorkers: (workspaceId: string) => TeamListItem[];
47
52
  getLastPtyLineForAgent: (workspaceId: string, agentId: string) => string | null;
48
53
  getWorkspaceSnapshot: (workspaceId: string) => WorkspaceRecord;
@@ -80,6 +85,8 @@ export interface RuntimeStore {
80
85
  searchMemoryEntries: (workspaceId: string, query: string, options?: MemorySearchOptions) => MemorySearchResult[];
81
86
  setMemoryDisabled: (workspaceId: string, memoryId: string, disabled: boolean) => MemoryEntryWithSources;
82
87
  setMemoryPinned: (workspaceId: string, memoryId: string, pinned: boolean) => MemoryEntryWithSources;
88
+ applyMemoryDreamRun: (workspaceId: string, runId: string, rawOps: unknown) => DreamRunRecord;
89
+ getMemoryDreamInput: (workspaceId: string, runId: string) => MemoryDreamInput;
83
90
  listMemoryDreamRuns: (workspaceId: string, limit?: number) => DreamRunRecord[];
84
91
  revertMemoryDream: (workspaceId: string, runId: string) => DreamRunRecord;
85
92
  runMemoryDream: (workspaceId: string) => Promise<DreamRunRecord>;
@@ -102,6 +109,8 @@ export interface RuntimeStore {
102
109
  confirmRemotePairing: (pairingId: string, name?: string) => Promise<RemoteDeviceRecord | null>;
103
110
  getRemoteDeviceStore: () => RemoteDeviceStore;
104
111
  getRemoteTunnelStatus: () => TunnelStatus;
112
+ /** Local retention signals (issue #23): per-day protocol event counts. Local-only, never transmitted. */
113
+ getRetentionSignals: () => import('./protocol-event-stats.js').RetentionSignals;
105
114
  setRemoteTunnelStatus: (status: TunnelStatus) => void;
106
115
  bindRemoteTunnel: (tunnel: RemoteTunnel) => void;
107
116
  setRemoteEnabled: (enabled: boolean) => void;
@@ -119,6 +128,12 @@ export interface RuntimeStore {
119
128
  ts: number;
120
129
  message: string;
121
130
  }>;
131
+ saveWorkspaceUpload: (input: SaveWorkspaceUploadInput) => Promise<WorkspaceUploadRecord>;
132
+ listWorkspaceUploads: (workspaceId: string, limit?: number) => WorkspaceUploadRecord[];
133
+ readWorkspaceUpload: (workspaceId: string, uploadId: string) => Promise<{
134
+ data: Buffer;
135
+ record: WorkspaceUploadRecord;
136
+ } | undefined>;
122
137
  createWorkflowSchedule: (input: {
123
138
  workspaceId: string;
124
139
  scriptPath: string;
@@ -1,4 +1,9 @@
1
- import type { DreamRunRecord } from './team-memory-dream-store.js';
1
+ import { type DreamMessageInput, type DreamRunRecord } from './team-memory-dream-store.js';
2
+ import type { MemoryEntryWithSources } from './team-memory-store.js';
3
+ export interface MemoryDreamInput {
4
+ prompt: string;
5
+ run: DreamRunRecord;
6
+ }
2
7
  interface RuntimeStoreDreamServices {
3
8
  teamMemoryDreamRunner: {
4
9
  runManual: (workspaceId: string) => Promise<DreamRunRecord>;
@@ -7,14 +12,22 @@ interface RuntimeStoreDreamServices {
7
12
  tick: (now?: number) => Promise<void>;
8
13
  };
9
14
  teamMemoryDreamStore: {
15
+ applyAndCompleteRun: (workspaceId: string, runId: string, rawOps: unknown) => DreamRunRecord;
16
+ getRun: (runId: string) => DreamRunRecord | undefined;
17
+ listInputMessages: (workspaceId: string, from: number | null, to: number | null) => DreamMessageInput[];
10
18
  listRuns: (workspaceId: string, limit?: number) => DreamRunRecord[];
11
19
  revertRun: (workspaceId: string, runId: string) => DreamRunRecord;
12
20
  };
21
+ teamMemoryStore: {
22
+ listAllEntries: (workspaceId: string) => MemoryEntryWithSources[];
23
+ };
13
24
  teamMemoryExport: {
14
25
  schedule: (workspaceId: string) => void;
15
26
  };
16
27
  }
17
28
  export declare const createRuntimeStoreDreamMethods: (services: RuntimeStoreDreamServices) => {
29
+ applyMemoryDreamRun(workspaceId: string, runId: string, rawOps: unknown): DreamRunRecord;
30
+ getMemoryDreamInput(workspaceId: string, runId: string): MemoryDreamInput;
18
31
  listMemoryDreamRuns(workspaceId: string, limit?: number): DreamRunRecord[];
19
32
  revertMemoryDream(workspaceId: string, runId: string): DreamRunRecord;
20
33
  runMemoryDream(workspaceId: string): Promise<DreamRunRecord>;
@@ -1,6 +1,54 @@
1
+ import { buildDreamPrompt, DREAM_PROMPT_MEMORY_LIMIT, DREAM_PROMPT_MESSAGE_LIMIT, } from './team-memory-dream-prompt.js';
2
+ import { DREAM_STALE_ERROR, DreamRunNotFoundError, DreamRunValidationError, } from './team-memory-dream-store.js';
3
+ const shouldExportStaleRun = (run) => run.status === 'failed' && run.error === DREAM_STALE_ERROR;
1
4
  export const createRuntimeStoreDreamMethods = (services) => ({
5
+ applyMemoryDreamRun(workspaceId, runId, rawOps) {
6
+ const existing = services.teamMemoryDreamStore.getRun(runId);
7
+ if (!existing || existing.workspaceId !== workspaceId) {
8
+ throw new DreamRunNotFoundError(workspaceId, runId);
9
+ }
10
+ if (existing.status !== 'running') {
11
+ if (shouldExportStaleRun(existing))
12
+ services.teamMemoryExport.schedule(workspaceId);
13
+ throw new DreamRunValidationError('Dream run is no longer running');
14
+ }
15
+ try {
16
+ const run = services.teamMemoryDreamStore.applyAndCompleteRun(workspaceId, runId, rawOps);
17
+ services.teamMemoryExport.schedule(workspaceId);
18
+ return run;
19
+ }
20
+ catch (error) {
21
+ services.teamMemoryExport.schedule(workspaceId);
22
+ throw error;
23
+ }
24
+ },
25
+ getMemoryDreamInput(workspaceId, runId) {
26
+ const run = services.teamMemoryDreamStore.getRun(runId);
27
+ if (!run || run.workspaceId !== workspaceId)
28
+ throw new DreamRunNotFoundError(workspaceId, runId);
29
+ if (run.status !== 'running') {
30
+ if (shouldExportStaleRun(run))
31
+ services.teamMemoryExport.schedule(workspaceId);
32
+ throw new DreamRunValidationError('Dream run is no longer running');
33
+ }
34
+ const messages = services.teamMemoryDreamStore
35
+ .listInputMessages(workspaceId, run.inputSeqFrom, run.inputSeqTo)
36
+ .slice(-DREAM_PROMPT_MESSAGE_LIMIT);
37
+ const memories = services.teamMemoryStore
38
+ .listAllEntries(workspaceId)
39
+ .filter((memory) => memory.status === 'active' && memory.updatedAt <= run.startedAt)
40
+ .slice(0, DREAM_PROMPT_MEMORY_LIMIT);
41
+ return {
42
+ prompt: buildDreamPrompt({ memories, messages, workspaceId }),
43
+ run,
44
+ };
45
+ },
2
46
  listMemoryDreamRuns(workspaceId, limit) {
3
- return services.teamMemoryDreamStore.listRuns(workspaceId, limit);
47
+ const runs = services.teamMemoryDreamStore.listRuns(workspaceId, limit);
48
+ if (runs.some(shouldExportStaleRun)) {
49
+ services.teamMemoryExport.schedule(workspaceId);
50
+ }
51
+ return runs;
4
52
  },
5
53
  revertMemoryDream(workspaceId, runId) {
6
54
  const run = services.teamMemoryDreamStore.revertRun(workspaceId, runId);
@@ -4,12 +4,14 @@ import { createAgentRuntime } from './agent-runtime.js';
4
4
  import type { LiveAgentRun } from './agent-runtime-types.js';
5
5
  import { createDispatchLedgerStore } from './dispatch-ledger-store.js';
6
6
  import { createMessageLogStore } from './message-log-store.js';
7
+ import { createProtocolEventStats } from './protocol-event-stats.js';
7
8
  import type { PtyOutputBus } from './pty-output-bus.js';
8
9
  import { type RemoteAuditStore } from './remote-audit-store.js';
9
10
  import type { DeviceSessionProvider } from './remote-device-session.js';
10
11
  import { type RemoteDeviceStore } from './remote-device-store.js';
11
12
  import { type RemotePairing } from './remote-pairing.js';
12
13
  import { openRuntimeDatabase } from './runtime-database.js';
14
+ import { createSentinelHeartbeatService } from './sentinel-heartbeat.js';
13
15
  import { createSettingsStore } from './settings-store.js';
14
16
  import { createTasksFileService } from './tasks-file.js';
15
17
  import { createTasksFileWatcher } from './tasks-file-watcher.js';
@@ -29,6 +31,7 @@ import { createWorkflowRunStore } from './workflow-run-store.js';
29
31
  import { createWorkflowScheduleStore } from './workflow-schedule-store.js';
30
32
  import { createWorkspaceShellRuntime } from './workspace-shell-runtime.js';
31
33
  import { createWorkspaceStore } from './workspace-store.js';
34
+ import { createWorkspaceUploadStore } from './workspace-upload-store.js';
32
35
  export interface RuntimeStoreServices {
33
36
  agentRunStore: ReturnType<typeof createAgentRunStore>;
34
37
  agentRuntime: ReturnType<typeof createAgentRuntime>;
@@ -46,6 +49,8 @@ export interface RuntimeStoreServices {
46
49
  tasksFileService: ReturnType<typeof createTasksFileService>;
47
50
  teamMemoryDreamRunner: ReturnType<typeof createTeamMemoryDreamRunner>;
48
51
  teamMemoryDreamScheduler: ReturnType<typeof createTeamMemoryDreamScheduler>;
52
+ sentinelHeartbeat: ReturnType<typeof createSentinelHeartbeatService>;
53
+ protocolEventStats: ReturnType<typeof createProtocolEventStats>;
49
54
  teamMemoryDreamStore: ReturnType<typeof createTeamMemoryDreamStore>;
50
55
  teamMemoryStore: ReturnType<typeof createTeamMemoryStore>;
51
56
  teamMemoryExport: ReturnType<typeof createTeamMemoryExportService>;
@@ -59,6 +64,8 @@ export interface RuntimeStoreServices {
59
64
  workflowRunStore: ReturnType<typeof createWorkflowRunStore>;
60
65
  workflowScheduleStore: ReturnType<typeof createWorkflowScheduleStore>;
61
66
  workspaceStore: ReturnType<typeof createWorkspaceStore>;
67
+ workspaceUploadStore: ReturnType<typeof createWorkspaceUploadStore>;
68
+ workspaceUploadStorageCleanup: () => void;
62
69
  }
63
70
  interface CreateRuntimeStoreServicesOptions {
64
71
  agentManager?: AgentManager;