@openscout/protocol 0.2.63 → 0.2.82
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/LICENSE +202 -0
- package/README.md +103 -52
- package/dist/a2a.d.ts +224 -0
- package/dist/a2a.d.ts.map +1 -0
- package/dist/a2a.js +225 -0
- package/dist/activity.d.ts +70 -0
- package/dist/activity.d.ts.map +1 -0
- package/dist/activity.js +1 -0
- package/dist/actors.d.ts +7 -3
- package/dist/actors.d.ts.map +1 -0
- package/dist/actors.js +15 -1
- package/dist/agent-broker-feed.d.ts +84 -0
- package/dist/agent-broker-feed.d.ts.map +1 -0
- package/dist/agent-broker-feed.js +1 -0
- package/dist/agent-identity.d.ts +23 -0
- package/dist/agent-identity.d.ts.map +1 -0
- package/dist/agent-identity.js +122 -8
- package/dist/agent-runs.d.ts +191 -0
- package/dist/agent-runs.d.ts.map +1 -0
- package/dist/agent-runs.js +365 -0
- package/dist/assigned-roles.d.ts +205 -0
- package/dist/assigned-roles.d.ts.map +1 -0
- package/dist/assigned-roles.js +228 -0
- package/dist/broker-routes.d.ts +40 -0
- package/dist/broker-routes.d.ts.map +1 -0
- package/dist/broker-routes.js +56 -0
- package/dist/budget.d.ts +65 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +19 -0
- package/dist/capability-matrix.d.ts +230 -0
- package/dist/capability-matrix.d.ts.map +1 -0
- package/dist/capability-matrix.js +304 -0
- package/dist/channel-identity.d.ts +40 -0
- package/dist/channel-identity.d.ts.map +1 -0
- package/dist/channel-identity.js +108 -0
- package/dist/collaboration.d.ts +45 -16
- package/dist/collaboration.d.ts.map +1 -0
- package/dist/collaboration.js +39 -23
- package/dist/common.d.ts +4 -3
- package/dist/common.d.ts.map +1 -0
- package/dist/conversations.d.ts +1 -0
- package/dist/conversations.d.ts.map +1 -0
- package/dist/cursor-transport.d.ts +22 -0
- package/dist/cursor-transport.d.ts.map +1 -0
- package/dist/cursor-transport.js +10 -0
- package/dist/debug-attach.d.ts +29 -0
- package/dist/debug-attach.d.ts.map +1 -0
- package/dist/debug-attach.js +6 -0
- package/dist/deliveries.d.ts +16 -2
- package/dist/deliveries.d.ts.map +1 -0
- package/dist/durable-actions.d.ts +94 -0
- package/dist/durable-actions.d.ts.map +1 -0
- package/dist/durable-actions.js +1 -0
- package/dist/events.d.ts +27 -1
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +14 -1
- package/dist/harness-feature-support.d.ts +76 -0
- package/dist/harness-feature-support.d.ts.map +1 -0
- package/dist/harness-feature-support.js +29 -0
- package/dist/host-integrations.d.ts +22 -0
- package/dist/host-integrations.d.ts.map +1 -0
- package/dist/host-integrations.js +64 -0
- package/dist/inbox.d.ts +50 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +9 -0
- package/dist/index.d.ts +36 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -2
- package/dist/invocations.d.ts +121 -0
- package/dist/invocations.d.ts.map +1 -0
- package/dist/invocations.js +201 -1
- package/dist/mesh.d.ts +98 -0
- package/dist/mesh.d.ts.map +1 -0
- package/dist/mesh.js +18 -1
- package/dist/messages.d.ts +3 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/native-interactions.d.ts +30 -0
- package/dist/native-interactions.d.ts.map +1 -0
- package/dist/native-interactions.js +51 -0
- package/dist/observed-status.d.ts +33 -0
- package/dist/observed-status.d.ts.map +1 -0
- package/dist/observed-status.js +6 -0
- package/dist/orchestrator-prompt.d.ts +10 -0
- package/dist/orchestrator-prompt.d.ts.map +1 -0
- package/dist/orchestrator-prompt.js +36 -0
- package/dist/permission-policy.d.ts +5 -0
- package/dist/permission-policy.d.ts.map +1 -0
- package/dist/permission-policy.js +15 -0
- package/dist/provisional-agent-names.d.ts +30 -0
- package/dist/provisional-agent-names.d.ts.map +1 -0
- package/dist/provisional-agent-names.js +224 -0
- package/dist/read-receipts.d.ts +12 -0
- package/dist/read-receipts.d.ts.map +1 -0
- package/dist/read-receipts.js +1 -0
- package/dist/relay-agent-card.d.ts +10 -0
- package/dist/relay-agent-card.d.ts.map +1 -0
- package/dist/rendezvous.d.ts +97 -0
- package/dist/rendezvous.d.ts.map +1 -0
- package/dist/rendezvous.js +35 -0
- package/dist/reserved-vocabulary.d.ts +21 -0
- package/dist/reserved-vocabulary.d.ts.map +1 -0
- package/dist/reserved-vocabulary.js +101 -0
- package/dist/route-aliases.d.ts +112 -0
- package/dist/route-aliases.d.ts.map +1 -0
- package/dist/route-aliases.js +1 -0
- package/dist/runtime-catalog-contract.d.ts +39 -0
- package/dist/runtime-catalog-contract.d.ts.map +1 -0
- package/dist/runtime-catalog-contract.js +17 -0
- package/dist/runtime-catalog.d.ts +11 -0
- package/dist/runtime-catalog.d.ts.map +1 -0
- package/dist/runtime-catalog.generated.d.ts +230 -0
- package/dist/runtime-catalog.generated.d.ts.map +1 -0
- package/dist/runtime-catalog.generated.js +299 -0
- package/dist/runtime-catalog.js +192 -0
- package/dist/runtime-execution.d.ts +161 -0
- package/dist/runtime-execution.d.ts.map +1 -0
- package/dist/runtime-execution.js +327 -0
- package/dist/runtime-profiles.d.ts +12 -0
- package/dist/runtime-profiles.d.ts.map +1 -0
- package/dist/runtime-profiles.js +20 -0
- package/dist/scout-agent-card.d.ts +10 -0
- package/dist/scout-agent-card.d.ts.map +1 -0
- package/dist/scout-composer.d.ts +27 -0
- package/dist/scout-composer.d.ts.map +1 -0
- package/dist/scout-composer.js +192 -0
- package/dist/scout-delivery.d.ts +101 -4
- package/dist/scout-delivery.d.ts.map +1 -0
- package/dist/scout-dispatch.d.ts +44 -3
- package/dist/scout-dispatch.d.ts.map +1 -0
- package/dist/scout-reply-context.d.ts +14 -0
- package/dist/scout-reply-context.d.ts.map +1 -0
- package/dist/scout-reply-context.js +1 -0
- package/dist/session-display-state.d.ts +132 -0
- package/dist/session-display-state.d.ts.map +1 -0
- package/dist/session-display-state.js +94 -0
- package/dist/terminal-host-topology.d.ts +119 -0
- package/dist/terminal-host-topology.d.ts.map +1 -0
- package/dist/terminal-host-topology.js +21 -0
- package/dist/terminal-sessions.d.ts +142 -0
- package/dist/terminal-sessions.d.ts.map +1 -0
- package/dist/terminal-sessions.js +81 -0
- package/dist/terminal-surface-id.d.ts +73 -0
- package/dist/terminal-surface-id.d.ts.map +1 -0
- package/dist/terminal-surface-id.js +162 -0
- package/dist/terminal-workspaces.d.ts +199 -0
- package/dist/terminal-workspaces.d.ts.map +1 -0
- package/dist/terminal-workspaces.js +287 -0
- package/dist/thread-events.d.ts +1 -0
- package/dist/thread-events.d.ts.map +1 -0
- package/dist/time.d.ts +16 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +33 -0
- package/dist/transports.d.ts +1 -0
- package/dist/transports.d.ts.map +1 -0
- package/package.json +37 -9
- package/src/a2a.ts +471 -0
- package/src/activity.ts +113 -0
- package/src/actors.ts +42 -8
- package/src/agent-broker-feed.ts +102 -0
- package/src/agent-identity.ts +143 -8
- package/src/agent-runs.ts +675 -0
- package/src/assigned-roles.ts +437 -0
- package/src/broker-routes.ts +62 -0
- package/src/budget.ts +88 -0
- package/src/capability-matrix.ts +648 -0
- package/src/channel-identity.ts +147 -0
- package/src/collaboration.ts +78 -48
- package/src/common.ts +24 -5
- package/src/cursor-transport.ts +34 -0
- package/src/debug-attach.ts +38 -0
- package/src/deliveries.ts +16 -1
- package/src/durable-actions.ts +125 -0
- package/src/events.ts +31 -0
- package/src/harness-feature-support.ts +116 -0
- package/src/host-integrations.ts +104 -0
- package/src/inbox.ts +68 -0
- package/src/index.ts +35 -2
- package/src/invocations.ts +353 -0
- package/src/mesh.ts +130 -0
- package/src/messages.ts +2 -0
- package/src/native-interactions.ts +100 -0
- package/src/observed-status.ts +90 -0
- package/src/orchestrator-prompt.ts +47 -0
- package/src/permission-policy.ts +21 -0
- package/src/provisional-agent-names.ts +272 -0
- package/src/read-receipts.ts +12 -0
- package/src/relay-agent-card.ts +73 -63
- package/src/rendezvous.ts +154 -0
- package/src/reserved-vocabulary.ts +135 -0
- package/src/route-aliases.ts +130 -0
- package/src/runtime-catalog-contract.ts +60 -0
- package/src/runtime-catalog.generated.ts +300 -0
- package/src/runtime-catalog.md +21 -0
- package/src/runtime-catalog.ts +193 -0
- package/src/runtime-catalog.v1.json +81 -0
- package/src/runtime-execution.ts +545 -0
- package/src/runtime-profiles.ts +37 -0
- package/src/scout-agent-card.ts +10 -0
- package/src/scout-composer.ts +249 -0
- package/src/scout-delivery.ts +117 -4
- package/src/scout-dispatch.ts +34 -2
- package/src/scout-reply-context.ts +15 -0
- package/src/session-display-state.ts +202 -0
- package/src/terminal-host-topology.ts +132 -0
- package/src/terminal-sessions.ts +187 -0
- package/src/terminal-surface-id.ts +189 -0
- package/src/terminal-workspaces.ts +455 -0
- package/src/time.ts +49 -0
- package/dist/agent-address.d.ts +0 -1
- package/dist/agent-address.js +0 -1
- package/dist/agent-selectors.d.ts +0 -1
- package/dist/agent-selectors.js +0 -1
- package/src/agent-address.ts +0 -1
- package/src/agent-identity.test.ts +0 -435
- package/src/agent-selectors.ts +0 -1
- package/src/scout-agent-card.test.ts +0 -54
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
export function makeMcpToolCapabilityId(serverId, toolName) {
|
|
2
|
+
return `cap:mcp:${encodeURIComponent(serverId)}:tool:${encodeURIComponent(toolName)}`;
|
|
3
|
+
}
|
|
4
|
+
export function makeModelCapabilityId(providerId, modelId) {
|
|
5
|
+
return `cap:model:${encodeURIComponent(providerId)}:${encodeURIComponent(modelId)}`;
|
|
6
|
+
}
|
|
7
|
+
export function inferMcpToolEffects(annotations) {
|
|
8
|
+
const effects = [];
|
|
9
|
+
if (annotations?.readOnlyHint === true) {
|
|
10
|
+
effects.push("read");
|
|
11
|
+
}
|
|
12
|
+
if (annotations?.destructiveHint === true) {
|
|
13
|
+
effects.push("write");
|
|
14
|
+
}
|
|
15
|
+
if (annotations?.openWorldHint === true) {
|
|
16
|
+
effects.push("network");
|
|
17
|
+
}
|
|
18
|
+
return effects.length > 0 ? effects : ["unknown"];
|
|
19
|
+
}
|
|
20
|
+
export function normalizeMcpToolsToScoutCapabilities(options) {
|
|
21
|
+
const capturedAt = options.capturedAt ?? Date.now();
|
|
22
|
+
const tools = getMcpToolDefinitions(options.tools);
|
|
23
|
+
const evidence = buildMcpDiscoveryEvidence(options, capturedAt);
|
|
24
|
+
return tools
|
|
25
|
+
.filter((tool) => typeof tool.name === "string" && tool.name.trim().length > 0)
|
|
26
|
+
.map((tool) => {
|
|
27
|
+
const toolName = tool.name.trim();
|
|
28
|
+
const annotations = tool.annotations;
|
|
29
|
+
const effects = inferMcpToolEffects(annotations);
|
|
30
|
+
const methodHints = buildMcpMethodHints(annotations);
|
|
31
|
+
const readinessEvidence = [
|
|
32
|
+
evidence,
|
|
33
|
+
...(options.readiness?.evidence ?? []),
|
|
34
|
+
];
|
|
35
|
+
return {
|
|
36
|
+
id: makeMcpToolCapabilityId(options.serverId, toolName),
|
|
37
|
+
name: `mcp.${options.serverId}.${toolName}`,
|
|
38
|
+
displayName: tool.title ?? annotations?.title ?? toolName,
|
|
39
|
+
description: tool.description,
|
|
40
|
+
provider: "mcp",
|
|
41
|
+
providerId: options.serverId,
|
|
42
|
+
methods: [{
|
|
43
|
+
name: "call",
|
|
44
|
+
displayName: tool.title ?? annotations?.title ?? toolName,
|
|
45
|
+
description: tool.description,
|
|
46
|
+
inputSchema: tool.inputSchema,
|
|
47
|
+
outputSchema: tool.outputSchema,
|
|
48
|
+
effects,
|
|
49
|
+
idempotent: inferMcpIdempotence(annotations),
|
|
50
|
+
requiresApproval: annotations?.destructiveHint === true ? true : undefined,
|
|
51
|
+
hints: methodHints,
|
|
52
|
+
metadata: {
|
|
53
|
+
mcpToolName: toolName,
|
|
54
|
+
},
|
|
55
|
+
}],
|
|
56
|
+
scope: options.scope,
|
|
57
|
+
readiness: {
|
|
58
|
+
state: "unknown",
|
|
59
|
+
detail: "Discovered from MCP tools/list; runtime readiness has not been probed.",
|
|
60
|
+
checkedAt: capturedAt,
|
|
61
|
+
...options.readiness,
|
|
62
|
+
evidence: readinessEvidence,
|
|
63
|
+
},
|
|
64
|
+
enforcement: {
|
|
65
|
+
level: "mcp_server",
|
|
66
|
+
trust: "advisory",
|
|
67
|
+
reason: "MCP tool metadata and annotations are advisory; Scout policy still decides availability.",
|
|
68
|
+
},
|
|
69
|
+
provenance: {
|
|
70
|
+
sourceKind: "mcp_server",
|
|
71
|
+
sourceId: options.serverId,
|
|
72
|
+
sourceName: options.serverName,
|
|
73
|
+
capturedAt,
|
|
74
|
+
protocol: "mcp",
|
|
75
|
+
protocolVersion: options.protocolVersion,
|
|
76
|
+
evidence: [evidence],
|
|
77
|
+
},
|
|
78
|
+
enabled: options.enabled ?? true,
|
|
79
|
+
metadata: {
|
|
80
|
+
mcpServerCapabilities: options.serverCapabilities,
|
|
81
|
+
mcpTool: {
|
|
82
|
+
name: toolName,
|
|
83
|
+
icons: tool.icons,
|
|
84
|
+
meta: tool._meta,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export function buildMcpCapabilityMatrixSnapshot(options) {
|
|
91
|
+
const generatedAt = options.capturedAt ?? Date.now();
|
|
92
|
+
const capabilities = normalizeMcpToolsToScoutCapabilities({
|
|
93
|
+
...options,
|
|
94
|
+
capturedAt: generatedAt,
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
generatedAt,
|
|
98
|
+
scope: options.scope,
|
|
99
|
+
sources: [{
|
|
100
|
+
kind: "mcp_server",
|
|
101
|
+
id: options.serverId,
|
|
102
|
+
name: options.serverName,
|
|
103
|
+
capturedAt: generatedAt,
|
|
104
|
+
protocol: "mcp",
|
|
105
|
+
protocolVersion: options.protocolVersion,
|
|
106
|
+
raw: {
|
|
107
|
+
capabilities: options.serverCapabilities,
|
|
108
|
+
tools: options.tools,
|
|
109
|
+
},
|
|
110
|
+
}],
|
|
111
|
+
capabilities,
|
|
112
|
+
warnings: [],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export function normalizeModelCatalogToScoutCapabilities(options) {
|
|
116
|
+
const capturedAt = options.capturedAt ?? Date.now();
|
|
117
|
+
const evidence = {
|
|
118
|
+
kind: "catalog",
|
|
119
|
+
ref: `model-catalog:${options.sourceId}`,
|
|
120
|
+
sourceId: options.sourceId,
|
|
121
|
+
observedAt: capturedAt,
|
|
122
|
+
trust: "declared",
|
|
123
|
+
};
|
|
124
|
+
return options.models
|
|
125
|
+
.filter((model) => model.providerId.trim() && model.modelId.trim())
|
|
126
|
+
.map((model) => {
|
|
127
|
+
const providerId = model.providerId.trim();
|
|
128
|
+
const modelId = model.modelId.trim();
|
|
129
|
+
return {
|
|
130
|
+
id: makeModelCapabilityId(providerId, modelId),
|
|
131
|
+
name: `model.${providerId}.${modelId}`,
|
|
132
|
+
displayName: model.displayName ?? modelId,
|
|
133
|
+
provider: "model",
|
|
134
|
+
providerId,
|
|
135
|
+
methods: [{
|
|
136
|
+
name: "invoke",
|
|
137
|
+
displayName: "Invoke model",
|
|
138
|
+
effects: ["execute"],
|
|
139
|
+
metadata: {
|
|
140
|
+
inputModalities: model.inputModalities ?? ["unknown"],
|
|
141
|
+
outputModalities: model.outputModalities ?? ["unknown"],
|
|
142
|
+
features: model.features ?? {},
|
|
143
|
+
contextWindowTokens: model.contextWindowTokens,
|
|
144
|
+
maxOutputTokens: model.maxOutputTokens,
|
|
145
|
+
},
|
|
146
|
+
}],
|
|
147
|
+
scope: options.scope,
|
|
148
|
+
readiness: {
|
|
149
|
+
state: "unknown",
|
|
150
|
+
detail: "Declared by model catalog; provider readiness has not been probed.",
|
|
151
|
+
checkedAt: capturedAt,
|
|
152
|
+
evidence: [evidence],
|
|
153
|
+
},
|
|
154
|
+
enforcement: {
|
|
155
|
+
level: "remote_authority",
|
|
156
|
+
trust: "declared",
|
|
157
|
+
reason: "Model metadata is catalog-declared; Scout must still verify provider readiness and policy.",
|
|
158
|
+
},
|
|
159
|
+
provenance: {
|
|
160
|
+
sourceKind: "model_catalog",
|
|
161
|
+
sourceId: options.sourceId,
|
|
162
|
+
sourceName: options.sourceName,
|
|
163
|
+
capturedAt,
|
|
164
|
+
evidence: [evidence],
|
|
165
|
+
},
|
|
166
|
+
enabled: options.enabled ?? true,
|
|
167
|
+
metadata: {
|
|
168
|
+
model: {
|
|
169
|
+
providerId,
|
|
170
|
+
modelId,
|
|
171
|
+
inputModalities: model.inputModalities ?? ["unknown"],
|
|
172
|
+
outputModalities: model.outputModalities ?? ["unknown"],
|
|
173
|
+
features: model.features ?? {},
|
|
174
|
+
contextWindowTokens: model.contextWindowTokens,
|
|
175
|
+
maxOutputTokens: model.maxOutputTokens,
|
|
176
|
+
metadata: model.metadata,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
export function evaluateScoutCapabilityAvailability(snapshot, options) {
|
|
183
|
+
const capability = snapshot.capabilities.find((candidate) => candidate.id === options.capabilityId);
|
|
184
|
+
if (!capability) {
|
|
185
|
+
return {
|
|
186
|
+
decision: "deny",
|
|
187
|
+
capabilityId: options.capabilityId,
|
|
188
|
+
reason: "capability_missing",
|
|
189
|
+
detail: "Capability is not present in the current matrix snapshot.",
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (!capability.enabled) {
|
|
193
|
+
return {
|
|
194
|
+
decision: "deny",
|
|
195
|
+
capabilityId: capability.id,
|
|
196
|
+
reason: "capability_disabled",
|
|
197
|
+
detail: "Capability is disabled in the current matrix snapshot.",
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const methodName = options.methodName ?? capability.methods[0]?.name;
|
|
201
|
+
const method = methodName ? capability.methods.find((candidate) => candidate.name === methodName) : undefined;
|
|
202
|
+
if (!methodName || !method) {
|
|
203
|
+
return {
|
|
204
|
+
decision: "deny",
|
|
205
|
+
capabilityId: capability.id,
|
|
206
|
+
methodName,
|
|
207
|
+
reason: "method_missing",
|
|
208
|
+
detail: "Requested method is not present on the capability.",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (method.requiresApproval) {
|
|
212
|
+
return {
|
|
213
|
+
decision: "require_approval",
|
|
214
|
+
capabilityId: capability.id,
|
|
215
|
+
methodName,
|
|
216
|
+
reason: "method_requires_approval",
|
|
217
|
+
detail: "Capability method requires operator approval before execution.",
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
switch (capability.readiness.state) {
|
|
221
|
+
case "ready":
|
|
222
|
+
return {
|
|
223
|
+
decision: "allow",
|
|
224
|
+
capabilityId: capability.id,
|
|
225
|
+
methodName,
|
|
226
|
+
reason: "Capability is ready.",
|
|
227
|
+
};
|
|
228
|
+
case "degraded":
|
|
229
|
+
return {
|
|
230
|
+
decision: "downgrade",
|
|
231
|
+
capabilityId: capability.id,
|
|
232
|
+
methodName,
|
|
233
|
+
reason: "readiness_degraded",
|
|
234
|
+
detail: capability.readiness.detail ?? "Capability readiness is degraded.",
|
|
235
|
+
};
|
|
236
|
+
case "missing":
|
|
237
|
+
return {
|
|
238
|
+
decision: "deny",
|
|
239
|
+
capabilityId: capability.id,
|
|
240
|
+
methodName,
|
|
241
|
+
reason: "readiness_missing",
|
|
242
|
+
detail: capability.readiness.detail ?? "Capability readiness is missing.",
|
|
243
|
+
};
|
|
244
|
+
case "disabled":
|
|
245
|
+
return {
|
|
246
|
+
decision: "deny",
|
|
247
|
+
capabilityId: capability.id,
|
|
248
|
+
methodName,
|
|
249
|
+
reason: "readiness_disabled",
|
|
250
|
+
detail: capability.readiness.detail ?? "Capability readiness is disabled.",
|
|
251
|
+
};
|
|
252
|
+
case "unknown":
|
|
253
|
+
if (options.requireReady === false) {
|
|
254
|
+
return {
|
|
255
|
+
decision: "allow",
|
|
256
|
+
capabilityId: capability.id,
|
|
257
|
+
methodName,
|
|
258
|
+
reason: "Capability readiness is unknown but this check does not require ready state.",
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
decision: "unknown",
|
|
263
|
+
capabilityId: capability.id,
|
|
264
|
+
methodName,
|
|
265
|
+
reason: "readiness_unknown",
|
|
266
|
+
detail: capability.readiness.detail ?? "Capability readiness is unknown.",
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function getMcpToolDefinitions(tools) {
|
|
271
|
+
return isMcpToolDefinitionArray(tools) ? tools : tools.tools ?? [];
|
|
272
|
+
}
|
|
273
|
+
function isMcpToolDefinitionArray(value) {
|
|
274
|
+
return Array.isArray(value);
|
|
275
|
+
}
|
|
276
|
+
function buildMcpDiscoveryEvidence(options, observedAt) {
|
|
277
|
+
return {
|
|
278
|
+
kind: "protocol_discovery",
|
|
279
|
+
ref: `mcp:${options.serverId}:tools/list`,
|
|
280
|
+
sourceId: options.serverId,
|
|
281
|
+
observedAt,
|
|
282
|
+
protocol: "mcp",
|
|
283
|
+
protocolVersion: options.protocolVersion,
|
|
284
|
+
trust: "declared",
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function buildMcpMethodHints(annotations) {
|
|
288
|
+
if (!annotations) {
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
readOnly: annotations.readOnlyHint,
|
|
293
|
+
destructive: annotations.destructiveHint,
|
|
294
|
+
idempotent: annotations.idempotentHint,
|
|
295
|
+
openWorld: annotations.openWorldHint,
|
|
296
|
+
annotationsTrusted: false,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function inferMcpIdempotence(annotations) {
|
|
300
|
+
if (annotations?.idempotentHint !== undefined) {
|
|
301
|
+
return annotations.idempotentHint;
|
|
302
|
+
}
|
|
303
|
+
return annotations?.readOnlyHint === true ? true : undefined;
|
|
304
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { MetadataMap, ScoutId } from "./common.js";
|
|
2
|
+
export type ConversationIdentityRecord = {
|
|
3
|
+
id: ScoutId;
|
|
4
|
+
kind: string;
|
|
5
|
+
metadata?: MetadataMap;
|
|
6
|
+
};
|
|
7
|
+
export declare const CHANNEL_ID_PREFIX = "chn-";
|
|
8
|
+
export declare const CHAT_ID_PREFIX = "chn-";
|
|
9
|
+
export declare const LEGACY_CHAT_ID_PREFIX = "chat_";
|
|
10
|
+
export declare const LEGACY_CHANNEL_ID_PREFIX = "c.";
|
|
11
|
+
export declare const CHANNEL_NATURAL_KEY_METADATA = "naturalKey";
|
|
12
|
+
export declare function mintChannelId(randomUuid: () => string): ScoutId;
|
|
13
|
+
/**
|
|
14
|
+
* Mint the opaque id for a named/system channel from its canonical identity.
|
|
15
|
+
* Every concurrent creator therefore arrives at the same broker record even
|
|
16
|
+
* when each caller began from a stale snapshot.
|
|
17
|
+
*/
|
|
18
|
+
export declare function stableChannelId(naturalKey: string): ScoutId;
|
|
19
|
+
export declare function isOpaqueChannelId(value: string | null | undefined): value is ScoutId;
|
|
20
|
+
export declare function directChannelNaturalKey(participantIds: ScoutId[]): string;
|
|
21
|
+
export declare function namedChannelNaturalKey(channel: string): string;
|
|
22
|
+
export declare function systemChannelNaturalKey(name: string): string;
|
|
23
|
+
export declare function channelNaturalKeyFromMetadata(metadata: MetadataMap | undefined): string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Read the semantic identity of a conversation, including the structural ids
|
|
26
|
+
* written by Scout versions that predate `metadata.naturalKey`.
|
|
27
|
+
*
|
|
28
|
+
* Structural ids remain read-only compatibility aliases. New writes must use
|
|
29
|
+
* an opaque id and persist the natural key in metadata.
|
|
30
|
+
*/
|
|
31
|
+
export declare function conversationNaturalKey(conversation: ConversationIdentityRecord): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Return every record for one semantic conversation in deterministic priority
|
|
34
|
+
* order: the stable named-channel id, another opaque id, then structural
|
|
35
|
+
* compatibility aliases. This prevents snapshot insertion order from choosing
|
|
36
|
+
* which chat receives a write.
|
|
37
|
+
*/
|
|
38
|
+
export declare function conversationsWithNaturalKey<T extends ConversationIdentityRecord>(conversations: Iterable<T>, naturalKey: string): T[];
|
|
39
|
+
export declare function preferredConversationWithNaturalKey<T extends ConversationIdentityRecord>(conversations: Iterable<T>, naturalKey: string): T | undefined;
|
|
40
|
+
//# sourceMappingURL=channel-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-identity.d.ts","sourceRoot":"","sources":["../src/channel-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAExD,MAAM,MAAM,0BAA0B,GAAG;IACvC,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,iBAAiB,SAAS,CAAC;AACxC,eAAO,MAAM,cAAc,SAAoB,CAAC;AAChD,eAAO,MAAM,qBAAqB,UAAU,CAAC;AAC7C,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,eAAO,MAAM,4BAA4B,eAAe,CAAC;AAEzD,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,MAAM,GAAG,OAAO,CAE/D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAW3D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,IAAI,OAAO,CAWpF;AAED,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,OAAO,EAAE,GAAG,MAAM,CAEzE;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,WAAW,GAAG,SAAS,GAChC,MAAM,GAAG,IAAI,CAGf;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,0BAA0B,GACvC,MAAM,GAAG,IAAI,CAwBf;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,0BAA0B,EAC9E,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC,EAC1B,UAAU,EAAE,MAAM,GACjB,CAAC,EAAE,CAeL;AAED,wBAAgB,mCAAmC,CAAC,CAAC,SAAS,0BAA0B,EACtF,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC,EAC1B,UAAU,EAAE,MAAM,GACjB,CAAC,GAAG,SAAS,CAEf"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export const CHANNEL_ID_PREFIX = "chn-";
|
|
2
|
+
export const CHAT_ID_PREFIX = CHANNEL_ID_PREFIX;
|
|
3
|
+
export const LEGACY_CHAT_ID_PREFIX = "chat_";
|
|
4
|
+
export const LEGACY_CHANNEL_ID_PREFIX = "c.";
|
|
5
|
+
export const CHANNEL_NATURAL_KEY_METADATA = "naturalKey";
|
|
6
|
+
export function mintChannelId(randomUuid) {
|
|
7
|
+
return `${CHAT_ID_PREFIX}${randomUuid().toLowerCase().replace(/-/g, "")}`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Mint the opaque id for a named/system channel from its canonical identity.
|
|
11
|
+
* Every concurrent creator therefore arrives at the same broker record even
|
|
12
|
+
* when each caller began from a stale snapshot.
|
|
13
|
+
*/
|
|
14
|
+
export function stableChannelId(naturalKey) {
|
|
15
|
+
const normalized = naturalKey.trim().toLowerCase();
|
|
16
|
+
const hash = (seed) => {
|
|
17
|
+
let value = seed;
|
|
18
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
19
|
+
value ^= BigInt(normalized.charCodeAt(index));
|
|
20
|
+
value = BigInt.asUintN(64, value * 0x100000001b3n);
|
|
21
|
+
}
|
|
22
|
+
return value.toString(16).padStart(16, "0");
|
|
23
|
+
};
|
|
24
|
+
return `${CHAT_ID_PREFIX}${hash(0xcbf29ce484222325n)}${hash(0x84222325cbf29ce4n)}`;
|
|
25
|
+
}
|
|
26
|
+
export function isOpaqueChannelId(value) {
|
|
27
|
+
if (typeof value !== "string")
|
|
28
|
+
return false;
|
|
29
|
+
return (value.startsWith(CHAT_ID_PREFIX) && value.length > CHAT_ID_PREFIX.length) || (value.startsWith(LEGACY_CHAT_ID_PREFIX)
|
|
30
|
+
&& value.length > LEGACY_CHAT_ID_PREFIX.length) || (value.startsWith(LEGACY_CHANNEL_ID_PREFIX)
|
|
31
|
+
&& value.length > LEGACY_CHANNEL_ID_PREFIX.length);
|
|
32
|
+
}
|
|
33
|
+
export function directChannelNaturalKey(participantIds) {
|
|
34
|
+
return `direct:${stableIdentityParts(participantIds).join(",")}`;
|
|
35
|
+
}
|
|
36
|
+
export function namedChannelNaturalKey(channel) {
|
|
37
|
+
return `channel:${encodeIdentityPart(channel.trim().toLowerCase() || "shared")}`;
|
|
38
|
+
}
|
|
39
|
+
export function systemChannelNaturalKey(name) {
|
|
40
|
+
return `system:${encodeIdentityPart(name.trim().toLowerCase() || "system")}`;
|
|
41
|
+
}
|
|
42
|
+
export function channelNaturalKeyFromMetadata(metadata) {
|
|
43
|
+
const value = metadata?.[CHANNEL_NATURAL_KEY_METADATA];
|
|
44
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Read the semantic identity of a conversation, including the structural ids
|
|
48
|
+
* written by Scout versions that predate `metadata.naturalKey`.
|
|
49
|
+
*
|
|
50
|
+
* Structural ids remain read-only compatibility aliases. New writes must use
|
|
51
|
+
* an opaque id and persist the natural key in metadata.
|
|
52
|
+
*/
|
|
53
|
+
export function conversationNaturalKey(conversation) {
|
|
54
|
+
const explicit = channelNaturalKeyFromMetadata(conversation.metadata);
|
|
55
|
+
if (explicit) {
|
|
56
|
+
return explicit;
|
|
57
|
+
}
|
|
58
|
+
if (conversation.kind !== "channel" && conversation.kind !== "system") {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const metadataChannel = conversation.metadata?.channel;
|
|
62
|
+
const legacyChannel = conversation.id.startsWith("channel.")
|
|
63
|
+
? conversation.id.slice("channel.".length)
|
|
64
|
+
: null;
|
|
65
|
+
const channel = typeof metadataChannel === "string" && metadataChannel.trim()
|
|
66
|
+
? metadataChannel.trim()
|
|
67
|
+
: legacyChannel?.trim() || null;
|
|
68
|
+
if (!channel) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return conversation.kind === "system"
|
|
72
|
+
? systemChannelNaturalKey(channel)
|
|
73
|
+
: namedChannelNaturalKey(channel);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Return every record for one semantic conversation in deterministic priority
|
|
77
|
+
* order: the stable named-channel id, another opaque id, then structural
|
|
78
|
+
* compatibility aliases. This prevents snapshot insertion order from choosing
|
|
79
|
+
* which chat receives a write.
|
|
80
|
+
*/
|
|
81
|
+
export function conversationsWithNaturalKey(conversations, naturalKey) {
|
|
82
|
+
const normalizedNaturalKey = naturalKey.trim();
|
|
83
|
+
const stableId = normalizedNaturalKey.startsWith("channel:")
|
|
84
|
+
|| normalizedNaturalKey.startsWith("system:")
|
|
85
|
+
? stableChannelId(normalizedNaturalKey)
|
|
86
|
+
: null;
|
|
87
|
+
const priority = (conversation) => {
|
|
88
|
+
if (stableId && conversation.id === stableId)
|
|
89
|
+
return 0;
|
|
90
|
+
if (isOpaqueChannelId(conversation.id))
|
|
91
|
+
return 1;
|
|
92
|
+
return 2;
|
|
93
|
+
};
|
|
94
|
+
return [...conversations]
|
|
95
|
+
.filter((conversation) => conversationNaturalKey(conversation) === normalizedNaturalKey)
|
|
96
|
+
.sort((left, right) => priority(left) - priority(right) || left.id.localeCompare(right.id));
|
|
97
|
+
}
|
|
98
|
+
export function preferredConversationWithNaturalKey(conversations, naturalKey) {
|
|
99
|
+
return conversationsWithNaturalKey(conversations, naturalKey)[0];
|
|
100
|
+
}
|
|
101
|
+
function stableIdentityParts(values) {
|
|
102
|
+
return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)))
|
|
103
|
+
.sort()
|
|
104
|
+
.map(encodeIdentityPart);
|
|
105
|
+
}
|
|
106
|
+
function encodeIdentityPart(value) {
|
|
107
|
+
return encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
108
|
+
}
|
package/dist/collaboration.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { MetadataMap, ScoutId } from "./common.js";
|
|
2
|
-
export type CollaborationKind = "
|
|
2
|
+
export type CollaborationKind = "work_item" | "question";
|
|
3
3
|
export type CollaborationPriority = "low" | "normal" | "high" | "urgent";
|
|
4
4
|
export type CollaborationAcceptanceState = "none" | "pending" | "accepted" | "reopened";
|
|
5
|
-
export type QuestionState = "open" | "answered" | "closed" | "declined";
|
|
6
5
|
export type WorkItemState = "open" | "working" | "waiting" | "review" | "done" | "cancelled";
|
|
6
|
+
export type QuestionState = "open" | "answered" | "closed" | "declined";
|
|
7
7
|
export type CollaborationRelationKind = "blocks" | "spawns" | "relates_to" | "references";
|
|
8
8
|
export interface CollaborationRelation {
|
|
9
9
|
kind: CollaborationRelationKind;
|
|
@@ -11,7 +11,7 @@ export interface CollaborationRelation {
|
|
|
11
11
|
metadata?: MetadataMap;
|
|
12
12
|
}
|
|
13
13
|
export interface CollaborationWaitingOn {
|
|
14
|
-
kind: "actor" | "
|
|
14
|
+
kind: "actor" | "work_item" | "approval" | "artifact" | "condition";
|
|
15
15
|
label: string;
|
|
16
16
|
targetId?: ScoutId;
|
|
17
17
|
metadata?: MetadataMap;
|
|
@@ -40,16 +40,6 @@ export interface CollaborationRecordBase {
|
|
|
40
40
|
updatedAt: number;
|
|
41
41
|
metadata?: MetadataMap;
|
|
42
42
|
}
|
|
43
|
-
export interface QuestionRecord extends CollaborationRecordBase {
|
|
44
|
-
kind: "question";
|
|
45
|
-
state: QuestionState;
|
|
46
|
-
acceptanceState: CollaborationAcceptanceState;
|
|
47
|
-
askedById?: ScoutId;
|
|
48
|
-
askedOfId?: ScoutId;
|
|
49
|
-
answerMessageId?: ScoutId;
|
|
50
|
-
spawnedWorkItemId?: ScoutId;
|
|
51
|
-
closedAt?: number;
|
|
52
|
-
}
|
|
53
43
|
export interface WorkItemRecord extends CollaborationRecordBase {
|
|
54
44
|
kind: "work_item";
|
|
55
45
|
state: WorkItemState;
|
|
@@ -61,8 +51,29 @@ export interface WorkItemRecord extends CollaborationRecordBase {
|
|
|
61
51
|
reviewRequestedAt?: number;
|
|
62
52
|
completedAt?: number;
|
|
63
53
|
}
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
/**
|
|
55
|
+
* A lightweight information-seeking interaction. A peer of {@link WorkItemRecord},
|
|
56
|
+
* not a point on the same severity ladder: a question can resolve directly, attach
|
|
57
|
+
* to a work item, or spawn one.
|
|
58
|
+
*
|
|
59
|
+
* Acceptance is modeled separately from workflow state (via `acceptanceState`) so a
|
|
60
|
+
* reply and satisfaction do not collapse into one transition — a question can be
|
|
61
|
+
* `answered` without yet being `closed`.
|
|
62
|
+
*/
|
|
63
|
+
export interface QuestionRecord extends CollaborationRecordBase {
|
|
64
|
+
kind: "question";
|
|
65
|
+
state: QuestionState;
|
|
66
|
+
acceptanceState: CollaborationAcceptanceState;
|
|
67
|
+
/** The actor being asked to answer. */
|
|
68
|
+
askedById?: ScoutId;
|
|
69
|
+
answeredById?: ScoutId;
|
|
70
|
+
/** The delivered answer text, present once `state` is `answered`. */
|
|
71
|
+
answer?: string;
|
|
72
|
+
answeredAt?: number;
|
|
73
|
+
closedAt?: number;
|
|
74
|
+
}
|
|
75
|
+
export type CollaborationRecord = WorkItemRecord | QuestionRecord;
|
|
76
|
+
export type CollaborationEventKind = "created" | "claimed" | "accepted" | "reopened" | "waiting" | "progressed" | "handoff" | "review_requested" | "done" | "dismissed" | "cancelled";
|
|
66
77
|
export interface CollaborationEvent {
|
|
67
78
|
id: ScoutId;
|
|
68
79
|
recordId: ScoutId;
|
|
@@ -73,8 +84,25 @@ export interface CollaborationEvent {
|
|
|
73
84
|
summary?: string;
|
|
74
85
|
metadata?: MetadataMap;
|
|
75
86
|
}
|
|
76
|
-
export declare function isQuestionTerminalState(state: QuestionState): boolean;
|
|
77
87
|
export declare function isWorkItemTerminalState(state: WorkItemState): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* A question is terminal once it is `closed` or `declined`. `answered` is NOT terminal
|
|
90
|
+
* — acceptance is tracked separately, so an answered question may still await closure.
|
|
91
|
+
*/
|
|
92
|
+
export declare function isQuestionTerminalState(state: QuestionState): boolean;
|
|
93
|
+
/** Terminal-state check across both collaboration kinds. */
|
|
94
|
+
export declare function isCollaborationTerminalState(record: CollaborationRecord): boolean;
|
|
95
|
+
/** Narrow a {@link CollaborationRecord} to a {@link WorkItemRecord}. */
|
|
96
|
+
export declare function isWorkItem(record: CollaborationRecord): record is WorkItemRecord;
|
|
97
|
+
/** Narrow a {@link CollaborationRecord} to a {@link QuestionRecord}. */
|
|
98
|
+
export declare function isQuestion(record: CollaborationRecord): record is QuestionRecord;
|
|
99
|
+
/**
|
|
100
|
+
* The actor a collaboration record is directed at / awaiting acceptance from: the
|
|
101
|
+
* `requestedById` for a work item, the `askedById` for a question. Consumers that
|
|
102
|
+
* route or attribute a record by its requester should use this rather than reaching
|
|
103
|
+
* for a kind-specific field, so questions are handled instead of silently dropped.
|
|
104
|
+
*/
|
|
105
|
+
export declare function collaborationRequesterId(record: CollaborationRecord): ScoutId | undefined;
|
|
78
106
|
export declare function collaborationRequiresNextMoveOwner(record: CollaborationRecord): boolean;
|
|
79
107
|
export declare function collaborationRequiresOwner(record: CollaborationRecord): boolean;
|
|
80
108
|
export declare function collaborationRequiresWaitingOn(record: CollaborationRecord): boolean;
|
|
@@ -83,3 +111,4 @@ export declare function validateCollaborationRecord(record: CollaborationRecord)
|
|
|
83
111
|
export declare function assertValidCollaborationRecord(record: CollaborationRecord): void;
|
|
84
112
|
export declare function validateCollaborationEvent(event: CollaborationEvent, record?: CollaborationRecord): string[];
|
|
85
113
|
export declare function assertValidCollaborationEvent(event: CollaborationEvent, record?: CollaborationRecord): void;
|
|
114
|
+
//# sourceMappingURL=collaboration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collaboration.d.ts","sourceRoot":"","sources":["../src/collaboration.ts"],"names":[],"mappings":"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"}
|
package/dist/collaboration.js
CHANGED
|
@@ -1,14 +1,38 @@
|
|
|
1
|
+
export function isWorkItemTerminalState(state) {
|
|
2
|
+
return state === "done" || state === "cancelled";
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* A question is terminal once it is `closed` or `declined`. `answered` is NOT terminal
|
|
6
|
+
* — acceptance is tracked separately, so an answered question may still await closure.
|
|
7
|
+
*/
|
|
1
8
|
export function isQuestionTerminalState(state) {
|
|
2
9
|
return state === "closed" || state === "declined";
|
|
3
10
|
}
|
|
4
|
-
|
|
5
|
-
|
|
11
|
+
/** Terminal-state check across both collaboration kinds. */
|
|
12
|
+
export function isCollaborationTerminalState(record) {
|
|
13
|
+
return record.kind === "work_item"
|
|
14
|
+
? isWorkItemTerminalState(record.state)
|
|
15
|
+
: isQuestionTerminalState(record.state);
|
|
16
|
+
}
|
|
17
|
+
/** Narrow a {@link CollaborationRecord} to a {@link WorkItemRecord}. */
|
|
18
|
+
export function isWorkItem(record) {
|
|
19
|
+
return record.kind === "work_item";
|
|
20
|
+
}
|
|
21
|
+
/** Narrow a {@link CollaborationRecord} to a {@link QuestionRecord}. */
|
|
22
|
+
export function isQuestion(record) {
|
|
23
|
+
return record.kind === "question";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The actor a collaboration record is directed at / awaiting acceptance from: the
|
|
27
|
+
* `requestedById` for a work item, the `askedById` for a question. Consumers that
|
|
28
|
+
* route or attribute a record by its requester should use this rather than reaching
|
|
29
|
+
* for a kind-specific field, so questions are handled instead of silently dropped.
|
|
30
|
+
*/
|
|
31
|
+
export function collaborationRequesterId(record) {
|
|
32
|
+
return isWorkItem(record) ? record.requestedById : record.askedById;
|
|
6
33
|
}
|
|
7
34
|
export function collaborationRequiresNextMoveOwner(record) {
|
|
8
|
-
|
|
9
|
-
return !isQuestionTerminalState(record.state);
|
|
10
|
-
}
|
|
11
|
-
return !isWorkItemTerminalState(record.state);
|
|
35
|
+
return !isCollaborationTerminalState(record);
|
|
12
36
|
}
|
|
13
37
|
export function collaborationRequiresOwner(record) {
|
|
14
38
|
return record.kind === "work_item" && !isWorkItemTerminalState(record.state);
|
|
@@ -20,10 +44,7 @@ export function collaborationRequiresAcceptance(record) {
|
|
|
20
44
|
if (record.acceptanceState === "none") {
|
|
21
45
|
return false;
|
|
22
46
|
}
|
|
23
|
-
|
|
24
|
-
return Boolean(record.askedById && record.askedOfId);
|
|
25
|
-
}
|
|
26
|
-
return Boolean(record.requestedById);
|
|
47
|
+
return Boolean(collaborationRequesterId(record));
|
|
27
48
|
}
|
|
28
49
|
export function validateCollaborationRecord(record) {
|
|
29
50
|
const errors = [];
|
|
@@ -51,14 +72,14 @@ export function validateCollaborationRecord(record) {
|
|
|
51
72
|
if (record.kind === "work_item" && collaborationRequiresWaitingOn(record) && !record.waitingOn) {
|
|
52
73
|
errors.push("waiting work items require waitingOn");
|
|
53
74
|
}
|
|
54
|
-
if (record.kind === "
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
else if (record.waitingOn?.targetId && record.waitingOn.targetId === record.id) {
|
|
75
|
+
if (record.kind === "work_item"
|
|
76
|
+
&& record.waitingOn?.targetId
|
|
77
|
+
&& record.waitingOn.targetId === record.id) {
|
|
60
78
|
errors.push("waitingOn.targetId cannot reference the work item itself");
|
|
61
79
|
}
|
|
80
|
+
if (record.kind === "question" && record.state === "answered" && !record.answer?.trim()) {
|
|
81
|
+
errors.push("answered questions require an answer");
|
|
82
|
+
}
|
|
62
83
|
if (record.acceptanceState !== "none" && !collaborationRequiresAcceptance(record)) {
|
|
63
84
|
errors.push("acceptanceState requires the corresponding requester and reviewer identities");
|
|
64
85
|
}
|
|
@@ -89,17 +110,12 @@ export function validateCollaborationEvent(event, record) {
|
|
|
89
110
|
errors.push("collaboration event recordKind does not match the target record");
|
|
90
111
|
}
|
|
91
112
|
}
|
|
92
|
-
if (event.kind === "answered" && event.recordKind !== "question") {
|
|
93
|
-
errors.push("answered events only apply to questions");
|
|
94
|
-
}
|
|
95
|
-
if (event.kind === "declined" && event.recordKind !== "question") {
|
|
96
|
-
errors.push("declined events only apply to questions");
|
|
97
|
-
}
|
|
98
113
|
if ((event.kind === "waiting"
|
|
99
114
|
|| event.kind === "progressed"
|
|
100
115
|
|| event.kind === "review_requested"
|
|
101
116
|
|| event.kind === "done"
|
|
102
|
-
|| event.kind === "cancelled"
|
|
117
|
+
|| event.kind === "cancelled"
|
|
118
|
+
|| event.kind === "claimed")
|
|
103
119
|
&& event.recordKind !== "work_item") {
|
|
104
120
|
errors.push(`${event.kind} events only apply to work items`);
|
|
105
121
|
}
|