@unbrained/pm-cli 2026.8.25 → 2026.8.26

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 (116) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +33 -0
  3. package/dist/cli-bundle/bundle-manifest.json +133 -133
  4. package/dist/cli-bundle/chunks/chunk-E2GCFJSU.js +35 -0
  5. package/dist/cli-bundle/chunks/{chunk-QKGMHGEI.js → chunk-ES25LX3D.js} +51 -51
  6. package/dist/cli-bundle/chunks/{chunk-TPQIBSL2.js → chunk-FRDWWB6R.js} +2 -2
  7. package/dist/cli-bundle/chunks/{chunk-65MHLHAA.js → chunk-ICQ3RVIY.js} +2 -2
  8. package/dist/cli-bundle/chunks/chunk-IV64RJVE.js +13 -0
  9. package/dist/cli-bundle/chunks/{chunk-QLUORNIB.js → chunk-KBFP3E4E.js} +2 -2
  10. package/dist/cli-bundle/chunks/{chunk-T2ENPRXF.js → chunk-MVYLQ67M.js} +2 -2
  11. package/dist/cli-bundle/chunks/{chunk-5I5RWIJC.js → chunk-S4U76VZF.js} +2 -2
  12. package/dist/cli-bundle/chunks/{chunk-OWHNAR2B.js → chunk-UKBCRPA2.js} +2 -2
  13. package/dist/cli-bundle/chunks/chunk-WRHJ3MB6.js +8 -0
  14. package/dist/cli-bundle/chunks/{chunk-244MI4GS.js → chunk-ZNRLJ54C.js} +60 -60
  15. package/dist/cli-bundle/chunks/{register-list-query-XVN2ZLI7.js → register-list-query-EMCPMICY.js} +2 -2
  16. package/dist/cli-bundle/chunks/{register-mutation-QCKAEGIJ.js → register-mutation-OJ67ABCB.js} +2 -2
  17. package/dist/cli-bundle/chunks/{register-operations-SDEAXE7E.js → register-operations-H2GLP7LT.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-setup-LXVBRCJ3.js → register-setup-GLZAHLVI.js} +2 -2
  19. package/dist/cli-bundle/focused-chunks/{chunk-NOOZGIXP.js → chunk-4K2II4TV.js} +2 -2
  20. package/dist/cli-bundle/focused-chunks/{chunk-IBZZZGK3.js → chunk-4XNH2HM7.js} +2 -2
  21. package/dist/cli-bundle/focused-chunks/{chunk-VXWATRFL.js → chunk-57XY346D.js} +2 -2
  22. package/dist/cli-bundle/focused-chunks/{chunk-GQR3WH3F.js → chunk-66VGB23P.js} +2 -2
  23. package/dist/cli-bundle/focused-chunks/{chunk-42S3GGZ7.js → chunk-72T6JGAE.js} +45 -45
  24. package/dist/cli-bundle/focused-chunks/{chunk-RAFKLNZX.js → chunk-FXDLT6FL.js} +2 -2
  25. package/dist/cli-bundle/focused-chunks/{chunk-H5JZEIQV.js → chunk-IBHXMFE7.js} +2 -2
  26. package/dist/cli-bundle/focused-chunks/{chunk-N7W67YIG.js → chunk-LV5N3LK5.js} +2 -2
  27. package/dist/cli-bundle/focused-chunks/{chunk-2UIWOP3O.js → chunk-MMXUPDDJ.js} +2 -2
  28. package/dist/cli-bundle/focused-chunks/{chunk-ONYQCALA.js → chunk-P2E6LDAE.js} +6 -6
  29. package/dist/cli-bundle/focused-chunks/{chunk-XUQPEKRN.js → chunk-TMJDFHVD.js} +2 -2
  30. package/dist/cli-bundle/focused-chunks/{chunk-OVJL6NZE.js → chunk-UYBA57GY.js} +4 -4
  31. package/dist/cli-bundle/main.js +2 -2
  32. package/dist/cli-bundle/sdk-authoring.js +1 -1
  33. package/dist/cli-bundle/sdk-contracts.js +1 -1
  34. package/dist/cli-bundle/sdk-core.js +31 -31
  35. package/dist/cli-bundle/sdk-governance.js +1 -1
  36. package/dist/cli-bundle/sdk-graph.js +1 -1
  37. package/dist/cli-bundle/sdk-merge.js +18 -18
  38. package/dist/cli-bundle/sdk-query.js +1 -1
  39. package/dist/cli-bundle/sdk-runtime.js +1 -1
  40. package/dist/cli-bundle/sdk-testing.js +1 -1
  41. package/dist/cli-bundle/sdk.js +7 -6
  42. package/dist/core/extensions/manifest-schema.d.ts +20 -0
  43. package/dist/core/extensions/manifest-schema.js +28 -10
  44. package/dist/mcp/http-server.d.ts +60 -0
  45. package/dist/mcp/http-server.js +451 -0
  46. package/dist/mcp/legacy-adapter.d.ts +50 -0
  47. package/dist/mcp/legacy-adapter.js +64 -0
  48. package/dist/mcp/server.d.ts +42 -9
  49. package/dist/mcp/server.js +457 -58
  50. package/dist/sdk/agent/refusal-closure-census.d.ts +6 -2
  51. package/dist/sdk/agent/refusal-closure-census.js +16 -8
  52. package/dist/sdk/compose.d.ts +4 -1
  53. package/dist/sdk/compose.js +23 -36
  54. package/dist/sdk/extension/author-manifest.d.ts +22 -0
  55. package/dist/sdk/extension/author-manifest.js +93 -0
  56. package/dist/sdk/extension.js +6 -3
  57. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +20 -5
  58. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +6 -3
  59. package/dist/sdk/governance/health.js +7 -2
  60. package/dist/sdk/index.d.ts +6 -0
  61. package/dist/sdk/index.js +8 -2
  62. package/dist/sdk/mcp/authorization.d.ts +134 -0
  63. package/dist/sdk/mcp/authorization.js +405 -0
  64. package/dist/sdk/mcp/interactions.d.ts +118 -0
  65. package/dist/sdk/mcp/interactions.js +337 -0
  66. package/dist/sdk/mcp/protocol.d.ts +142 -0
  67. package/dist/sdk/mcp/protocol.js +174 -0
  68. package/dist/sdk/mcp/subscriptions.d.ts +65 -0
  69. package/dist/sdk/mcp/subscriptions.js +212 -0
  70. package/dist/sdk/mcp/tasks.d.ts +107 -0
  71. package/dist/sdk/mcp/tasks.js +431 -0
  72. package/dist/sdk/mcp/transport.d.ts +30 -0
  73. package/dist/sdk/mcp/transport.js +261 -0
  74. package/dist/sdk/merge/receipts.d.ts +16 -0
  75. package/dist/sdk/merge/receipts.js +9 -8
  76. package/dist/sdk/runtime-primitives.d.ts +1 -1
  77. package/dist/sdk/runtime-primitives.js +3 -3
  78. package/dist/sdk/test/execution.d.ts +6 -0
  79. package/dist/sdk/test/execution.js +32 -3
  80. package/docs/AGENT_PROVENANCE_ADR.md +6 -4
  81. package/docs/AGENT_RUNTIME_PRIMITIVES.md +6 -5
  82. package/docs/CLAUDE_CODE_PLUGIN.md +12 -5
  83. package/docs/DIAGNOSTIC_OUTPUT_CONTRACTS.md +8 -0
  84. package/docs/EXTENSIONS.md +3 -4
  85. package/docs/MCP_2026_07_28.md +138 -0
  86. package/docs/MCP_2026_07_28_CONFORMANCE.md +30 -0
  87. package/docs/MCP_REMOTE_TRANSPORT_SECURITY.md +180 -0
  88. package/docs/README.md +4 -0
  89. package/docs/RELEASING.md +3 -2
  90. package/docs/SDK.md +22 -1
  91. package/docs/SDK_AGENT_SESSION_CONTEXT.md +18 -13
  92. package/docs/SDK_MCP_INTERACTIONS.md +227 -0
  93. package/docs/TESTING.md +4 -0
  94. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +9 -8
  95. package/marketplace.json +2 -2
  96. package/package.json +7 -5
  97. package/packages/pm-beads/package.json +1 -1
  98. package/packages/pm-calendar/package.json +1 -1
  99. package/packages/pm-command-kit/package.json +1 -1
  100. package/packages/pm-digital-twin/package.json +1 -1
  101. package/packages/pm-governance-audit/package.json +1 -1
  102. package/packages/pm-guide-shell/package.json +1 -1
  103. package/packages/pm-kanban/package.json +1 -1
  104. package/packages/pm-lifecycle-hooks/package.json +1 -1
  105. package/packages/pm-linked-test-adapters/package.json +1 -1
  106. package/packages/pm-search-advanced/package.json +1 -1
  107. package/packages/pm-templates/package.json +1 -1
  108. package/packages/pm-todos/package.json +1 -1
  109. package/packages/pm-vcs/package.json +1 -1
  110. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  111. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  112. package/scripts/finalize-build.mjs +1 -0
  113. package/sdk/public-surface.json +628 -2
  114. package/dist/cli-bundle/chunks/chunk-2F3LUFMW.js +0 -8
  115. package/dist/cli-bundle/chunks/chunk-6C7GIMIL.js +0 -13
  116. package/dist/cli-bundle/chunks/chunk-YQMYF3YD.js +0 -35
