maka-agent 0.2.0-dev.31.20260913 → 0.2.0-dev.32.20260914

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 (71) hide show
  1. package/dist/pi-transcript.js +3 -4
  2. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  3. package/node_modules/@maka/core/dist/agent-graph-schedule.js +15 -4
  4. package/node_modules/@maka/core/dist/executor-id.js +22 -0
  5. package/node_modules/@maka/core/dist/external-session.js +18 -0
  6. package/node_modules/@maka/core/dist/model-call-usage-projection.js +0 -13
  7. package/node_modules/@maka/core/dist/runtime-event.js +19 -3
  8. package/node_modules/@maka/core/dist/session-send-projection.js +6 -3
  9. package/node_modules/@maka/core/dist/session.js +8 -2
  10. package/node_modules/@maka/core/dist/shell-run-result.js +1 -0
  11. package/node_modules/@maka/core/dist/shell-run.js +4 -0
  12. package/node_modules/@maka/core/dist/work-board.js +94 -1
  13. package/node_modules/@maka/core/package.json +1 -0
  14. package/node_modules/@maka/eval/dist/fleet-simulation.js +270 -0
  15. package/node_modules/@maka/eval/dist/fleet-store.js +243 -0
  16. package/node_modules/@maka/eval/dist/fleet-worker.js +125 -0
  17. package/node_modules/@maka/eval/dist/fleet.js +329 -0
  18. package/node_modules/@maka/eval/dist/index.js +3 -0
  19. package/node_modules/@maka/runtime/dist/agent-run.js +2 -17
  20. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +6 -2
  21. package/node_modules/@maka/runtime/dist/ai-sdk-message-projection.js +36 -24
  22. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +257 -330
  23. package/node_modules/@maka/runtime/dist/background-task-health-tool.js +104 -0
  24. package/node_modules/@maka/runtime/dist/history-compaction.js +3 -1
  25. package/node_modules/@maka/runtime/dist/local-web-fetch.js +1 -1
  26. package/node_modules/@maka/runtime/dist/model-adapter.js +62 -53
  27. package/node_modules/@maka/runtime/dist/model-history.js +1 -5
  28. package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +311 -0
  29. package/node_modules/@maka/runtime/dist/plugin-executor-service.js +344 -0
  30. package/node_modules/@maka/runtime/dist/provider-error-classification.js +7 -1
  31. package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +35 -8
  32. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +7 -1
  33. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +1 -0
  34. package/node_modules/@maka/runtime/dist/runtime-invocation-route.js +49 -0
  35. package/node_modules/@maka/runtime/dist/runtime-kernel.js +72 -150
  36. package/node_modules/@maka/runtime/dist/runtime-read-model.js +0 -2
  37. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -0
  38. package/node_modules/@maka/runtime/dist/session-manager.js +100 -58
  39. package/node_modules/@maka/runtime/dist/shell-run-manager.js +9 -3
  40. package/node_modules/@maka/runtime/dist/shell-run-tool-result.js +1 -0
  41. package/node_modules/@maka/runtime/dist/stream-graph-schedule-reconcile.js +1 -0
  42. package/node_modules/@maka/runtime/dist/stream-graph-supervisor-tools.js +26 -4
  43. package/node_modules/@maka/runtime/dist/subagent-tools.js +7 -0
  44. package/node_modules/@maka/runtime/dist/tool-runtime.js +45 -466
  45. package/node_modules/@maka/runtime/package.json +3 -0
  46. package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +7 -2
  47. package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +1 -0
  48. package/node_modules/@maka/runtime-host/dist/protocol/external-session.js +24 -3
  49. package/node_modules/@maka/runtime-host/dist/protocol/index.js +4 -1
  50. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +42 -5
  51. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +31 -3
  52. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +6 -0
  53. package/node_modules/@maka/runtime-host/dist/server/child-agent-composition.js +1 -0
  54. package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +1 -22
  55. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +54 -8
  56. package/node_modules/@maka/runtime-host/dist/server/external-session-coordinator.js +14 -1
  57. package/node_modules/@maka/runtime-host/dist/server/host-session-availability.js +10 -2
  58. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
  59. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +6 -0
  60. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +56 -16
  61. package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +4 -3
  62. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +4 -0
  63. package/node_modules/@maka/runtime-host/dist/server/web-fetch-tool.js +37 -1
  64. package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +450 -234
  65. package/node_modules/@maka/storage/dist/claude-code-transcript-lineage.js +106 -77
  66. package/node_modules/@maka/storage/dist/legacy-run-header.js +2 -2
  67. package/node_modules/@maka/storage/dist/model-call-usage-sql.js +1 -1
  68. package/node_modules/@maka/storage/dist/session-store.js +12 -2
  69. package/node_modules/@maka/storage/dist/sqlite-long-term-memory-store.js +86 -12
  70. package/node_modules/@maka/storage/dist/work-board-store.js +40 -1
  71. package/package.json +1 -1
