@adhdev/mesh-shared 1.0.82-rc.33 → 1.0.82-rc.35

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.
@@ -85,9 +85,43 @@ export interface MissionUpsertRequest {
85
85
  * this wire guard only checks SHAPE, not that decision.
86
86
  */
87
87
  brief?: MissionBriefWire | null
88
- }
89
-
90
- export interface MeshMissionRecordWire {
88
+ /**
89
+ * Structured-missions bundle A (design 2026-10-28 §5.1). Every key below is optional
90
+ * and ABSENT on a legacy call. `structured: true` is the caller's capability flag: the
91
+ * daemon only puts the new keys on the response (mission summary/outcome/version,
92
+ * `result`) when it is set, so an older mcp-server that never sends it keeps decoding
93
+ * byte-identical responses (mixed-version fleets, design §13.1).
94
+ */
95
+ structured?: boolean
96
+ summary?: string
97
+ outcome?: string
98
+ outcomeReason?: string
99
+ /** Mission id open items move to when this call closes the mission (default: the carry-over mission). */
100
+ carryOverTo?: string
101
+ /** Route to the mesh's auto-created carry-over mission instead of `id`/`title` (decision requests without a mission). `title` may then be empty. */
102
+ toCarryOver?: boolean
103
+ itemsAdd?: readonly Record<string, unknown>[]
104
+ itemsUpdate?: readonly Record<string, unknown>[]
105
+ }
106
+
107
+ export interface MeshMissionOpenItemCountsWire {
108
+ remaining: number
109
+ live_check: number
110
+ decision: number
111
+ evidence: number
112
+ }
113
+
114
+ /** Structured-missions fields (only with `structured: true` on the request). */
115
+ export interface MeshMissionStructuredWire {
116
+ summary?: string
117
+ outcome?: string
118
+ outcomeReason?: string
119
+ version?: number
120
+ carryOver?: boolean
121
+ openItems?: MeshMissionOpenItemCountsWire
122
+ }
123
+
124
+ export interface MeshMissionRecordWire extends MeshMissionStructuredWire {
91
125
  id: string
92
126
  meshId: string
93
127
  title: string
@@ -99,10 +133,30 @@ export interface MeshMissionRecordWire {
99
133
 
100
134
  export interface MissionUpsertResponse {
101
135
  mission: MeshMissionRecordWire
136
+ /** Structured calls only: per-item results, movedItems/movedTo, restoredItems, ownerConfirmationItemId, warnings. */
137
+ result?: Record<string, unknown>
138
+ }
139
+
140
+ const STRUCTURED_MISSION_KEYS = ['summary', 'outcome', 'outcomeReason', 'version', 'carryOver', 'openItems'] as const
141
+
142
+ function isOpenItemCountsWire(value: unknown): value is MeshMissionOpenItemCountsWire {
143
+ if (!isRecord(value) || !hasOnlyKeys(value, ['remaining', 'live_check', 'decision', 'evidence'])) return false
144
+ return (['remaining', 'live_check', 'decision', 'evidence'] as const).every((k) => isNonNegativeInt(value[k]))
145
+ }
146
+
147
+ function isStructuredMissionFields(value: Record<string, unknown>): boolean {
148
+ for (const k of ['summary', 'outcome', 'outcomeReason'] as const) {
149
+ if (value[k] !== undefined && typeof value[k] !== 'string') return false
150
+ }
151
+ if (value.version !== undefined && !isNonNegativeInt(value.version)) return false
152
+ if (value.carryOver !== undefined && typeof value.carryOver !== 'boolean') return false
153
+ if (value.openItems !== undefined && !isOpenItemCountsWire(value.openItems)) return false
154
+ return true
102
155
  }
103
156
 
104
157
  function isMeshMissionRecordWire(value: unknown): value is MeshMissionRecordWire {
105
- if (!isRecord(value) || !hasOnlyKeys(value, ['id', 'meshId', 'title', 'goal', 'status', 'brief'])) return false
158
+ if (!isRecord(value) || !hasOnlyKeys(value, ['id', 'meshId', 'title', 'goal', 'status', 'brief', ...STRUCTURED_MISSION_KEYS])) return false
159
+ if (!isStructuredMissionFields(value)) return false
106
160
  if (!isEvidenceIdentifier(value.id) || !isEvidenceIdentifier(value.meshId)) return false
107
161
  if (typeof value.title !== 'string' || typeof value.goal !== 'string') return false
108
162
  if (!isMeshMissionStatusValue(value.status)) return false
@@ -111,11 +165,24 @@ function isMeshMissionRecordWire(value: unknown): value is MeshMissionRecordWire
111
165
  }
112
166
 
113
167
  function isMissionUpsertRequest(value: unknown): value is MissionUpsertRequest {
114
- if (!isRecord(value) || !hasOnlyKeys(value, ['v', 'meshId', 'id', 'title', 'goal', 'status', 'brief'])) return false
168
+ if (!isRecord(value) || !hasOnlyKeys(value, ['v', 'meshId', 'id', 'title', 'goal', 'status', 'brief', 'structured', 'summary', 'outcome', 'outcomeReason', 'carryOverTo', 'toCarryOver', 'itemsAdd', 'itemsUpdate'])) return false
115
169
  if (value.v !== TURN_IPC_PROTOCOL_VERSION) return false
116
170
  if (!isEvidenceIdentifier(value.meshId)) return false
117
171
  if (value.id !== undefined && !isEvidenceIdentifier(value.id)) return false
118
- if (typeof value.title !== 'string' || value.title.trim().length === 0) return false
172
+ if (typeof value.title !== 'string') return false
173
+ // An empty title is allowed only when the daemon can supply one: an existing mission (id) or the carry-over mission.
174
+ if (value.title.trim().length === 0 && value.toCarryOver !== true && value.id === undefined) return false
175
+ if (value.structured !== undefined && typeof value.structured !== 'boolean') return false
176
+ if (value.toCarryOver !== undefined && typeof value.toCarryOver !== 'boolean') return false
177
+ for (const k of ['summary', 'outcome', 'outcomeReason'] as const) {
178
+ if (value[k] !== undefined && typeof value[k] !== 'string') return false
179
+ }
180
+ if (value.carryOverTo !== undefined && !isEvidenceIdentifier(value.carryOverTo)) return false
181
+ for (const k of ['itemsAdd', 'itemsUpdate'] as const) {
182
+ const list = value[k]
183
+ // Bounded only to keep the IPC message sane; per-entry shape/size is clamped (never rejected) by the daemon.
184
+ if (list !== undefined && (!Array.isArray(list) || list.length > 500 || !list.every(isRecord))) return false
185
+ }
119
186
  if (value.goal !== undefined && typeof value.goal !== 'string') return false
120
187
  if (value.status !== undefined && !isMeshMissionStatusValue(value.status)) return false
121
188
  if (value.brief !== undefined && value.brief !== null && !isMissionBriefWire(value.brief)) return false
@@ -127,7 +194,8 @@ export function decodeMissionUpsertRequest(value: unknown): MissionUpsertRequest
127
194
  }
128
195
 
129
196
  export function isMissionUpsertResponse(value: unknown): value is MissionUpsertResponse {
130
- return isRecord(value) && hasOnlyKeys(value, ['mission']) && isMeshMissionRecordWire(value.mission)
197
+ return isRecord(value) && hasOnlyKeys(value, ['mission', 'result']) && isMeshMissionRecordWire(value.mission)
198
+ && (value.result === undefined || isRecord(value.result))
131
199
  }
132
200
 
133
201
  export function decodeMissionUpsertResponse(value: unknown): MissionUpsertResponse | null {
@@ -207,6 +275,12 @@ export interface MissionListQueryRequest {
207
275
  * option except `historyIdLimit` is ignored. An older daemon rejects the key.
208
276
  */
209
277
  meshStatusView?: 'compact' | 'verbose'
278
+ /** Structured-missions capability flag (see MissionUpsertRequest.structured): rows then carry summary/outcome/openItems/carryOver/version. */
279
+ structured?: boolean
280
+ /** Structured only: attach each mission's items. */
281
+ items?: boolean
282
+ /** Structured only: attach each mission's change history (bounded tail). */
283
+ history?: boolean
210
284
  }
211
285
 
212
286
  export interface MeshMissionTaskAggregateWire {
@@ -248,6 +322,15 @@ export interface MissionListSummaryVerboseWire {
248
322
  createdAt?: string
249
323
  updatedAt?: string
250
324
  closeCandidateEmittedAt?: string | null
325
+ /** Structured lists only. */
326
+ summary?: string
327
+ outcome?: string
328
+ outcomeReason?: string
329
+ version?: number
330
+ carryOver?: boolean
331
+ openItems?: MeshMissionOpenItemCountsWire
332
+ items?: readonly Record<string, unknown>[]
333
+ history?: readonly Record<string, unknown>[]
251
334
  }
252
335
 
253
336
  /** Compact (default) mission row. `goalPreview`/`goalTruncated` present, `goal` absent. */
@@ -266,6 +349,15 @@ export interface MissionListSummarySlimWire {
266
349
  createdAt?: string
267
350
  updatedAt?: string
268
351
  closeCandidateEmittedAt?: string | null
352
+ /** Structured lists only. */
353
+ summary?: string
354
+ outcome?: string
355
+ outcomeReason?: string
356
+ version?: number
357
+ carryOver?: boolean
358
+ openItems?: MeshMissionOpenItemCountsWire
359
+ items?: readonly Record<string, unknown>[]
360
+ history?: readonly Record<string, unknown>[]
269
361
  }
270
362
 
271
363
  export type MissionListSummaryWire = MissionListSummaryVerboseWire | MissionListSummarySlimWire
@@ -286,7 +378,10 @@ export interface MissionListQueryResponse {
286
378
  }
287
379
 
288
380
  function isMissionListQueryRequest(value: unknown): value is MissionListQueryRequest {
289
- if (!isRecord(value) || !hasOnlyKeys(value, ['v', 'meshId', 'statuses', 'verbose', 'withStats', 'limit', 'historyIdLimit', 'meshStatusView'])) return false
381
+ if (!isRecord(value) || !hasOnlyKeys(value, ['v', 'meshId', 'statuses', 'verbose', 'withStats', 'limit', 'historyIdLimit', 'meshStatusView', 'structured', 'items', 'history'])) return false
382
+ for (const k of ['structured', 'items', 'history'] as const) {
383
+ if (value[k] !== undefined && typeof value[k] !== 'boolean') return false
384
+ }
290
385
  if (value.meshStatusView !== undefined && value.meshStatusView !== 'compact' && value.meshStatusView !== 'verbose') return false
291
386
  if (value.v !== TURN_IPC_PROTOCOL_VERSION) return false
292
387
  if (!isEvidenceIdentifier(value.meshId)) return false
@@ -332,7 +427,7 @@ function isMissionListSummaryWire(value: unknown): value is MissionListSummaryWi
332
427
  const hasGoal = 'goal' in value
333
428
  const hasPreview = 'goalPreview' in value && 'goalTruncated' in value
334
429
  if (hasGoal === hasPreview) return false // exactly one of the two shapes
335
- const baseKeys = ['id', 'meshId', 'title', 'status', 'tasks', 'stats', 'brief', 'createdAt', 'updatedAt', 'closeCandidateEmittedAt'] as const
430
+ const baseKeys = ['id', 'meshId', 'title', 'status', 'tasks', 'stats', 'brief', 'createdAt', 'updatedAt', 'closeCandidateEmittedAt', ...STRUCTURED_MISSION_KEYS, 'items', 'history'] as const
336
431
  const allowed = hasGoal ? [...baseKeys, 'goal'] : [...baseKeys, 'goalPreview', 'goalTruncated']
337
432
  if (!hasOnlyKeys(value, allowed)) return false
338
433
  if (!isEvidenceIdentifier(value.id) || !isEvidenceIdentifier(value.meshId)) return false
@@ -346,6 +441,10 @@ function isMissionListSummaryWire(value: unknown): value is MissionListSummaryWi
346
441
  if (value.createdAt !== undefined && typeof value.createdAt !== 'string') return false
347
442
  if (value.updatedAt !== undefined && typeof value.updatedAt !== 'string') return false
348
443
  if (value.closeCandidateEmittedAt !== undefined && value.closeCandidateEmittedAt !== null && typeof value.closeCandidateEmittedAt !== 'string') return false
444
+ if (!isStructuredMissionFields(value)) return false
445
+ for (const k of ['items', 'history'] as const) {
446
+ if (value[k] !== undefined && (!Array.isArray(value[k]) || !(value[k] as unknown[]).every(isRecord))) return false
447
+ }
349
448
  return true
350
449
  }
351
450
 
@@ -43,7 +43,18 @@ export const WORKER_TOOLS = [
43
43
  'driver_list',
44
44
  'driver_describe',
45
45
  'driver_invoke',
46
+ 'driver_scaffold',
47
+ 'driver_validate',
48
+ 'driver_export',
49
+ 'driver_install',
46
50
  'artifact_register',
51
+ 'artifact_list',
52
+ 'artifact_read',
53
+ // Contract versioning (Cut A): exact pinned-unit reads (E2), the explicit
54
+ // read claim (C1) and the authoritative head/notice pull.
55
+ 'contract_read',
56
+ 'contract_observe',
57
+ 'contract_current',
47
58
  ] as const
48
59
 
49
60
  export type WorkerTool = typeof WORKER_TOOLS[number]