@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +33 -0
- package/dist/cli-bundle/bundle-manifest.json +133 -133
- package/dist/cli-bundle/chunks/chunk-E2GCFJSU.js +35 -0
- package/dist/cli-bundle/chunks/{chunk-QKGMHGEI.js → chunk-ES25LX3D.js} +51 -51
- package/dist/cli-bundle/chunks/{chunk-TPQIBSL2.js → chunk-FRDWWB6R.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-65MHLHAA.js → chunk-ICQ3RVIY.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-IV64RJVE.js +13 -0
- package/dist/cli-bundle/chunks/{chunk-QLUORNIB.js → chunk-KBFP3E4E.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-T2ENPRXF.js → chunk-MVYLQ67M.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-5I5RWIJC.js → chunk-S4U76VZF.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-OWHNAR2B.js → chunk-UKBCRPA2.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-WRHJ3MB6.js +8 -0
- package/dist/cli-bundle/chunks/{chunk-244MI4GS.js → chunk-ZNRLJ54C.js} +60 -60
- package/dist/cli-bundle/chunks/{register-list-query-XVN2ZLI7.js → register-list-query-EMCPMICY.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-QCKAEGIJ.js → register-mutation-OJ67ABCB.js} +2 -2
- package/dist/cli-bundle/chunks/{register-operations-SDEAXE7E.js → register-operations-H2GLP7LT.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-LXVBRCJ3.js → register-setup-GLZAHLVI.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-NOOZGIXP.js → chunk-4K2II4TV.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-IBZZZGK3.js → chunk-4XNH2HM7.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-VXWATRFL.js → chunk-57XY346D.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-GQR3WH3F.js → chunk-66VGB23P.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-42S3GGZ7.js → chunk-72T6JGAE.js} +45 -45
- package/dist/cli-bundle/focused-chunks/{chunk-RAFKLNZX.js → chunk-FXDLT6FL.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-H5JZEIQV.js → chunk-IBHXMFE7.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-N7W67YIG.js → chunk-LV5N3LK5.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-2UIWOP3O.js → chunk-MMXUPDDJ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-ONYQCALA.js → chunk-P2E6LDAE.js} +6 -6
- package/dist/cli-bundle/focused-chunks/{chunk-XUQPEKRN.js → chunk-TMJDFHVD.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-OVJL6NZE.js → chunk-UYBA57GY.js} +4 -4
- package/dist/cli-bundle/main.js +2 -2
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +31 -31
- package/dist/cli-bundle/sdk-governance.js +1 -1
- package/dist/cli-bundle/sdk-graph.js +1 -1
- package/dist/cli-bundle/sdk-merge.js +18 -18
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +7 -6
- package/dist/core/extensions/manifest-schema.d.ts +20 -0
- package/dist/core/extensions/manifest-schema.js +28 -10
- package/dist/mcp/http-server.d.ts +60 -0
- package/dist/mcp/http-server.js +451 -0
- package/dist/mcp/legacy-adapter.d.ts +50 -0
- package/dist/mcp/legacy-adapter.js +64 -0
- package/dist/mcp/server.d.ts +42 -9
- package/dist/mcp/server.js +457 -58
- package/dist/sdk/agent/refusal-closure-census.d.ts +6 -2
- package/dist/sdk/agent/refusal-closure-census.js +16 -8
- package/dist/sdk/compose.d.ts +4 -1
- package/dist/sdk/compose.js +23 -36
- package/dist/sdk/extension/author-manifest.d.ts +22 -0
- package/dist/sdk/extension/author-manifest.js +93 -0
- package/dist/sdk/extension.js +6 -3
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +20 -5
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +6 -3
- package/dist/sdk/governance/health.js +7 -2
- package/dist/sdk/index.d.ts +6 -0
- package/dist/sdk/index.js +8 -2
- package/dist/sdk/mcp/authorization.d.ts +134 -0
- package/dist/sdk/mcp/authorization.js +405 -0
- package/dist/sdk/mcp/interactions.d.ts +118 -0
- package/dist/sdk/mcp/interactions.js +337 -0
- package/dist/sdk/mcp/protocol.d.ts +142 -0
- package/dist/sdk/mcp/protocol.js +174 -0
- package/dist/sdk/mcp/subscriptions.d.ts +65 -0
- package/dist/sdk/mcp/subscriptions.js +212 -0
- package/dist/sdk/mcp/tasks.d.ts +107 -0
- package/dist/sdk/mcp/tasks.js +431 -0
- package/dist/sdk/mcp/transport.d.ts +30 -0
- package/dist/sdk/mcp/transport.js +261 -0
- package/dist/sdk/merge/receipts.d.ts +16 -0
- package/dist/sdk/merge/receipts.js +9 -8
- package/dist/sdk/runtime-primitives.d.ts +1 -1
- package/dist/sdk/runtime-primitives.js +3 -3
- package/dist/sdk/test/execution.d.ts +6 -0
- package/dist/sdk/test/execution.js +32 -3
- package/docs/AGENT_PROVENANCE_ADR.md +6 -4
- package/docs/AGENT_RUNTIME_PRIMITIVES.md +6 -5
- package/docs/CLAUDE_CODE_PLUGIN.md +12 -5
- package/docs/DIAGNOSTIC_OUTPUT_CONTRACTS.md +8 -0
- package/docs/EXTENSIONS.md +3 -4
- package/docs/MCP_2026_07_28.md +138 -0
- package/docs/MCP_2026_07_28_CONFORMANCE.md +30 -0
- package/docs/MCP_REMOTE_TRANSPORT_SECURITY.md +180 -0
- package/docs/README.md +4 -0
- package/docs/RELEASING.md +3 -2
- package/docs/SDK.md +22 -1
- package/docs/SDK_AGENT_SESSION_CONTEXT.md +18 -13
- package/docs/SDK_MCP_INTERACTIONS.md +227 -0
- package/docs/TESTING.md +4 -0
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +9 -8
- package/marketplace.json +2 -2
- package/package.json +7 -5
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/scripts/finalize-build.mjs +1 -0
- package/sdk/public-surface.json +628 -2
- package/dist/cli-bundle/chunks/chunk-2F3LUFMW.js +0 -8
- package/dist/cli-bundle/chunks/chunk-6C7GIMIL.js +0 -13
- package/dist/cli-bundle/chunks/chunk-YQMYF3YD.js +0 -35
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/mcp/subscriptions
|
|
3
|
+
*
|
|
4
|
+
* Implements transport-neutral MCP 2026-07-28 subscription filtering,
|
|
5
|
+
* acknowledgment, notification correlation, and graceful closure.
|
|
6
|
+
*/
|
|
7
|
+
import { type PmMcpImplementation, type PmMcpServerCapabilities } from "./protocol.js";
|
|
8
|
+
/** Notification/result metadata key that correlates a subscription stream. */
|
|
9
|
+
export declare const PM_MCP_SUBSCRIPTION_ID_META_KEY = "io.modelcontextprotocol/subscriptionId";
|
|
10
|
+
/** JSON-RPC identifiers permitted for long-lived subscription requests. */
|
|
11
|
+
export type PmMcpSubscriptionId = string | number;
|
|
12
|
+
/** Notification families a client may opt into on one listen stream. */
|
|
13
|
+
export interface PmMcpSubscriptionFilter {
|
|
14
|
+
/** Receive notifications when the tool list changes. */
|
|
15
|
+
toolsListChanged?: boolean;
|
|
16
|
+
/** Receive notifications when the prompt list changes. */
|
|
17
|
+
promptsListChanged?: boolean;
|
|
18
|
+
/** Receive notifications when the resource list changes. */
|
|
19
|
+
resourcesListChanged?: boolean;
|
|
20
|
+
/** Receive updates for these exact resource URIs. */
|
|
21
|
+
resourceSubscriptions?: string[];
|
|
22
|
+
}
|
|
23
|
+
/** One JSON-RPC notification emitted by a subscription. */
|
|
24
|
+
export interface PmMcpSubscriptionNotification {
|
|
25
|
+
/** JSON-RPC revision. */
|
|
26
|
+
jsonrpc: "2.0";
|
|
27
|
+
/** Notification method. */
|
|
28
|
+
method: string;
|
|
29
|
+
/** Correlated notification parameters. */
|
|
30
|
+
params: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
/** Sink used by stdio and HTTP adapters to write subscription messages. */
|
|
33
|
+
export type PmMcpSubscriptionSink = (notification: PmMcpSubscriptionNotification) => void | Promise<void>;
|
|
34
|
+
/** Validate and clone one subscription notification filter. */
|
|
35
|
+
export declare function parseMcpSubscriptionFilter(value: unknown): PmMcpSubscriptionFilter;
|
|
36
|
+
/** Intersect a requested filter with notification capabilities the server advertises. */
|
|
37
|
+
export declare function resolveMcpAcknowledgedSubscriptionFilter(requested: PmMcpSubscriptionFilter, capabilities: PmMcpServerCapabilities): PmMcpSubscriptionFilter;
|
|
38
|
+
/** Manage concurrent stateless subscriptions without retaining client sessions. */
|
|
39
|
+
export declare class PmMcpSubscriptionRegistry {
|
|
40
|
+
#private;
|
|
41
|
+
/** Create a registry shared by one concrete transport process. */
|
|
42
|
+
constructor(input: {
|
|
43
|
+
capabilities: PmMcpServerCapabilities;
|
|
44
|
+
serverInfo: PmMcpImplementation;
|
|
45
|
+
});
|
|
46
|
+
/** Number of currently open request-scoped subscription streams. */
|
|
47
|
+
get size(): number;
|
|
48
|
+
/** Open a subscription and emit its required first acknowledgment. */
|
|
49
|
+
open(input: {
|
|
50
|
+
id: PmMcpSubscriptionId;
|
|
51
|
+
notifications: unknown;
|
|
52
|
+
sink: PmMcpSubscriptionSink;
|
|
53
|
+
}): Promise<PmMcpSubscriptionFilter>;
|
|
54
|
+
/** Emit one list-change notification only to streams that opted into it. */
|
|
55
|
+
emitListChanged(family: "tools" | "prompts" | "resources"): Promise<number>;
|
|
56
|
+
/** Emit a resource update only to streams that opted into the exact URI. */
|
|
57
|
+
emitResourceUpdated(uri: string): Promise<number>;
|
|
58
|
+
/** Gracefully close one subscription and return its final modern result. */
|
|
59
|
+
close(id: PmMcpSubscriptionId): Record<string, unknown> | undefined;
|
|
60
|
+
/** Gracefully close every stream and preserve stable creation order. */
|
|
61
|
+
closeAll(): Array<{
|
|
62
|
+
id: PmMcpSubscriptionId;
|
|
63
|
+
result: Record<string, unknown>;
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/mcp/subscriptions
|
|
3
|
+
*
|
|
4
|
+
* Implements transport-neutral MCP 2026-07-28 subscription filtering,
|
|
5
|
+
* acknowledgment, notification correlation, and graceful closure.
|
|
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]="cb791784-bde2-5b1d-b5df-038282816043")}catch(e){}}();
|
|
9
|
+
import { PM_MCP_ERROR_CODES, PmMcpProtocolError, attachMcpServerInfo, isMcpRecord, } from "./protocol.js";
|
|
10
|
+
/** Notification/result metadata key that correlates a subscription stream. */
|
|
11
|
+
export const PM_MCP_SUBSCRIPTION_ID_META_KEY = "io.modelcontextprotocol/subscriptionId";
|
|
12
|
+
const MAX_RESOURCE_SUBSCRIPTIONS = 256;
|
|
13
|
+
const MAX_RESOURCE_URI_BYTES = 2_048;
|
|
14
|
+
const MCP_SUBSCRIPTION_WRITE_TIMEOUT_MS = 5_000;
|
|
15
|
+
function writeSubscriptionNotification(sink, notification) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const timer = setTimeout(() => reject(new Error("MCP subscription sink write timed out")), MCP_SUBSCRIPTION_WRITE_TIMEOUT_MS);
|
|
18
|
+
timer.unref();
|
|
19
|
+
Promise.resolve()
|
|
20
|
+
.then(() => sink(notification))
|
|
21
|
+
.then(() => {
|
|
22
|
+
clearTimeout(timer);
|
|
23
|
+
resolve();
|
|
24
|
+
}, (error) => {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function validateOptionalBoolean(value, field) {
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (typeof value !== "boolean") {
|
|
34
|
+
throw new PmMcpProtocolError("Invalid MCP subscription filter", PM_MCP_ERROR_CODES.invalidParams, { field: `notifications.${field}`, expected: "boolean" });
|
|
35
|
+
}
|
|
36
|
+
return value || undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Validate and clone one subscription notification filter. */
|
|
39
|
+
export function parseMcpSubscriptionFilter(value) {
|
|
40
|
+
if (!isMcpRecord(value)) {
|
|
41
|
+
throw new PmMcpProtocolError("Invalid MCP subscription filter", PM_MCP_ERROR_CODES.invalidParams, { field: "notifications", expected: "object" });
|
|
42
|
+
}
|
|
43
|
+
const toolsListChanged = validateOptionalBoolean(value.toolsListChanged, "toolsListChanged");
|
|
44
|
+
const promptsListChanged = validateOptionalBoolean(value.promptsListChanged, "promptsListChanged");
|
|
45
|
+
const resourcesListChanged = validateOptionalBoolean(value.resourcesListChanged, "resourcesListChanged");
|
|
46
|
+
let resourceSubscriptions;
|
|
47
|
+
if (value.resourceSubscriptions !== undefined) {
|
|
48
|
+
if (!Array.isArray(value.resourceSubscriptions) ||
|
|
49
|
+
value.resourceSubscriptions.length > MAX_RESOURCE_SUBSCRIPTIONS) {
|
|
50
|
+
throw new PmMcpProtocolError("Invalid MCP resource subscriptions", PM_MCP_ERROR_CODES.invalidParams, {
|
|
51
|
+
field: "notifications.resourceSubscriptions",
|
|
52
|
+
maximum: MAX_RESOURCE_SUBSCRIPTIONS,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const unique = new Set();
|
|
56
|
+
for (const entry of value.resourceSubscriptions) {
|
|
57
|
+
if (typeof entry !== "string" ||
|
|
58
|
+
entry.trim().length === 0 ||
|
|
59
|
+
Buffer.byteLength(entry, "utf8") > MAX_RESOURCE_URI_BYTES) {
|
|
60
|
+
throw new PmMcpProtocolError("Invalid MCP resource subscription URI", PM_MCP_ERROR_CODES.invalidParams, {
|
|
61
|
+
field: "notifications.resourceSubscriptions",
|
|
62
|
+
maximumBytes: MAX_RESOURCE_URI_BYTES,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
unique.add(entry);
|
|
66
|
+
}
|
|
67
|
+
if (unique.size > 0)
|
|
68
|
+
resourceSubscriptions = [...unique].sort();
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
...(toolsListChanged ? { toolsListChanged } : {}),
|
|
72
|
+
...(promptsListChanged ? { promptsListChanged } : {}),
|
|
73
|
+
...(resourcesListChanged ? { resourcesListChanged } : {}),
|
|
74
|
+
...(resourceSubscriptions ? { resourceSubscriptions } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Intersect a requested filter with notification capabilities the server advertises. */
|
|
78
|
+
export function resolveMcpAcknowledgedSubscriptionFilter(requested, capabilities) {
|
|
79
|
+
return {
|
|
80
|
+
...(requested.toolsListChanged && capabilities.tools?.listChanged
|
|
81
|
+
? { toolsListChanged: true }
|
|
82
|
+
: {}),
|
|
83
|
+
...(requested.promptsListChanged && capabilities.prompts?.listChanged
|
|
84
|
+
? { promptsListChanged: true }
|
|
85
|
+
: {}),
|
|
86
|
+
...(requested.resourcesListChanged && capabilities.resources?.listChanged
|
|
87
|
+
? { resourcesListChanged: true }
|
|
88
|
+
: {}),
|
|
89
|
+
...(requested.resourceSubscriptions && capabilities.resources?.subscribe
|
|
90
|
+
? { resourceSubscriptions: [...requested.resourceSubscriptions] }
|
|
91
|
+
: {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** Manage concurrent stateless subscriptions without retaining client sessions. */
|
|
95
|
+
export class PmMcpSubscriptionRegistry {
|
|
96
|
+
#capabilities;
|
|
97
|
+
#records = new Map();
|
|
98
|
+
#serverInfo;
|
|
99
|
+
/** Create a registry shared by one concrete transport process. */
|
|
100
|
+
constructor(input) {
|
|
101
|
+
this.#capabilities = structuredClone(input.capabilities);
|
|
102
|
+
this.#serverInfo = { ...input.serverInfo };
|
|
103
|
+
}
|
|
104
|
+
/** Number of currently open request-scoped subscription streams. */
|
|
105
|
+
get size() {
|
|
106
|
+
return this.#records.size;
|
|
107
|
+
}
|
|
108
|
+
/** Open a subscription and emit its required first acknowledgment. */
|
|
109
|
+
async open(input) {
|
|
110
|
+
if (this.#records.has(input.id)) {
|
|
111
|
+
throw new PmMcpProtocolError("MCP subscription id is already active", PM_MCP_ERROR_CODES.invalidParams, { subscriptionId: input.id });
|
|
112
|
+
}
|
|
113
|
+
const filter = resolveMcpAcknowledgedSubscriptionFilter(parseMcpSubscriptionFilter(input.notifications), this.#capabilities);
|
|
114
|
+
const record = {
|
|
115
|
+
filter,
|
|
116
|
+
sink: input.sink,
|
|
117
|
+
writeQueue: Promise.resolve(),
|
|
118
|
+
};
|
|
119
|
+
this.#records.set(input.id, record);
|
|
120
|
+
try {
|
|
121
|
+
await this.#deliver(input.id, record, {
|
|
122
|
+
jsonrpc: "2.0",
|
|
123
|
+
method: "notifications/subscriptions/acknowledged",
|
|
124
|
+
params: {
|
|
125
|
+
_meta: { [PM_MCP_SUBSCRIPTION_ID_META_KEY]: input.id },
|
|
126
|
+
notifications: structuredClone(filter),
|
|
127
|
+
},
|
|
128
|
+
}, true);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
this.#records.delete(input.id);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
return structuredClone(filter);
|
|
135
|
+
}
|
|
136
|
+
/** Emit one list-change notification only to streams that opted into it. */
|
|
137
|
+
async emitListChanged(family) {
|
|
138
|
+
const field = `${family}ListChanged`;
|
|
139
|
+
const method = `notifications/${family}/list_changed`;
|
|
140
|
+
const writes = [];
|
|
141
|
+
for (const [id, record] of this.#records) {
|
|
142
|
+
if (record.filter[field] !== true)
|
|
143
|
+
continue;
|
|
144
|
+
writes.push(this.#deliver(id, record, {
|
|
145
|
+
jsonrpc: "2.0",
|
|
146
|
+
method,
|
|
147
|
+
params: {
|
|
148
|
+
_meta: { [PM_MCP_SUBSCRIPTION_ID_META_KEY]: id },
|
|
149
|
+
},
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
return (await Promise.all(writes)).filter(Boolean).length;
|
|
153
|
+
}
|
|
154
|
+
/** Emit a resource update only to streams that opted into the exact URI. */
|
|
155
|
+
async emitResourceUpdated(uri) {
|
|
156
|
+
const writes = [];
|
|
157
|
+
for (const [id, record] of this.#records) {
|
|
158
|
+
if (!record.filter.resourceSubscriptions?.includes(uri))
|
|
159
|
+
continue;
|
|
160
|
+
writes.push(this.#deliver(id, record, {
|
|
161
|
+
jsonrpc: "2.0",
|
|
162
|
+
method: "notifications/resources/updated",
|
|
163
|
+
params: {
|
|
164
|
+
_meta: { [PM_MCP_SUBSCRIPTION_ID_META_KEY]: id },
|
|
165
|
+
uri,
|
|
166
|
+
},
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
return (await Promise.all(writes)).filter(Boolean).length;
|
|
170
|
+
}
|
|
171
|
+
async #deliver(id, record, notification, propagateFailure = false) {
|
|
172
|
+
const pending = record.writeQueue.then(async () => {
|
|
173
|
+
if (this.#records.get(id) !== record)
|
|
174
|
+
return false;
|
|
175
|
+
try {
|
|
176
|
+
await writeSubscriptionNotification(record.sink, notification);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (this.#records.get(id) === record)
|
|
181
|
+
this.#records.delete(id);
|
|
182
|
+
if (propagateFailure)
|
|
183
|
+
throw error;
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
record.writeQueue = pending.then(() => undefined, () => undefined);
|
|
188
|
+
return pending;
|
|
189
|
+
}
|
|
190
|
+
/** Gracefully close one subscription and return its final modern result. */
|
|
191
|
+
close(id) {
|
|
192
|
+
if (!this.#records.delete(id))
|
|
193
|
+
return undefined;
|
|
194
|
+
return {
|
|
195
|
+
...attachMcpServerInfo({
|
|
196
|
+
resultType: "complete",
|
|
197
|
+
_meta: { [PM_MCP_SUBSCRIPTION_ID_META_KEY]: id },
|
|
198
|
+
}, this.#serverInfo),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/** Gracefully close every stream and preserve stable creation order. */
|
|
202
|
+
closeAll() {
|
|
203
|
+
const closed = [];
|
|
204
|
+
for (const id of this.#records.keys()) {
|
|
205
|
+
const result = this.close(id);
|
|
206
|
+
closed.push({ id, result });
|
|
207
|
+
}
|
|
208
|
+
return closed;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=subscriptions.js.map
|
|
212
|
+
//# debugId=cb791784-bde2-5b1d-b5df-038282816043
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type PmMcpRequestContext } from "./protocol.js";
|
|
2
|
+
import { type PmMcpInputRequests, type PmMcpInputResponses } from "./interactions.js";
|
|
3
|
+
/** Standard identifier used to negotiate the official MCP tasks extension. */
|
|
4
|
+
export declare const PM_MCP_TASKS_EXTENSION: "io.modelcontextprotocol/tasks";
|
|
5
|
+
/** Lifecycle states defined by the official MCP tasks extension. */
|
|
6
|
+
export declare const PM_MCP_TASK_STATUSES: readonly ["working", "input_required", "completed", "failed", "cancelled"];
|
|
7
|
+
/** Current state of a durable MCP task. */
|
|
8
|
+
export type PmMcpTaskStatus = (typeof PM_MCP_TASK_STATUSES)[number];
|
|
9
|
+
/** JSON-RPC error retained by a failed task. */
|
|
10
|
+
export interface PmMcpTaskError {
|
|
11
|
+
/** JSON-RPC or MCP error code. */
|
|
12
|
+
code: number;
|
|
13
|
+
/** Bounded human-readable failure summary. */
|
|
14
|
+
message: string;
|
|
15
|
+
/** Optional structured diagnostics. */
|
|
16
|
+
data?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
/** Common public fields returned for every task state. */
|
|
19
|
+
export interface PmMcpTask {
|
|
20
|
+
/** Stable server-minted task identifier. */
|
|
21
|
+
taskId: string;
|
|
22
|
+
/** Current extension lifecycle state. */
|
|
23
|
+
status: PmMcpTaskStatus;
|
|
24
|
+
/** Optional bounded human-readable state summary. */
|
|
25
|
+
statusMessage?: string;
|
|
26
|
+
/** ISO 8601 creation timestamp. */
|
|
27
|
+
createdAt: string;
|
|
28
|
+
/** ISO 8601 last mutation timestamp. */
|
|
29
|
+
lastUpdatedAt: string;
|
|
30
|
+
/** Milliseconds from creation before server retention may end. */
|
|
31
|
+
ttlMs: number | null;
|
|
32
|
+
/** Suggested client polling interval. */
|
|
33
|
+
pollIntervalMs?: number;
|
|
34
|
+
/** Additional forward-compatible extension fields. */
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
/** Detailed public task state returned by `tasks/get`. */
|
|
38
|
+
export type PmMcpDetailedTask = (PmMcpTask & {
|
|
39
|
+
status: "working";
|
|
40
|
+
}) | (PmMcpTask & {
|
|
41
|
+
status: "input_required";
|
|
42
|
+
inputRequests: PmMcpInputRequests;
|
|
43
|
+
}) | (PmMcpTask & {
|
|
44
|
+
status: "completed";
|
|
45
|
+
result: Record<string, unknown>;
|
|
46
|
+
}) | (PmMcpTask & {
|
|
47
|
+
status: "failed";
|
|
48
|
+
error: PmMcpTaskError;
|
|
49
|
+
}) | (PmMcpTask & {
|
|
50
|
+
status: "cancelled";
|
|
51
|
+
});
|
|
52
|
+
/** Task handle returned instead of a synchronous tool result. */
|
|
53
|
+
export type PmMcpCreateTaskResult = PmMcpTask & {
|
|
54
|
+
/** Official extension result discriminator. */
|
|
55
|
+
resultType: "task";
|
|
56
|
+
};
|
|
57
|
+
/** Construction options for one filesystem-backed task provider. */
|
|
58
|
+
export interface PmMcpTaskStoreOptions {
|
|
59
|
+
/** Tracker root whose ignored runtime area owns task records. */
|
|
60
|
+
pmRoot: string;
|
|
61
|
+
/** Lock owner retained in diagnostics. */
|
|
62
|
+
owner?: string;
|
|
63
|
+
/** Deterministic clock seam used by conformance tests. */
|
|
64
|
+
now?: () => Date;
|
|
65
|
+
/** Deterministic task-id seam used by conformance tests. */
|
|
66
|
+
taskIdFactory?: () => string;
|
|
67
|
+
/** Process identifier used for restart recovery. */
|
|
68
|
+
workerPid?: number;
|
|
69
|
+
}
|
|
70
|
+
/** Filesystem-backed provider for official MCP task methods and state changes. */
|
|
71
|
+
export declare class PmMcpTaskStore {
|
|
72
|
+
#private;
|
|
73
|
+
/** Create one task store scoped to a tracker runtime directory. */
|
|
74
|
+
constructor(options: PmMcpTaskStoreOptions);
|
|
75
|
+
/** Durably create a working task before returning its public handle. */
|
|
76
|
+
create(input: {
|
|
77
|
+
principal: string;
|
|
78
|
+
ttlMs?: number | null;
|
|
79
|
+
pollIntervalMs?: number;
|
|
80
|
+
statusMessage?: string;
|
|
81
|
+
}): Promise<PmMcpCreateTaskResult>;
|
|
82
|
+
/** Return authoritative task state, applying expiry and restart recovery. */
|
|
83
|
+
get(taskId: string, principal: string): Promise<PmMcpDetailedTask>;
|
|
84
|
+
/** Transition a nonterminal task to an MRTR input-required state. */
|
|
85
|
+
requireInput(input: {
|
|
86
|
+
taskId: string;
|
|
87
|
+
principal: string;
|
|
88
|
+
requestContext: PmMcpRequestContext;
|
|
89
|
+
inputRequests: unknown;
|
|
90
|
+
statusMessage?: string;
|
|
91
|
+
}): Promise<PmMcpDetailedTask>;
|
|
92
|
+
/** Accept outstanding task inputs, ignoring unknown and duplicate response keys. */
|
|
93
|
+
update(taskId: string, principal: string, inputResponses: unknown): Promise<{
|
|
94
|
+
acceptedKeys: string[];
|
|
95
|
+
remainingKeys: string[];
|
|
96
|
+
}>;
|
|
97
|
+
/** Retrieve and clear accepted task inputs for a resumed worker. */
|
|
98
|
+
takeInputResponses(taskId: string, principal: string): Promise<PmMcpInputResponses>;
|
|
99
|
+
/** Record one immutable successful terminal result. */
|
|
100
|
+
complete(taskId: string, principal: string, result: Record<string, unknown>): Promise<PmMcpDetailedTask>;
|
|
101
|
+
/** Record one immutable JSON-RPC terminal failure. */
|
|
102
|
+
fail(taskId: string, principal: string, error: PmMcpTaskError): Promise<PmMcpDetailedTask>;
|
|
103
|
+
/** Cooperatively cancel a nonterminal task and acknowledge terminal tasks idempotently. */
|
|
104
|
+
cancel(taskId: string, principal: string): Promise<PmMcpDetailedTask>;
|
|
105
|
+
}
|
|
106
|
+
/** Build a filesystem-backed task provider for a tracker runtime root. */
|
|
107
|
+
export declare function createMcpTaskStore(options: PmMcpTaskStoreOptions): PmMcpTaskStore;
|