@@ -945,10 +945,9 @@ function systemNoteText(message) {
945
945
  const data = message.data;
946
946
  const used = typeof data?.inputTokens === 'number' ? data.inputTokens : undefined;
947
947
  const prior = typeof data?.priorInputTokens === 'number' ? data.priorInputTokens : undefined;
948
- if (used === undefined || prior === undefined) {
949
- return 'The provider is dropping or rewriting context: content was appended but its reported usage did not grow. Declare a context window for this model so Maka compacts first.';
950
- }
951
- return `The provider is dropping or rewriting context: content was appended, and it counted ${used} input tokens against ${prior} before, which is no growth. Declare a context window for this model so Maka compacts first.`;
948
+ if (used === undefined || prior === undefined)
949
+ return "After content was appended, the provider-reported input token count did not grow; context may have been truncated or rewritten. If this persists, check that the model's actual context capacity and the connection settings agree.";
950
+ return `After content was appended, the provider-reported input token count did not grow; context may have been truncated or rewritten (${used} tokens versus ${prior} before). If this persists, check that the model's actual context capacity and the connection settings agree.`;
952
951
  }
953
952
  case 'context_overflow_after_compaction':
954
953
  return 'History was compacted and the provider still called this request too large. What remains also carries the system prompt, the tool schemas, the summary and the recent tail; shortening this message is the part you control.';
@@ -16,6 +16,7 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
+ import { isExecutorId } from './executor-id.js';
19
20
  export const AGENT_GRAPH_SCHEDULE_UPDATE_SCHEMA_VERSION = 1;
20
21
  export const AGENT_GRAPH_SCHEDULE_MAX_ADD_WORK = 32;
21
22
  export const AGENT_GRAPH_SCHEDULE_MAX_STOP = 20;