@@ -0,0 +1,431 @@
1
+ /**
2
+ * @module sdk/mcp/tasks
3
+ *
4
+ * Provides a filesystem-backed, transport-neutral implementation of the
5
+ * official `io.modelcontextprotocol/tasks` extension lifecycle.
6
+ */
7
+
8
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="1774674f-4d33-5a24-9d97-522c55b861c0")}catch(e){}}();
9
+ import { randomUUID } from "node:crypto";
10
+ import { readFile } from "node:fs/promises";
11
+ import path from "node:path";
12
+ import { acquireLock, isFileMissingError, writeFileAtomic, } from "../runtime-primitives.js";
13
+ import { PM_MCP_ERROR_CODES, PmMcpProtocolError, isMcpRecord, } from "./protocol.js";
14
+ import { parseMcpInputRequests, parseMcpInputResponses, } from "./interactions.js";
15
+ /** Standard identifier used to negotiate the official MCP tasks extension. */
16
+ export const PM_MCP_TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
17
+ /** Lifecycle states defined by the official MCP tasks extension. */
18
+ export const PM_MCP_TASK_STATUSES = [
19
+ "working",
20
+ "input_required",
21
+ "completed",
22
+ "failed",
23
+ "cancelled",
24
+ ];
25
+ const TASK_ID_PATTERN = /^mcp-task-[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
26
+ const DEFAULT_TASK_TTL_MS = 60 * 60 * 1000;
27
+ const DEFAULT_TASK_POLL_INTERVAL_MS = 1000;
28
+ const MAX_STATUS_MESSAGE_LENGTH = 1024;
29
+ const TASK_LOCK_TTL_SECONDS = 30;
30
+ const TASK_LOCK_WAIT_MS = 2000;
31
+ function taskProtocolError(message) {
32
+ return new PmMcpProtocolError(message, PM_MCP_ERROR_CODES.invalidParams, {
33
+ code: "mcp_task_not_found_or_not_authorized",
34
+ });
35
+ }
36
+ function validateTaskId(taskId) {
37
+ const normalized = taskId.trim();
38
+ if (!TASK_ID_PATTERN.test(normalized)) {
39
+ throw taskProtocolError("Unknown MCP task");
40
+ }
41
+ return normalized;
42
+ }
43
+ function parseTimestamp(value, field) {
44
+ if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
45
+ throw new PmMcpProtocolError("Corrupt MCP task record", PM_MCP_ERROR_CODES.invalidParams, { field });
46
+ }
47
+ return value;
48
+ }
49
+ function parseTaskError(value) {
50
+ if (!isMcpRecord(value) ||
51
+ typeof value.code !== "number" ||
52
+ !Number.isFinite(value.code) ||
53
+ typeof value.message !== "string") {
54
+ throw new PmMcpProtocolError("Corrupt MCP task error", PM_MCP_ERROR_CODES.invalidParams);
55
+ }
56
+ return {
57
+ code: value.code,
58
+ message: value.message.slice(0, MAX_STATUS_MESSAGE_LENGTH),
59
+ ...(isMcpRecord(value.data) ? { data: structuredClone(value.data) } : {}),
60
+ };
61
+ }
62
+ function parseTaskStatus(value) {
63
+ if (!PM_MCP_TASK_STATUSES.includes(value)) {
64
+ throw new PmMcpProtocolError("Corrupt MCP task record", PM_MCP_ERROR_CODES.invalidParams);
65
+ }
66
+ return value;
67
+ }
68
+ function parseTaskPrincipal(value) {
69
+ if (typeof value !== "string" || value.length === 0) {
70
+ throw new PmMcpProtocolError("Corrupt MCP task record", PM_MCP_ERROR_CODES.invalidParams);
71
+ }
72
+ return value;
73
+ }
74
+ function parseTaskWorkerPid(value) {
75
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
76
+ throw new PmMcpProtocolError("Corrupt MCP task record", PM_MCP_ERROR_CODES.invalidParams);
77
+ }
78
+ return value;
79
+ }
80
+ function parseTaskTtl(value) {
81
+ if (value !== null &&
82
+ (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)) {
83
+ throw new PmMcpProtocolError("Corrupt MCP task record", PM_MCP_ERROR_CODES.invalidParams);
84
+ }
85
+ return value;
86
+ }
87
+ function parseTaskPollInterval(value) {
88
+ if (value === undefined)
89
+ return undefined;
90
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
91
+ throw new PmMcpProtocolError("Corrupt MCP task record", PM_MCP_ERROR_CODES.invalidParams);
92
+ }
93
+ return value;
94
+ }
95
+ function parseConsumedInputKeys(value) {
96
+ if (!Array.isArray(value) ||
97
+ value.some((entry) => typeof entry !== "string")) {
98
+ throw new PmMcpProtocolError("Corrupt MCP task record", PM_MCP_ERROR_CODES.invalidParams);
99
+ }
100
+ return [...value];
101
+ }
102
+ function parseStoredTask(value) {
103
+ if (!isMcpRecord(value) || value.version !== 1) {
104
+ throw new PmMcpProtocolError("Corrupt MCP task record", PM_MCP_ERROR_CODES.invalidParams);
105
+ }
106
+ const taskId = typeof value.taskId === "string" ? validateTaskId(value.taskId) : "";
107
+ const status = parseTaskStatus(value.status);
108
+ const principal = parseTaskPrincipal(value.principal);
109
+ const workerPid = parseTaskWorkerPid(value.workerPid);
110
+ const ttlMs = parseTaskTtl(value.ttlMs);
111
+ const pollIntervalMs = parseTaskPollInterval(value.pollIntervalMs);
112
+ const parsed = {
113
+ version: 1,
114
+ taskId,
115
+ status,
116
+ principal,
117
+ workerPid,
118
+ createdAt: parseTimestamp(value.createdAt, "createdAt"),
119
+ lastUpdatedAt: parseTimestamp(value.lastUpdatedAt, "lastUpdatedAt"),
120
+ ttlMs,
121
+ consumedInputKeys: parseConsumedInputKeys(value.consumedInputKeys),
122
+ ...(typeof value.statusMessage === "string"
123
+ ? {
124
+ statusMessage: value.statusMessage.slice(0, MAX_STATUS_MESSAGE_LENGTH),
125
+ }
126
+ : {}),
127
+ ...(typeof pollIntervalMs === "number" ? { pollIntervalMs } : {}),
128
+ };
129
+ if (isMcpRecord(value.inputRequests)) {
130
+ parsed.inputRequests = structuredClone(value.inputRequests);
131
+ }
132
+ if (isMcpRecord(value.inputResponses)) {
133
+ parsed.inputResponses = structuredClone(value.inputResponses);
134
+ }
135
+ if (isMcpRecord(value.result))
136
+ parsed.result = structuredClone(value.result);
137
+ if (value.error !== undefined)
138
+ parsed.error = parseTaskError(value.error);
139
+ if ((parsed.status === "input_required" && !parsed.inputRequests) ||
140
+ (parsed.status === "completed" && !parsed.result) ||
141
+ (parsed.status === "failed" && !parsed.error)) {
142
+ throw new PmMcpProtocolError("Corrupt MCP task status payload", PM_MCP_ERROR_CODES.invalidParams, { status: parsed.status });
143
+ }
144
+ return parsed;
145
+ }
146
+ function publicTask(record) {
147
+ const base = {
148
+ taskId: record.taskId,
149
+ status: record.status,
150
+ createdAt: record.createdAt,
151
+ lastUpdatedAt: record.lastUpdatedAt,
152
+ ttlMs: record.ttlMs,
153
+ ...(record.statusMessage ? { statusMessage: record.statusMessage } : {}),
154
+ ...(record.pollIntervalMs === undefined
155
+ ? {}
156
+ : { pollIntervalMs: record.pollIntervalMs }),
157
+ };
158
+ if (record.status === "input_required") {
159
+ return {
160
+ ...base,
161
+ status: "input_required",
162
+ inputRequests: structuredClone(record.inputRequests),
163
+ };
164
+ }
165
+ if (record.status === "completed") {
166
+ return {
167
+ ...base,
168
+ status: "completed",
169
+ result: structuredClone(record.result),
170
+ };
171
+ }
172
+ if (record.status === "failed") {
173
+ return {
174
+ ...base,
175
+ status: "failed",
176
+ error: structuredClone(record.error),
177
+ };
178
+ }
179
+ return record.status === "cancelled"
180
+ ? { ...base, status: "cancelled" }
181
+ : { ...base, status: "working" };
182
+ }
183
+ function isTerminalTask(status) {
184
+ return (status === "completed" || status === "failed" || status === "cancelled");
185
+ }
186
+ function isProcessAlive(pid) {
187
+ try {
188
+ process.kill(pid, 0);
189
+ return true;
190
+ }
191
+ catch (error) {
192
+ return !(typeof error === "object" &&
193
+ error !== null &&
194
+ "code" in error &&
195
+ error.code === "ESRCH");
196
+ }
197
+ }
198
+ /** Filesystem-backed provider for official MCP task methods and state changes. */
199
+ export class PmMcpTaskStore {
200
+ #pmRoot;
201
+ #owner;
202
+ #now;
203
+ #taskIdFactory;
204
+ #workerPid;
205
+ /** Create one task store scoped to a tracker runtime directory. */
206
+ constructor(options) {
207
+ this.#pmRoot = path.resolve(options.pmRoot);
208
+ this.#owner = options.owner?.trim() || "pm-mcp";
209
+ this.#now = options.now ?? (() => new Date());
210
+ this.#taskIdFactory =
211
+ options.taskIdFactory ?? (() => `mcp-task-${randomUUID()}`);
212
+ this.#workerPid = options.workerPid ?? process.pid;
213
+ }
214
+ #taskPath(taskId) {
215
+ return path.join(this.#pmRoot, "runtime", "mcp-tasks", "records", `${validateTaskId(taskId)}.json`);
216
+ }
217
+ async #read(taskId) {
218
+ try {
219
+ return parseStoredTask(JSON.parse(await readFile(this.#taskPath(taskId), "utf8")));
220
+ }
221
+ catch (error) {
222
+ if (isFileMissingError(error) || error instanceof SyntaxError) {
223
+ throw taskProtocolError("Unknown MCP task");
224
+ }
225
+ throw error;
226
+ }
227
+ }
228
+ async #write(record) {
229
+ await writeFileAtomic(this.#taskPath(record.taskId), `${JSON.stringify(record, null, 2)}\n`);
230
+ }
231
+ async #mutate(taskId, principal, mutation) {
232
+ const normalizedId = validateTaskId(taskId);
233
+ const release = await acquireLock(this.#pmRoot, normalizedId, TASK_LOCK_TTL_SECONDS, this.#owner, false, false, TASK_LOCK_WAIT_MS);
234
+ try {
235
+ const current = await this.#read(normalizedId);
236
+ if (current.principal !== principal)
237
+ throw taskProtocolError("Unknown MCP task");
238
+ const next = mutation(current);
239
+ await this.#write(next);
240
+ return next;
241
+ }
242
+ finally {
243
+ await release();
244
+ }
245
+ }
246
+ /** Durably create a working task before returning its public handle. */
247
+ async create(input) {
248
+ const taskId = validateTaskId(this.#taskIdFactory());
249
+ const principal = input.principal.trim();
250
+ const ttlMs = input.ttlMs === undefined ? DEFAULT_TASK_TTL_MS : input.ttlMs;
251
+ const pollIntervalMs = input.pollIntervalMs ?? DEFAULT_TASK_POLL_INTERVAL_MS;
252
+ if (principal.length === 0 ||
253
+ (ttlMs !== null && (!Number.isSafeInteger(ttlMs) || ttlMs < 0)) ||
254
+ !Number.isSafeInteger(pollIntervalMs) ||
255
+ pollIntervalMs < 0) {
256
+ throw new PmMcpProtocolError("Invalid MCP task creation options", PM_MCP_ERROR_CODES.invalidParams);
257
+ }
258
+ const timestamp = this.#now().toISOString();
259
+ const record = {
260
+ version: 1,
261
+ taskId,
262
+ status: "working",
263
+ createdAt: timestamp,
264
+ lastUpdatedAt: timestamp,
265
+ ttlMs,
266
+ pollIntervalMs,
267
+ principal,
268
+ workerPid: this.#workerPid,
269
+ consumedInputKeys: [],
270
+ ...(input.statusMessage?.trim()
271
+ ? {
272
+ statusMessage: input.statusMessage
273
+ .trim()
274
+ .slice(0, MAX_STATUS_MESSAGE_LENGTH),
275
+ }
276
+ : {}),
277
+ };
278
+ await this.#write(record);
279
+ return { ...publicTask(record), resultType: "task" };
280
+ }
281
+ /** Return authoritative task state, applying expiry and restart recovery. */
282
+ async get(taskId, principal) {
283
+ return publicTask(await this.#mutate(taskId, principal, (record) => {
284
+ if (isTerminalTask(record.status))
285
+ return record;
286
+ const nowMs = this.#now().getTime();
287
+ const expired = record.ttlMs !== null &&
288
+ Date.parse(record.createdAt) + record.ttlMs <= nowMs;
289
+ const unavailableWorker = record.workerPid !== this.#workerPid &&
290
+ !isProcessAlive(record.workerPid);
291
+ if (!expired && !unavailableWorker)
292
+ return record;
293
+ return {
294
+ ...record,
295
+ status: "failed",
296
+ statusMessage: expired
297
+ ? "Task retention expired before completion."
298
+ : "Task worker ended before recording a terminal result.",
299
+ lastUpdatedAt: this.#now().toISOString(),
300
+ error: {
301
+ code: -32603,
302
+ message: expired
303
+ ? "MCP task expired"
304
+ : "MCP task worker unavailable after restart",
305
+ data: {
306
+ recoverable: false,
307
+ createNewTask: true,
308
+ },
309
+ },
310
+ inputRequests: undefined,
311
+ };
312
+ }));
313
+ }
314
+ /** Transition a nonterminal task to an MRTR input-required state. */
315
+ async requireInput(input) {
316
+ const requests = parseMcpInputRequests(input.inputRequests, input.requestContext);
317
+ return publicTask(await this.#mutate(input.taskId, input.principal, (record) => {
318
+ if (isTerminalTask(record.status))
319
+ return record;
320
+ const reused = Object.keys(requests).filter((key) => record.consumedInputKeys.includes(key));
321
+ if (reused.length > 0) {
322
+ throw new PmMcpProtocolError("MCP task input request keys must be unique for the task lifetime", PM_MCP_ERROR_CODES.invalidParams, { reused });
323
+ }
324
+ return {
325
+ ...record,
326
+ status: "input_required",
327
+ statusMessage: input.statusMessage
328
+ ?.trim()
329
+ .slice(0, MAX_STATUS_MESSAGE_LENGTH),
330
+ lastUpdatedAt: this.#now().toISOString(),
331
+ inputRequests: requests,
332
+ };
333
+ }));
334
+ }
335
+ /** Accept outstanding task inputs, ignoring unknown and duplicate response keys. */
336
+ async update(taskId, principal, inputResponses) {
337
+ const responses = parseMcpInputResponses(inputResponses);
338
+ const acceptedKeys = [];
339
+ let remainingKeys = [];
340
+ await this.#mutate(taskId, principal, (record) => {
341
+ if (record.status !== "input_required" || !record.inputRequests) {
342
+ return record;
343
+ }
344
+ const outstanding = { ...record.inputRequests };
345
+ const accepted = record.inputResponses
346
+ ? { ...record.inputResponses }
347
+ : {};
348
+ for (const [key, response] of Object.entries(responses)) {
349
+ if (!Object.prototype.hasOwnProperty.call(outstanding, key))
350
+ continue;
351
+ accepted[key] = response;
352
+ delete outstanding[key];
353
+ acceptedKeys.push(key);
354
+ }
355
+ remainingKeys = Object.keys(outstanding).sort();
356
+ if (acceptedKeys.length === 0)
357
+ return record;
358
+ return {
359
+ ...record,
360
+ status: remainingKeys.length === 0 ? "working" : "input_required",
361
+ lastUpdatedAt: this.#now().toISOString(),
362
+ inputRequests: remainingKeys.length === 0 ? undefined : outstanding,
363
+ inputResponses: accepted,
364
+ consumedInputKeys: [
365
+ ...new Set([...record.consumedInputKeys, ...acceptedKeys]),
366
+ ].sort(),
367
+ };
368
+ });
369
+ return { acceptedKeys: acceptedKeys.sort(), remainingKeys };
370
+ }
371
+ /** Retrieve and clear accepted task inputs for a resumed worker. */
372
+ async takeInputResponses(taskId, principal) {
373
+ let responses = {};
374
+ await this.#mutate(taskId, principal, (record) => {
375
+ responses = structuredClone(record.inputResponses ?? {});
376
+ if (Object.keys(responses).length === 0)
377
+ return record;
378
+ return { ...record, inputResponses: undefined };
379
+ });
380
+ return responses;
381
+ }
382
+ /** Record one immutable successful terminal result. */
383
+ async complete(taskId, principal, result) {
384
+ return publicTask(await this.#mutate(taskId, principal, (record) => isTerminalTask(record.status)
385
+ ? record
386
+ : {
387
+ ...record,
388
+ status: "completed",
389
+ statusMessage: "Task completed.",
390
+ lastUpdatedAt: this.#now().toISOString(),
391
+ result: structuredClone(result),
392
+ inputRequests: undefined,
393
+ error: undefined,
394
+ }));
395
+ }
396
+ /** Record one immutable JSON-RPC terminal failure. */
397
+ async fail(taskId, principal, error) {
398
+ const boundedError = parseTaskError(error);
399
+ return publicTask(await this.#mutate(taskId, principal, (record) => isTerminalTask(record.status)
400
+ ? record
401
+ : {
402
+ ...record,
403
+ status: "failed",
404
+ statusMessage: boundedError.message,
405
+ lastUpdatedAt: this.#now().toISOString(),
406
+ error: boundedError,
407
+ inputRequests: undefined,
408
+ result: undefined,
409
+ }));
410
+ }
411
+ /** Cooperatively cancel a nonterminal task and acknowledge terminal tasks idempotently. */
412
+ async cancel(taskId, principal) {
413
+ return publicTask(await this.#mutate(taskId, principal, (record) => isTerminalTask(record.status)
414
+ ? record
415
+ : {
416
+ ...record,
417
+ status: "cancelled",
418
+ statusMessage: "Task cancelled by the client.",
419
+ lastUpdatedAt: this.#now().toISOString(),
420
+ inputRequests: undefined,
421
+ result: undefined,
422
+ error: undefined,
423
+ }));
424
+ }
425
+ }
426
+ /** Build a filesystem-backed task provider for a tracker runtime root. */
427
+ export function createMcpTaskStore(options) {
428
+ return new PmMcpTaskStore(options);
429
+ }
430
+ //# sourceMappingURL=tasks.js.map
431
+ //# debugId=1774674f-4d33-5a24-9d97-522c55b861c0
@@ -0,0 +1,30 @@
1
+ /** Case-insensitive HTTP header input accepted by public transport helpers. */
2
+ export type PmMcpHttpHeaders = Record<string, string | readonly string[] | undefined>;
3
+ /** One statically reachable `x-mcp-header` annotation. */
4
+ export interface PmMcpHeaderAnnotation {
5
+ /** Suffix used to construct `Mcp-Param-{name}`. */
6
+ name: string;
7
+ /** Exact property path in tool-call arguments. */
8
+ path: string[];
9
+ /** Primitive schema type permitted by the specification. */
10
+ type: "boolean" | "integer" | "string";
11
+ }
12
+ /** Encode a name or primitive parameter into the protocol's safe header form. */
13
+ export declare function encodeMcpHttpHeaderValue(value: string | number | boolean): string;
14
+ /** Decode and validate a mirrored MCP header value. */
15
+ export declare function decodeMcpHttpHeaderValue(value: string, header: string): string;
16
+ /** Parse valid statically reachable custom-header annotations from a tool schema. */
17
+ export declare function collectMcpHeaderAnnotations(schema: unknown): PmMcpHeaderAnnotation[];
18
+ /** Build required Streamable HTTP headers for one modern client request. */
19
+ export declare function buildMcpHttpRequestHeaders(input: {
20
+ request: Record<string, unknown>;
21
+ toolSchema?: unknown;
22
+ }): Record<string, string>;
23
+ /** Validate required Streamable HTTP headers against the JSON-RPC body. */
24
+ export declare function validateMcpHttpRequestHeaders(input: {
25
+ headers: PmMcpHttpHeaders;
26
+ request: Record<string, unknown>;
27
+ toolSchema?: unknown;
28
+ }): void;
29
+ /** Required protocol version exposed for clients constructing headers manually. */
30
+ export declare const PM_MCP_HTTP_PROTOCOL_VERSION: "2026-07-28";