@@ -192,14 +193,24 @@ function isSelectedResultInput(value) {
192
193
  function isWorkTarget(value) {
193
194
  if (!value || typeof value !== 'object' || Array.isArray(value))
194
195
  return false;
195
- if (isExactRecord(value, ['kind', 'agentId']) &&
196
+ if (isExactRecord(value, [
197
+ 'kind',
198
+ 'agentId',
199
+ ...(hasOwn(value, 'executorId') ? ['executorId'] : []),
200
+ ]) &&
196
201
  value.kind === 'agent' &&
197
- isOpaqueIdentity(value.agentId)) {
202
+ isOpaqueIdentity(value.agentId) &&
203
+ (value.executorId === undefined || isExecutorId(value.executorId))) {
198
204
  return true;
199
205
  }
200
- if (isExactRecord(value, ['kind', 'presetId']) &&
206
+ if (isExactRecord(value, [
207
+ 'kind',
208
+ 'presetId',
209
+ ...(hasOwn(value, 'executorId') ? ['executorId'] : []),
210
+ ]) &&
201
211
  value.kind === 'preset' &&
202
- isOpaqueIdentity(value.presetId)) {
212
+ isOpaqueIdentity(value.presetId) &&
213
+ (value.executorId === undefined || isExecutorId(value.executorId))) {
203
214
  return true;
204
215
  }
205
216
  return (isExactRecord(value, ['kind', 'operatorId']) &&
@@ -0,0 +1,22 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ export const EXECUTOR_ID_PATTERN = /^[A-Za-z][A-Za-z0-9._:-]{0,127}$/u;
20
+ export function isExecutorId(value) {
21
+ return typeof value === 'string' && EXECUTOR_ID_PATTERN.test(value);
22
+ }
@@ -114,6 +114,24 @@ function normalizeExternalSessionPath(value) {
114
114
  const trimmed = stripped.length > 0 ? stripped : folded.length > 0 ? '/' : '';
115
115
  return /^[A-Za-z]:\//u.test(trimmed) ? trimmed.toLowerCase() : trimmed;
116
116
  }
117
+ export const EXTERNAL_SESSION_LIMIT_KINDS = [
118
+ 'transcript_bytes',
119
+ 'record_bytes',
120
+ 'records',
121
+ 'converted_bytes',
122
+ 'messages',
123
+ ];
124
+ export class ExternalSessionLimitError extends Error {
125
+ limit;
126
+ constructor(kind, max, message) {
127
+ super(message);
128
+ if (!EXTERNAL_SESSION_LIMIT_KINDS.includes(kind) || !Number.isSafeInteger(max) || max <= 0) {
129
+ throw new Error('Invalid external Session import limit');
130
+ }
131
+ this.name = 'ExternalSessionLimitError';
132
+ this.limit = Object.freeze({ kind, max });
133
+ }
134
+ }
117
135
  export class ExternalSessionAdapterRegistry {
118
136
  adapters = new Map();
119
137
  constructor(adapters = []) {
@@ -29,19 +29,6 @@ export function resolveUsageRange(range, now) {
29
29
  };
30
30
  return { from: now - spans[range], to: now };
31
31
  }
32
- /**
33
- * Maps a physical attempt outcome onto the status vocabulary the Usage surface
34
- * filters by. `interrupted` joins `aborted`: both mean the call stopped short
35
- * without the provider reporting a failure, and collapsing it into `error`
36
- * would inflate the error rate with user cancellations.
37
- */
38
- export function usageStatusForAttempt(status) {
39
- if (status === 'completed')
40
- return 'success';
41
- if (status === 'failed')
42
- return 'error';
43
- return 'aborted';
44
- }
45
32
  export function clampCacheReadTokens(inputTokens, cacheReadTokens) {
46
33
  return Math.min(cacheReadTokens, inputTokens);
47
34
  }
@@ -40,6 +40,7 @@ import { isPermissionMode, } from './permission.js';
40
40
  import { isCollaborationMode } from './collaboration.js';
41
41
  import { isAgentSwarmAuthorizationSource, isEffectiveOrchestrationSource, isOrchestrationMode, } from './orchestration.js';
42
42
  import { isToolMode } from './tool-mode.js';
43
+ import { isExecutorId } from './executor-id.js';
43
44
  import { isRuntimeSystemNoteKind, } from './session.js';
44
45
  import { decodeTurnOrigin } from './turn-origin.js';
45
46
  import { defineObjectShape, hasExactShape, isFiniteNumber, isOptionalMember, isOptionalString, isRecord, isStringArray, } from './record-schema.js';
@@ -172,6 +173,7 @@ export function runtimeEventEnvelopeValueDomains() {
172
173
  const RUNTIME_EVENT_SHAPE = defineObjectShape()(['id', 'invocationId', 'runId', 'sessionId', 'turnId', 'ts', 'partial', 'role', 'author'], ['branch', 'origin', 'modelVisibility', 'status', 'content', 'actions', 'refs']);
173
174
  const TEXT_CONTENT_SHAPE = defineObjectShape()(['kind', 'text'], [
174
175
  'displayText',
176
+ 'interrupted',
175
177
  'origin',
176
178
  'attachments',
177
179
  'directoryReferences',
@@ -186,7 +188,15 @@ const FUNCTION_RESPONSE_CONTENT_SHAPE = defineObjectShape()(['kind', 'id', 'name
186
188
  const ERROR_CONTENT_SHAPE = defineObjectShape()(['kind', 'message'], ['code', 'reason', 'details', 'retry']);
187
189
  const SYSTEM_NOTE_CONTENT_SHAPE = defineObjectShape()(['kind', 'note'], ['data']);
188
190
  const INVOCATION_OPENED_CONTENT_SHAPE = defineObjectShape()(['kind', 'protocol', 'route', 'configuration', 'root', 'source'], ['lineage']);
189
- const INVOCATION_ROUTE_RUNTIME_SHAPE = defineObjectShape()(['provenance', 'backendKind', 'llmConnectionId', 'llmConnectionSlug', 'modelId'], ['providerStateIdentity']);
191
+ const INVOCATION_ROUTE_RUNTIME_MODEL_SHAPE = defineObjectShape()(['provenance', 'backendKind', 'llmConnectionId', 'llmConnectionSlug', 'modelId'], ['providerStateIdentity']);
192
+ const INVOCATION_ROUTE_RUNTIME_EXECUTOR_SHAPE = defineObjectShape()([
193
+ 'provenance',
194
+ 'backendKind',
195
+ 'executorId',
196
+ 'llmConnectionSlug',
197
+ 'modelId',
198
+ 'providerStateIdentity',
199
+ ], []);
190
200
  const INVOCATION_ROUTE_UNKNOWN_SHAPE = defineObjectShape()(['provenance', 'backendKind', 'llmConnectionSlug', 'modelId'], []);
191
201
  const INVOCATION_CONFIGURATION_SHAPE = defineObjectShape()([
192
202
  'cwd',
@@ -394,6 +404,7 @@ function isRuntimeEventContent(value) {
394
404
  if (!hasExactShape(value, TEXT_CONTENT_SHAPE) ||
395
405
  (value.origin !== undefined && !isTurnOrigin(value.origin)) ||
396
406
  (value.steering !== undefined && value.steering !== true) ||
407
+ (value.interrupted !== undefined && value.interrupted !== true) ||
397
408
  (value.providerOptions !== undefined && !isRecord(value.providerOptions))) {
398
409
  return false;
399
410
  }
@@ -481,14 +492,19 @@ function isRuntimeInvocationRoute(value) {
481
492
  return false;
482
493
  }
483
494
  if (value.provenance === 'runtime') {
484
- return (hasExactShape(value, INVOCATION_ROUTE_RUNTIME_SHAPE) &&
495
+ if (value.backendKind === 'plugin-executor') {
496
+ return (hasExactShape(value, INVOCATION_ROUTE_RUNTIME_EXECUTOR_SHAPE) &&
497
+ isExecutorId(value.executorId) &&
498
+ isSha256Digest(value.providerStateIdentity));
499
+ }
500
+ return (hasExactShape(value, INVOCATION_ROUTE_RUNTIME_MODEL_SHAPE) &&
485
501
  isNonEmptyString(value.llmConnectionId) &&
486
502
  (value.providerStateIdentity === undefined || isSha256Digest(value.providerStateIdentity)));
487
503
  }
488
504
  return value.provenance === 'unknown' && hasExactShape(value, INVOCATION_ROUTE_UNKNOWN_SHAPE);
489
505
  }
490
506
  function isPersistedBackendKind(value) {
491
- return value === 'ai-sdk' || value === 'fake';
507
+ return value === 'ai-sdk' || value === 'plugin-executor' || value === 'fake';
492
508
  }
493
509
  function isRuntimeInvocationConfiguration(value) {
494
510
  return (isRecord(value) &&
@@ -26,11 +26,12 @@
26
26
  * whether that exact target looks usable for presentation and readiness checks.
27
27
  *
28
28
  * The compatibility rules are:
29
- * 1. The session's own connection must pass `isConnectionReady` with
29
+ * 1. Plugin executor Sessions do not depend on Maka model connections.
30
+ * 2. The session's own connection must pass `isConnectionReady` with
30
31
  * the sticky session model.
31
- * 2. Legacy Sessions without an immutable connection id are blocked until
32
+ * 3. Legacy Sessions without an immutable connection id are blocked until
32
33
  * the user explicitly selects an account.
33
- * 3. A missing id, slug mismatch, or unusable exact connection is blocked.
34
+ * 4. A missing id, slug mismatch, or unusable exact connection is blocked.
34
35
  *
35
36
  * `lastTestStatus` deliberately plays no part here (E4): telemetry about
36
37
  * a past credential test must not gate send, so it must not gate the
@@ -71,6 +72,8 @@ function sessionOwnConnectionBlockReason(session, ownConnection, hasSecret) {
71
72
  return verdict.ready ? undefined : verdict.reason;
72
73
  }
73
74
  function ownConnectionBlockReason(session, connections, hasSecret) {
75
+ if (session.backend === 'plugin-executor')
76
+ return undefined;
74
77
  if (session.backend === 'fake')
75
78
  return 'fake_backend';
76
79
  if (!session.llmConnectionId)
@@ -17,6 +17,7 @@
17
17
  * under the License.
18
18
  */
19
19
  import { isWorkHubActionReceipt } from './workhub-action-result.js';
20
+ import { isExecutorId } from './executor-id.js';
20
21
  import { MODEL_FAILURE_MESSAGE_MAX_BYTES, isModelRetryDecision, } from './model-failure.js';
21
22
  import { decodeMessageContent, TOOL_ACTIVITY_KINDS, } from './events.js';
22
23
  import { isPermissionMode, isToolCategory, } from './permission.js';
@@ -290,10 +291,14 @@ export const WORKHUB_COORDINATION_REPLACEMENT_SCHEMA_VERSION = 2;
290
291
  export const WORKHUB_COORDINATION_STOP_SCHEMA_VERSION = 3;
291
292
  export function isWorkHubCreateDefaults(value) {
292
293
  if (!isRecord(value) ||
293
- Object.keys(value).some((key) => key !== 'model' && key !== 'permissionMode'))
294
+ Object.keys(value).some((key) => key !== 'executorId' && key !== 'model' && key !== 'permissionMode'))
294
295
  return false;
295
296
  if (value.permissionMode !== undefined && !isPermissionMode(value.permissionMode))
296
297
  return false;
298
+ if (value.executorId !== undefined && !isExecutorId(value.executorId))
299
+ return false;
300
+ if (value.executorId !== undefined && value.model !== undefined)
301
+ return false;
297
302
  if (value.model === undefined)
298
303
  return true;
299
304
  const model = value.model;
@@ -349,7 +354,7 @@ const USER_MESSAGE_SHAPE = defineObjectShape()(['type', 'id', 'turnId', 'ts', 't
349
354
  'coordinationActionId',
350
355
  'origin',
351
356
  ]);
352
- const ASSISTANT_MESSAGE_SHAPE = defineObjectShape()(['type', 'id', 'turnId', 'ts', 'text', 'modelId'], ['thinking', 'contentOrder', 'providerOptions']);
357
+ const ASSISTANT_MESSAGE_SHAPE = defineObjectShape()(['type', 'id', 'turnId', 'ts', 'text', 'modelId'], ['thinking', 'contentOrder', 'providerOptions', 'interrupted']);
353
358
  const TOOL_CALL_MESSAGE_SHAPE = defineObjectShape()(['type', 'id', 'turnId', 'ts', 'toolName', 'args'], [
354
359
  'activityKind',
355
360
  'displayName',
@@ -567,6 +572,7 @@ function decodeMessage(value, decodeToolResultContent) {
567
572
  hasMessageEnvelope(message, true) &&
568
573
  typeof message.text === 'string' &&
569
574
  typeof message.modelId === 'string' &&
575
+ (message.interrupted === undefined || message.interrupted === true) &&
570
576
  (message.providerOptions === undefined || isRecord(message.providerOptions)) &&
571
577
  (message.thinking === undefined || isAssistantThinking(message.thinking)) &&
572
578
  (message.contentOrder === undefined ||
@@ -67,6 +67,7 @@ export class ShellRunUpdateBuffer {
67
67
  }
68
68
  const CURRENT_TERMINAL_RESULT_SHAPE = defineObjectShape()(['kind', 'cwd', 'cmd', 'status', 'output'], ['exitCode', 'failureMessage', 'sandboxDenial']);
69
69
  const CURRENT_SHELL_RUN_RESULT_SHAPE = defineObjectShape()(['kind', 'ref', 'mode', 'status', 'cwd', 'cmd', 'startedAt', 'updatedAt', 'revision'], [
70
+ 'pid',
70
71
  'completedAt',
71
72
  'exitCode',
72
73
  'failureMessage',
@@ -162,6 +162,7 @@ export function isValidShellRunState(value) {
162
162
  const SHELL_RUN_SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
163
163
  const SHELL_RUN_PATCH_KEYS = new Set([
164
164
  'status',
165
+ 'pid',
165
166
  'exitCode',
166
167
  'failureMessage',
167
168
  'updatedAt',
@@ -179,6 +180,7 @@ const SHELL_RUN_RECORD_KEYS = new Set([
179
180
  'cwd',
180
181
  'command',
181
182
  'status',
183
+ 'pid',
182
184
  'startedAt',
183
185
  'updatedAt',
184
186
  'completedAt',
@@ -227,6 +229,7 @@ export function normalizeShellRunRecord(value, sessionId, shellRunId) {
227
229
  record.sessionId === sessionId &&
228
230
  record.shellRunId === shellRunId &&
229
231
  isShellRunStatus(record.status) &&
232
+ (record.pid === undefined || isPositiveInteger(record.pid)) &&
230
233
  isFiniteNumber(record.startedAt) &&
231
234
  isFiniteNumber(record.updatedAt) &&
232
235
  isPositiveInteger(record.revision) &&
@@ -349,6 +352,7 @@ function isShellRunSandboxEscalation(value, execution) {
349
352
  }
350
353
  function canonicalShellRunRecord(record) {
351
354
  return {
355
+ ...(record.pid !== undefined ? { pid: record.pid } : {}),
352
356
  shellRunId: record.shellRunId,
353
357
  sessionId: record.sessionId,
354
358
  ...(record.sourceRunId !== undefined ? { sourceRunId: record.sourceRunId } : {}),
@@ -32,9 +32,18 @@ export const WORK_BOARD_ID_MAX_CHARS = 64;
32
32
  export const WORK_BOARD_SESSION_ID_MAX_CHARS = 160;
33
33
  export const WORK_BOARD_MESSAGE_ID_MAX_CHARS = 256;
34
34
  export const WORK_BOARD_PROJECT_ID_MAX_CHARS = 160;
35
+ export const WORK_BOARD_HOST_ID_MAX_CHARS = 160;
36
+ export const WORK_BOARD_PROFILE_ID_MAX_CHARS = 160;
35
37
  export const WORK_BOARD_DEFAULT_PAGE_SIZE = 50;
36
38
  export const WORK_BOARD_PAGE_SIZE_MAX = 100;
37
39
  export const WORK_BOARD_CURSOR_MAX_CHARS = 1024;
40
+ /**
41
+ * Single-link contract: a project-scoped item owns at most one started
42
+ * Session at a time. Linking a freshly started Session replaces any previous
43
+ * link, keeping `linkedSessions` bounded instead of growing on repeated
44
+ * starts. Read paths stay tolerant of legacy arrays with more entries.
45
+ */
46
+ export const WORK_BOARD_MAX_LINKED_SESSIONS = 1;
38
47
  export const WORK_BOARD_ITEM_STATES = ['todo', 'in_progress', 'done'];
39
48
  const WORK_BOARD_SCOPE_INBOX_SHAPE = defineObjectShape()(['kind'], []);
40
49
  const WORK_BOARD_SCOPE_PROJECT_SHAPE = defineObjectShape()(['kind', 'projectId'], []);
@@ -55,7 +64,7 @@ const WORK_BOARD_ITEM_SHAPE = defineObjectShape()([
55
64
  'createdAt',
56
65
  'updatedAt',
57
66
  'archived',
58
- ], ['notes', 'archivedAt']);
67
+ ], ['notes', 'archivedAt', 'linkedSessions']);
59
68
  const WORK_BOARD_CREATE_INPUT_SHAPE = defineObjectShape()(['scope', 'title', 'creator', 'provenance'], ['notes']);
60
69
  const WORK_BOARD_UPDATE_INPUT_SHAPE = defineObjectShape()([], ['title', 'notes', 'scope', 'state']);
61
70
  const WORK_BOARD_LIST_QUERY_SHAPE = defineObjectShape()([], ['scope', 'projectIds', 'includeArchived', 'limit', 'cursor']);
@@ -236,6 +245,87 @@ export function normalizeCreateWorkBoardItemInput(input) {
236
245
  },
237
246
  };
238
247
  }
248
+ const WORK_BOARD_LINKED_SESSION_SHAPE = defineObjectShape()(['profileId', 'hostId', 'sessionId', 'linkedAt'], []);
249
+ export function normalizeWorkBoardLinkedSession(input) {
250
+ if (!isRecord(input) || !hasExactShape(input, WORK_BOARD_LINKED_SESSION_SHAPE)) {
251
+ return fail('Work Board linked Session reference is invalid');
252
+ }
253
+ const profileId = normalizeIdString(input.profileId, {
254
+ field: 'linkedSessions.profileId',
255
+ max: WORK_BOARD_PROFILE_ID_MAX_CHARS,
256
+ });
257
+ if (!profileId.ok)
258
+ return profileId;
259
+ const hostId = normalizeIdString(input.hostId, {
260
+ field: 'linkedSessions.hostId',
261
+ max: WORK_BOARD_HOST_ID_MAX_CHARS,
262
+ });
263
+ if (!hostId.ok)
264
+ return hostId;
265
+ const sessionId = normalizeIdString(input.sessionId, {
266
+ field: 'linkedSessions.sessionId',
267
+ max: WORK_BOARD_SESSION_ID_MAX_CHARS,
268
+ });
269
+ if (!sessionId.ok)
270
+ return sessionId;
271
+ const linkedAt = asSafeInteger(input.linkedAt);
272
+ if (linkedAt === null || linkedAt < 0)
273
+ return fail('linkedAt must be a non-negative integer');
274
+ return {
275
+ ok: true,
276
+ value: {
277
+ profileId: profileId.value,
278
+ hostId: hostId.value,
279
+ sessionId: sessionId.value,
280
+ linkedAt,
281
+ },
282
+ };
283
+ }
284
+ export function normalizeWorkBoardLinkedSessions(input) {
285
+ if (input === undefined)
286
+ return { ok: true, value: [] };
287
+ if (!Array.isArray(input))
288
+ return fail('linkedSessions must be an array');
289
+ const result = [];
290
+ const seen = new Set();
291
+ for (const entry of input) {
292
+ const normalized = normalizeWorkBoardLinkedSession(entry);
293
+ if (!normalized.ok)
294
+ return normalized;
295
+ const key = `${normalized.value.profileId}\u0000${normalized.value.hostId}\u0000${normalized.value.sessionId}`;
296
+ if (seen.has(key))
297
+ return fail('linkedSessions contains duplicates');
298
+ seen.add(key);
299
+ result.push(normalized.value);
300
+ }
301
+ if (result.length > WORK_BOARD_MAX_LINKED_SESSIONS) {
302
+ return fail(`linkedSessions must hold at most ${WORK_BOARD_MAX_LINKED_SESSIONS} entry (single-link contract)`);
303
+ }
304
+ return { ok: true, value: result };
305
+ }
306
+ /**
307
+ * Lenient read path for stored items: drop malformed or duplicate link entries
308
+ * instead of failing the whole item, so one corrupt reference cannot hide an
309
+ * otherwise valid board item. The strict {@link normalizeWorkBoardLinkedSessions}
310
+ * guard remains on the write path.
311
+ */
312
+ function tolerantWorkBoardLinkedSessions(value) {
313
+ if (!Array.isArray(value))
314
+ return [];
315
+ const result = [];
316
+ const seen = new Set();
317
+ for (const entry of value) {
318
+ const normalized = normalizeWorkBoardLinkedSession(entry);
319
+ if (!normalized.ok)
320
+ continue;
321
+ const key = `${normalized.value.profileId}\u0000${normalized.value.hostId}\u0000${normalized.value.sessionId}`;
322
+ if (seen.has(key))
323
+ continue;
324
+ seen.add(key);
325
+ result.push(normalized.value);
326
+ }
327
+ return result;
328
+ }
239
329
  export function normalizeUpdateWorkBoardItemInput(input) {
240
330
  if (!isRecord(input))
241
331
  return fail('Work Board update must be an object');
@@ -395,6 +485,7 @@ export function decodeWorkBoardItem(value) {
395
485
  const provenance = normalizeWorkBoardProvenance(value.provenance);
396
486
  if (!provenance.ok)
397
487
  return null;
488
+ const linkedSessions = tolerantWorkBoardLinkedSessions(value.linkedSessions);
398
489
  const createdAt = asSafeInteger(value.createdAt);
399
490
  const updatedAt = asSafeInteger(value.updatedAt);
400
491
  if (createdAt === null || updatedAt === null)
@@ -418,6 +509,7 @@ export function decodeWorkBoardItem(value) {
418
509
  archivedAt,
419
510
  creator: creator.value,
420
511
  provenance: provenance.value,
512
+ linkedSessions,
421
513
  createdAt,
422
514
  updatedAt,
423
515
  };
@@ -435,6 +527,7 @@ export function decodeWorkBoardItem(value) {
435
527
  archived: false,
436
528
  creator: creator.value,
437
529
  provenance: provenance.value,
530
+ linkedSessions,
438
531
  createdAt,
439
532
  updatedAt,
440
533
  };
@@ -6,6 +6,7 @@
6
6
  "type": "module",
7
7
  "sideEffects": false,
8
8
  "exports": {
9
+ "./executor-id": "./dist/executor-id.js",
9
10
  "./durable-tool-result-projection": "./dist/durable-tool-result-projection.js",
10
11
  "./model-projection-transition": "./dist/model-projection-transition.js",
11
12
  "./canonical-runtime-event": "./dist/canonical-runtime-event.js",