@opengeni/api-router 2.4.2-canary.0 → 2.6.3-canary.0
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/dist/app.js +1 -1
- package/dist/auth/managed-auth-attempt-context.d.ts +5 -1
- package/dist/auth/managed-auth.d.ts +24 -1
- package/dist/{chunk-L7GVVSSQ.js → chunk-XTLI3CBH.js} +2884 -483
- package/dist/chunk-XTLI3CBH.js.map +1 -0
- package/dist/http/sse.d.ts +9 -0
- package/dist/index.js +104 -4
- package/dist/index.js.map +1 -1
- package/dist/integrations/slack-interactions.d.ts +1 -1
- package/dist/interaction-metrics.d.ts +2 -0
- package/dist/mcp/company-brain-governed-writes.d.ts +1 -1
- package/dist/mcp/company-profile-agent-admin.d.ts +6 -6
- package/dist/mcp/remember.d.ts +2 -2
- package/dist/mcp/server.d.ts +34 -0
- package/dist/mcp/session-view.d.ts +1 -0
- package/dist/mcp/session-wait.d.ts +19 -0
- package/dist/routes/api-keys.d.ts +2 -0
- package/dist/routes/browser-sessions.d.ts +1 -0
- package/dist/routes/computer-sessions.d.ts +12 -0
- package/dist/routes/managed-auth-session-sets.d.ts +7 -0
- package/dist/routes/sessions.d.ts +17 -5
- package/dist/routes/workspaces.d.ts +1 -1
- package/dist/sandbox/metrics-ingestion.d.ts +16 -0
- package/dist/work-discovery-observability.d.ts +33 -0
- package/dist/workspace-delete-observability.d.ts +10 -0
- package/package.json +19 -19
- package/src/app.ts +172 -20
- package/src/auth/managed-auth-attempt-context.ts +40 -3
- package/src/auth/managed-auth-session-adapter.ts +1 -0
- package/src/auth/managed-auth.ts +164 -4
- package/src/http/sse.ts +279 -45
- package/src/integrations/oauth-client.ts +8 -1
- package/src/integrations/provider-oauth.ts +12 -2
- package/src/interaction-metrics.ts +30 -0
- package/src/mcp/company-brain-governed-writes.ts +38 -23
- package/src/mcp/company-profile-agent-admin.ts +7 -7
- package/src/mcp/remember.ts +19 -8
- package/src/mcp/server.ts +621 -54
- package/src/mcp/session-wait.ts +56 -8
- package/src/routes/api-integrations.ts +2 -2
- package/src/routes/api-keys.ts +149 -7
- package/src/routes/browser-sessions.ts +10 -2
- package/src/routes/capabilities.ts +3 -3
- package/src/routes/codex.ts +483 -74
- package/src/routes/company-profile.ts +64 -0
- package/src/routes/computer-sessions.ts +103 -1
- package/src/routes/integration-facets.ts +8 -5
- package/src/routes/interaction-resources.ts +7 -1
- package/src/routes/managed-auth-session-sets.ts +199 -2
- package/src/routes/organization-memberships.ts +28 -8
- package/src/routes/packs.ts +5 -5
- package/src/routes/plugins.ts +2 -2
- package/src/routes/scheduled-tasks.ts +9 -0
- package/src/routes/sessions.ts +380 -87
- package/src/routes/skills.ts +3 -3
- package/src/routes/workspaces.ts +293 -54
- package/src/sandbox/channel-a.ts +10 -4
- package/src/sandbox/machines.ts +13 -6
- package/src/sandbox/metrics-ingestion.ts +157 -3
- package/src/sandbox/viewer.ts +20 -1
- package/src/work-discovery-observability.ts +121 -0
- package/src/workspace-delete-observability.ts +75 -0
- package/dist/chunk-L7GVVSSQ.js.map +0 -1
package/src/mcp/server.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
stableJson,
|
|
15
15
|
compactSessionEventResult,
|
|
16
16
|
sessionEventLatestClassToSemanticClass,
|
|
17
|
+
MemorySlackPublicationDistribution,
|
|
17
18
|
SessionMcpCredentialUpdateInput,
|
|
18
19
|
ToolAuthNeededPayload,
|
|
19
20
|
VariableSetVariableName,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
SESSION_GOAL_SUCCESS_CRITERIA_MAX_BYTES,
|
|
39
40
|
SESSION_GOAL_TEXT_MAX_BYTES,
|
|
40
41
|
SESSION_INSTRUCTIONS_MAX_CHARACTERS,
|
|
42
|
+
SESSION_TITLE_MAX_CHARACTERS,
|
|
41
43
|
MAX_SELECTED_VARIABLE_SETS,
|
|
42
44
|
sessionGoalUtf8Bytes,
|
|
43
45
|
TASK_NOTE_LIST_DEFAULT_LIMIT,
|
|
@@ -45,6 +47,16 @@ import {
|
|
|
45
47
|
TASK_NOTE_MAX_LIFETIME_DAYS,
|
|
46
48
|
TASK_NOTE_REASON_MAX_BYTES,
|
|
47
49
|
TASK_NOTE_TEXT_MAX_BYTES,
|
|
50
|
+
WORK_CLAIM_CANONICAL_KEY_MAX_BYTES,
|
|
51
|
+
WORK_CLAIM_DISPLAY_LABEL_MAX_BYTES,
|
|
52
|
+
WORK_CLAIM_DISCOVERY_LIMIT,
|
|
53
|
+
WORK_CLAIM_NAMESPACE_MAX_BYTES,
|
|
54
|
+
WORK_CLAIM_VERSION_VALUE_MAX_BYTES,
|
|
55
|
+
WORK_DISCOVERY_QUERY_MAX_CHARS,
|
|
56
|
+
WORK_DISCOVERY_RECENT_HOURS_MAX,
|
|
57
|
+
WorkClaimSubjectType,
|
|
58
|
+
type WorkClaimSubjectFilter,
|
|
59
|
+
type SessionStatus,
|
|
48
60
|
SubmitHumanInputResponseRequest,
|
|
49
61
|
} from "@opengeni/contracts";
|
|
50
62
|
import {
|
|
@@ -80,6 +92,8 @@ import {
|
|
|
80
92
|
readVariableSetSecretAtomically,
|
|
81
93
|
recordSyncedSocialPosts,
|
|
82
94
|
listVariableSets,
|
|
95
|
+
MEMORY_CORRECT_TOOL_DESCRIPTION,
|
|
96
|
+
MEMORY_SAVE_TOOL_DESCRIPTION,
|
|
83
97
|
MEMORY_SEARCH_TOOL_DESCRIPTION,
|
|
84
98
|
requireScheduledTask,
|
|
85
99
|
requireSession,
|
|
@@ -104,10 +118,16 @@ import {
|
|
|
104
118
|
createTaskNote,
|
|
105
119
|
listTaskNotes,
|
|
106
120
|
replaceTaskNote,
|
|
121
|
+
releaseWorkClaim,
|
|
122
|
+
upsertWorkClaim,
|
|
107
123
|
acceptSessionHumanInputResponse,
|
|
108
124
|
HumanInputResponseValidationError,
|
|
109
125
|
} from "@opengeni/db";
|
|
110
|
-
import {
|
|
126
|
+
import {
|
|
127
|
+
appendAndPublishEvents,
|
|
128
|
+
appendAndPublishTurnEventsFenced,
|
|
129
|
+
publishDurableSessionEvents,
|
|
130
|
+
} from "@opengeni/events";
|
|
111
131
|
import { allowedFirstPartyMcpToolsForSession, codemodeWorkspaceUrl } from "@opengeni/config";
|
|
112
132
|
import {
|
|
113
133
|
createSignedState,
|
|
@@ -129,12 +149,14 @@ import {
|
|
|
129
149
|
authorizedSocialConnectionsForGrant,
|
|
130
150
|
authorizedAtlassianConnectionsForGrant,
|
|
131
151
|
buildCapabilityCatalog,
|
|
152
|
+
correctWorkspaceMemoryWithSlackPublication,
|
|
132
153
|
nativeConnectionCapabilityRecommendations,
|
|
133
154
|
requireLiveAgentAttemptAuthorization,
|
|
134
155
|
requireSessionAuthorization,
|
|
135
156
|
requireSessionAuthorizationListScope,
|
|
136
157
|
SessionAuthorizationDeniedError,
|
|
137
158
|
SessionAuthorizationUnavailableError,
|
|
159
|
+
saveWorkspaceMemoryWithSlackPublication,
|
|
138
160
|
searchCapabilityCatalogItems,
|
|
139
161
|
type ResolvedSessionAuthorization,
|
|
140
162
|
} from "@opengeni/core";
|
|
@@ -179,6 +201,7 @@ import {
|
|
|
179
201
|
ScheduledTaskSyncError,
|
|
180
202
|
syncCreatedScheduledTask,
|
|
181
203
|
syncUpdatedScheduledTask,
|
|
204
|
+
validateScheduledTaskMachineTarget,
|
|
182
205
|
validateScheduledTaskTarget,
|
|
183
206
|
updateScheduledTaskForApi,
|
|
184
207
|
validatedScheduledTaskUpdate,
|
|
@@ -216,11 +239,13 @@ import {
|
|
|
216
239
|
SESSION_EVENT_MCP_MAX_BYTES,
|
|
217
240
|
} from "./session-view";
|
|
218
241
|
import {
|
|
242
|
+
SESSION_WAIT_COMPLETION_EVENT_TYPES,
|
|
219
243
|
SESSION_WAIT_DEFAULT_SECONDS,
|
|
220
244
|
SESSION_WAIT_EVENT_TYPES,
|
|
221
245
|
SESSION_WAIT_EVENTS_PER_TARGET,
|
|
222
246
|
SESSION_WAIT_MAX_SECONDS,
|
|
223
247
|
SESSION_WAIT_MAX_TARGETS,
|
|
248
|
+
sessionWaitCompletionEventMatches,
|
|
224
249
|
waitForSessionChanges,
|
|
225
250
|
} from "./session-wait";
|
|
226
251
|
import {
|
|
@@ -251,6 +276,7 @@ import { registerCompanyProfileAgentAdminTools } from "./company-profile-agent-a
|
|
|
251
276
|
import { registerRememberTools } from "./remember";
|
|
252
277
|
import { mintSandboxCodemodeToken } from "@opengeni/runtime/sandbox";
|
|
253
278
|
import { deleteScheduledTaskWithDurableCleanup } from "../scheduled-task-deletion";
|
|
279
|
+
import { observeWorkDiscovery, summarizeWorkDiscoveryRows } from "../work-discovery-observability";
|
|
254
280
|
|
|
255
281
|
export type McpServerOptions = {
|
|
256
282
|
// Origin of the HTTP request that reached the MCP route. Browser-oriented
|
|
@@ -262,6 +288,12 @@ export type McpServerOptions = {
|
|
|
262
288
|
|
|
263
289
|
const ORCHESTRATION_FAILURE_CODE_MAX_LENGTH = 128;
|
|
264
290
|
const ORCHESTRATION_FAILURE_MESSAGE_MAX_UTF8_BYTES = 1_024;
|
|
291
|
+
// Keep pathological raw MCP payloads away from Unicode normalization while
|
|
292
|
+
// leaving the shared DB normalizer authoritative for the exact post-NFKC
|
|
293
|
+
// code-point limit. The multiplier admits supplementary-plane characters,
|
|
294
|
+
// decomposed forms, and ordinary whitespace folding without reopening the
|
|
295
|
+
// API-wide request-body ceiling for this 200-code-point field.
|
|
296
|
+
const MCP_DISCOVERY_QUERY_MAX_UTF16_CODE_UNITS = WORK_DISCOVERY_QUERY_MAX_CHARS * 8;
|
|
265
297
|
|
|
266
298
|
type OrchestrationToolName = "session_create" | "session_send_message";
|
|
267
299
|
|
|
@@ -396,9 +428,6 @@ const FIRST_PARTY_TOOL_AUTHORIZATION = {
|
|
|
396
428
|
goal_complete: { sessionRequired: true, allOf: ["goals:manage"] },
|
|
397
429
|
goal_pause: { sessionRequired: true, allOf: ["goals:manage"] },
|
|
398
430
|
memory_search: { sessionRequired: true, allOf: ["documents:search"] },
|
|
399
|
-
// Retired: never registered, so these are never consulted. The map must stay
|
|
400
|
-
// total over the tool-name union, which still carries both names so that
|
|
401
|
-
// previously written scheduled-task snapshots keep parsing.
|
|
402
431
|
memory_save: { sessionRequired: true, allOf: ["documents:search"] },
|
|
403
432
|
memory_correct: { sessionRequired: true, allOf: ["documents:search"] },
|
|
404
433
|
preference_registry_summary: {
|
|
@@ -410,6 +439,8 @@ const FIRST_PARTY_TOOL_AUTHORIZATION = {
|
|
|
410
439
|
task_note_save: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
411
440
|
task_note_archive: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
412
441
|
task_note_replace: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
442
|
+
work_claim_upsert: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
443
|
+
work_claim_release: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
413
444
|
knowledge_propose: { sessionRequired: true, allOf: ["documents:search"] },
|
|
414
445
|
knowledge_correct: { sessionRequired: true, allOf: ["documents:search"] },
|
|
415
446
|
task_note_promote_knowledge: {
|
|
@@ -791,6 +822,9 @@ export function buildOpenGeniMcpServer(
|
|
|
791
822
|
if (sessionId !== null && exactAgentAttemptClaims(grant) !== null) {
|
|
792
823
|
registerPreferenceRegistryTools(server, deps, grant, json);
|
|
793
824
|
registerTaskNoteTools(server, deps, grant, sessionId, json);
|
|
825
|
+
if (deps.settings.workClaimMutationsEnabled) {
|
|
826
|
+
registerWorkClaimTools(server, deps, grant, sessionId, json);
|
|
827
|
+
}
|
|
794
828
|
const attempt = exactAgentAttemptClaims(grant)!;
|
|
795
829
|
registerCompanyBrainGovernedWriteTools({
|
|
796
830
|
server,
|
|
@@ -1696,6 +1730,14 @@ export function buildOpenGeniMcpServer(
|
|
|
1696
1730
|
agentConfig: task.agentConfig,
|
|
1697
1731
|
missingTargetStatus: 404,
|
|
1698
1732
|
});
|
|
1733
|
+
await validateScheduledTaskMachineTarget({
|
|
1734
|
+
settings: deps.settings,
|
|
1735
|
+
db: deps.db,
|
|
1736
|
+
grant,
|
|
1737
|
+
runMode: task.runMode,
|
|
1738
|
+
agentConfig: task.agentConfig,
|
|
1739
|
+
requireOnline: true,
|
|
1740
|
+
});
|
|
1699
1741
|
await requireLimit(deps, {
|
|
1700
1742
|
accountId: grant.accountId,
|
|
1701
1743
|
workspaceId: grant.workspaceId,
|
|
@@ -3241,6 +3283,133 @@ function registerTaskNoteTools(
|
|
|
3241
3283
|
);
|
|
3242
3284
|
}
|
|
3243
3285
|
|
|
3286
|
+
function registerWorkClaimTools(
|
|
3287
|
+
server: McpServer,
|
|
3288
|
+
deps: ApiRouteDeps,
|
|
3289
|
+
grant: AccessGrant,
|
|
3290
|
+
sessionId: string,
|
|
3291
|
+
json: JsonResult,
|
|
3292
|
+
): void {
|
|
3293
|
+
const attemptClaims = () => {
|
|
3294
|
+
const resolved = exactAgentAttemptClaims(grant);
|
|
3295
|
+
if (!resolved || resolved.sessionId !== sessionId) {
|
|
3296
|
+
throw new Error("Exact signed work-claim attempt authority is required.");
|
|
3297
|
+
}
|
|
3298
|
+
return {
|
|
3299
|
+
accountId: grant.accountId,
|
|
3300
|
+
workspaceId: grant.workspaceId,
|
|
3301
|
+
...resolved,
|
|
3302
|
+
};
|
|
3303
|
+
};
|
|
3304
|
+
const authorize = async () => {
|
|
3305
|
+
await authorizeFirstPartySession(deps, grant, sessionId, "session.first_party_mcp.call");
|
|
3306
|
+
};
|
|
3307
|
+
|
|
3308
|
+
server.registerTool(
|
|
3309
|
+
"work_claim_upsert",
|
|
3310
|
+
{
|
|
3311
|
+
description:
|
|
3312
|
+
"Create or refresh one typed, non-exclusive claim describing this session's current external work. Claims are advisory evidence, never locks or authority. Use stable public identifiers only; never put credentials, tokens, or other secrets in claim fields. expectedRevision=0 creates a new active claim; refreshing an existing active claim requires its exact revision. Use a fresh operationId; an exact retry from a replacement attempt on the same logical turn replays safely.",
|
|
3313
|
+
inputSchema: {
|
|
3314
|
+
operationId: z4.string().uuid(),
|
|
3315
|
+
expectedRevision: z4.number().int().min(0),
|
|
3316
|
+
subjectNamespace: z4.string().min(1).max(WORK_CLAIM_NAMESPACE_MAX_BYTES),
|
|
3317
|
+
subjectType: z4.enum([
|
|
3318
|
+
"repository",
|
|
3319
|
+
"branch",
|
|
3320
|
+
"pull_request",
|
|
3321
|
+
"issue",
|
|
3322
|
+
"artifact",
|
|
3323
|
+
"release",
|
|
3324
|
+
"ci_run",
|
|
3325
|
+
"other",
|
|
3326
|
+
]),
|
|
3327
|
+
canonicalKey: z4.string().min(1).max(WORK_CLAIM_CANONICAL_KEY_MAX_BYTES),
|
|
3328
|
+
displayLabel: z4.string().min(1).max(WORK_CLAIM_DISPLAY_LABEL_MAX_BYTES).optional(),
|
|
3329
|
+
role: z4.enum(["working", "reviewing", "monitoring", "delivering"]),
|
|
3330
|
+
versionKind: z4
|
|
3331
|
+
.enum([
|
|
3332
|
+
"git_commit",
|
|
3333
|
+
"branch_head",
|
|
3334
|
+
"pull_request_head",
|
|
3335
|
+
"artifact_version",
|
|
3336
|
+
"release_version",
|
|
3337
|
+
"ci_run",
|
|
3338
|
+
"other",
|
|
3339
|
+
])
|
|
3340
|
+
.optional(),
|
|
3341
|
+
versionValue: z4.string().min(1).max(WORK_CLAIM_VERSION_VALUE_MAX_BYTES).optional(),
|
|
3342
|
+
},
|
|
3343
|
+
},
|
|
3344
|
+
async ({
|
|
3345
|
+
operationId,
|
|
3346
|
+
expectedRevision,
|
|
3347
|
+
subjectNamespace,
|
|
3348
|
+
subjectType,
|
|
3349
|
+
canonicalKey,
|
|
3350
|
+
displayLabel,
|
|
3351
|
+
role,
|
|
3352
|
+
versionKind,
|
|
3353
|
+
versionValue,
|
|
3354
|
+
}) => {
|
|
3355
|
+
await authorize();
|
|
3356
|
+
if ((versionKind === undefined) !== (versionValue === undefined)) {
|
|
3357
|
+
throw new Error("work_claim_upsert versionKind and versionValue must be supplied together");
|
|
3358
|
+
}
|
|
3359
|
+
return json(
|
|
3360
|
+
await upsertWorkClaim(deps.db, {
|
|
3361
|
+
...attemptClaims(),
|
|
3362
|
+
operationId,
|
|
3363
|
+
expectedRevision,
|
|
3364
|
+
subjectNamespace,
|
|
3365
|
+
subjectType,
|
|
3366
|
+
canonicalKey,
|
|
3367
|
+
...(displayLabel === undefined ? {} : { displayLabel }),
|
|
3368
|
+
role,
|
|
3369
|
+
...(versionKind && versionValue
|
|
3370
|
+
? { version: { kind: versionKind, value: versionValue } }
|
|
3371
|
+
: {}),
|
|
3372
|
+
}),
|
|
3373
|
+
);
|
|
3374
|
+
},
|
|
3375
|
+
);
|
|
3376
|
+
|
|
3377
|
+
server.registerTool(
|
|
3378
|
+
"work_claim_release",
|
|
3379
|
+
{
|
|
3380
|
+
description:
|
|
3381
|
+
"Release one exact active claim owned by this session. This records an immutable receipt and does not affect other sessions claiming the same subject. Use a fresh operationId and the claim's exact revision.",
|
|
3382
|
+
inputSchema: {
|
|
3383
|
+
operationId: z4.string().uuid(),
|
|
3384
|
+
claimId: z4.string().uuid(),
|
|
3385
|
+
expectedRevision: z4.number().int().min(1),
|
|
3386
|
+
reason: z4.enum([
|
|
3387
|
+
"completed",
|
|
3388
|
+
"cancelled",
|
|
3389
|
+
"failed",
|
|
3390
|
+
"superseded",
|
|
3391
|
+
"no_longer_active",
|
|
3392
|
+
"corrected",
|
|
3393
|
+
"external_state_changed",
|
|
3394
|
+
"other",
|
|
3395
|
+
]),
|
|
3396
|
+
},
|
|
3397
|
+
},
|
|
3398
|
+
async ({ operationId, claimId, expectedRevision, reason }) => {
|
|
3399
|
+
await authorize();
|
|
3400
|
+
return json(
|
|
3401
|
+
await releaseWorkClaim(deps.db, {
|
|
3402
|
+
...attemptClaims(),
|
|
3403
|
+
operationId,
|
|
3404
|
+
claimId,
|
|
3405
|
+
expectedRevision,
|
|
3406
|
+
reason,
|
|
3407
|
+
}),
|
|
3408
|
+
);
|
|
3409
|
+
},
|
|
3410
|
+
);
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3244
3413
|
function registerPreferenceRegistryTools(
|
|
3245
3414
|
server: McpServer,
|
|
3246
3415
|
deps: ApiRouteDeps,
|
|
@@ -3280,6 +3449,7 @@ function registerPreferenceRegistryTools(
|
|
|
3280
3449
|
}
|
|
3281
3450
|
|
|
3282
3451
|
const MemoryKindSchema = z4.enum(["preference", "semantic", "procedural", "decision", "episodic"]);
|
|
3452
|
+
const MemoryWriteKindSchema = z4.enum(["semantic", "decision", "episodic"]);
|
|
3283
3453
|
|
|
3284
3454
|
function scheduledTaskReceipt(
|
|
3285
3455
|
operation: string,
|
|
@@ -3371,6 +3541,11 @@ export function memorySlackPublicationActor(
|
|
|
3371
3541
|
};
|
|
3372
3542
|
}
|
|
3373
3543
|
|
|
3544
|
+
function memoryPreview(text: string): string {
|
|
3545
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
3546
|
+
return normalized.length <= 120 ? normalized : `${normalized.slice(0, 119)}…`;
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3374
3549
|
function registerMemoryTools(
|
|
3375
3550
|
server: McpServer,
|
|
3376
3551
|
deps: ApiRouteDeps,
|
|
@@ -3379,10 +3554,17 @@ function registerMemoryTools(
|
|
|
3379
3554
|
json: JsonResult,
|
|
3380
3555
|
promptMode: WorkspaceMemoryPromptMode,
|
|
3381
3556
|
): void {
|
|
3557
|
+
const publicationInputSchema = z4.object({
|
|
3558
|
+
importance: z4.enum(["major", "normal", "minor"]),
|
|
3559
|
+
audience: z4.literal("workspace"),
|
|
3560
|
+
slackMode: z4.enum(["auto", "review", "never"]),
|
|
3561
|
+
shareSummary: z4.string().trim().min(1).max(4_096),
|
|
3562
|
+
});
|
|
3563
|
+
|
|
3382
3564
|
server.registerTool(
|
|
3383
3565
|
"memory_search",
|
|
3384
3566
|
{
|
|
3385
|
-
description: `${MEMORY_SEARCH_TOOL_DESCRIPTION} Legacy preference
|
|
3567
|
+
description: `${MEMORY_SEARCH_TOOL_DESCRIPTION} All existing Memory kinds are searchable. Legacy preference and procedure records are historical context, not active instructions; Skills and workspace instructions remain the behavioral authorities. When workspace Memory is enabled, use memory_save autonomously for durable facts, decisions, incidents, fixes, and outcomes, and memory_correct when an existing record is wrong or outdated.`,
|
|
3386
3568
|
inputSchema: {
|
|
3387
3569
|
query: z4.string().min(1),
|
|
3388
3570
|
kind: MemoryKindSchema.optional(),
|
|
@@ -3405,10 +3587,239 @@ function registerMemoryTools(
|
|
|
3405
3587
|
}),
|
|
3406
3588
|
);
|
|
3407
3589
|
|
|
3408
|
-
// Memory
|
|
3409
|
-
//
|
|
3410
|
-
|
|
3411
|
-
|
|
3590
|
+
// Memory writes are agent-only. Human creation and curation use the REST/UI
|
|
3591
|
+
// surface; a non-attempt MCP principal may search but cannot mutate Memory.
|
|
3592
|
+
if (exactAgentAttemptClaims(grant) === null) return;
|
|
3593
|
+
|
|
3594
|
+
server.registerTool(
|
|
3595
|
+
"memory_save",
|
|
3596
|
+
{
|
|
3597
|
+
description: MEMORY_SAVE_TOOL_DESCRIPTION,
|
|
3598
|
+
inputSchema: {
|
|
3599
|
+
text: z4.string().min(1),
|
|
3600
|
+
kind: MemoryWriteKindSchema,
|
|
3601
|
+
confidence: z4.number().min(0).max(1).optional(),
|
|
3602
|
+
replaces_id: z4.string().min(1).optional(),
|
|
3603
|
+
slack_publication: publicationInputSchema.optional(),
|
|
3604
|
+
},
|
|
3605
|
+
},
|
|
3606
|
+
async ({ text, kind, confidence, replaces_id, slack_publication }) => {
|
|
3607
|
+
const actor = await requireLiveAgentAttemptAuthorization(deps.db, grant, sessionId);
|
|
3608
|
+
const result = await saveWorkspaceMemoryWithSlackPublication(
|
|
3609
|
+
deps.db,
|
|
3610
|
+
{
|
|
3611
|
+
accountId: grant.accountId,
|
|
3612
|
+
workspaceId: grant.workspaceId,
|
|
3613
|
+
sessionId,
|
|
3614
|
+
text,
|
|
3615
|
+
kind,
|
|
3616
|
+
...(confidence !== undefined ? { confidence } : {}),
|
|
3617
|
+
...(replaces_id ? { replacesId: replaces_id } : {}),
|
|
3618
|
+
origin: "agent",
|
|
3619
|
+
},
|
|
3620
|
+
slack_publication
|
|
3621
|
+
? {
|
|
3622
|
+
distribution: MemorySlackPublicationDistribution.parse(slack_publication),
|
|
3623
|
+
actor: memorySlackPublicationActor(actor, sessionId, grant.subjectLabel ?? null)
|
|
3624
|
+
.actor,
|
|
3625
|
+
ownerLabel: actor.initiator.label ?? grant.subjectLabel ?? null,
|
|
3626
|
+
}
|
|
3627
|
+
: null,
|
|
3628
|
+
deps.getDocumentServices().embedder,
|
|
3629
|
+
);
|
|
3630
|
+
let timelineWarning: string | null = null;
|
|
3631
|
+
try {
|
|
3632
|
+
await appendAndPublishEvents(deps.db, deps.bus, grant.workspaceId, sessionId, [
|
|
3633
|
+
{
|
|
3634
|
+
type: "memory.saved",
|
|
3635
|
+
payload: {
|
|
3636
|
+
memoryId: result.memory.id,
|
|
3637
|
+
kind: result.memory.kind,
|
|
3638
|
+
preview: memoryPreview(result.memory.text),
|
|
3639
|
+
deduped: result.deduped,
|
|
3640
|
+
...(result.superseded ? { supersededMemoryId: result.superseded.id } : {}),
|
|
3641
|
+
},
|
|
3642
|
+
},
|
|
3643
|
+
]);
|
|
3644
|
+
} catch {
|
|
3645
|
+
timelineWarning = "Memory committed, but its session timeline event could not be recorded.";
|
|
3646
|
+
console.warn("workspace memory save: committed without session timeline event", {
|
|
3647
|
+
errorClass: "MemoryTimelineOperationError",
|
|
3648
|
+
errorCode: "memory_save_timeline_append_failed",
|
|
3649
|
+
origin: "api",
|
|
3650
|
+
workspaceId: grant.workspaceId,
|
|
3651
|
+
sessionId,
|
|
3652
|
+
memoryId: result.memory.id,
|
|
3653
|
+
});
|
|
3654
|
+
}
|
|
3655
|
+
const changed = !result.deduped || result.updated || result.superseded !== null;
|
|
3656
|
+
const outcome =
|
|
3657
|
+
result.updated || result.superseded !== null
|
|
3658
|
+
? "updated"
|
|
3659
|
+
: result.deduped
|
|
3660
|
+
? "unchanged"
|
|
3661
|
+
: "created";
|
|
3662
|
+
return json(
|
|
3663
|
+
mcpMutationReceipt({
|
|
3664
|
+
operation: "memory_save",
|
|
3665
|
+
committed: true,
|
|
3666
|
+
outcome,
|
|
3667
|
+
changed,
|
|
3668
|
+
resource: {
|
|
3669
|
+
type: "knowledge_memory",
|
|
3670
|
+
id: result.memory.id,
|
|
3671
|
+
state: result.memory.status,
|
|
3672
|
+
},
|
|
3673
|
+
relatedResources: result.superseded
|
|
3674
|
+
? [
|
|
3675
|
+
{
|
|
3676
|
+
type: "knowledge_memory",
|
|
3677
|
+
id: result.superseded.id,
|
|
3678
|
+
state: result.superseded.status,
|
|
3679
|
+
},
|
|
3680
|
+
]
|
|
3681
|
+
: undefined,
|
|
3682
|
+
timestamp: result.memory.updatedAt,
|
|
3683
|
+
idempotency: { status: "not_supported" },
|
|
3684
|
+
warnings: [
|
|
3685
|
+
...(!result.embedded
|
|
3686
|
+
? ["Memory committed without a vector embedding; keyword search remains available."]
|
|
3687
|
+
: []),
|
|
3688
|
+
...(timelineWarning ? [timelineWarning] : []),
|
|
3689
|
+
],
|
|
3690
|
+
facts: {
|
|
3691
|
+
deduped: result.deduped,
|
|
3692
|
+
dedupeReason: result.dedupeReason,
|
|
3693
|
+
updatedInPlace: result.updated,
|
|
3694
|
+
embedded: result.embedded,
|
|
3695
|
+
slackPublicationDecision: result.slackPublication.decision?.eligible
|
|
3696
|
+
? "eligible"
|
|
3697
|
+
: (result.slackPublication.decision?.reason ?? "not_requested"),
|
|
3698
|
+
slackPublicationId:
|
|
3699
|
+
result.slackPublication.enqueue?.kind === "enqueued" ||
|
|
3700
|
+
result.slackPublication.enqueue?.kind === "replayed"
|
|
3701
|
+
? result.slackPublication.enqueue.publication.id
|
|
3702
|
+
: null,
|
|
3703
|
+
slackPublicationState:
|
|
3704
|
+
result.slackPublication.enqueue?.kind === "enqueued" ||
|
|
3705
|
+
result.slackPublication.enqueue?.kind === "replayed"
|
|
3706
|
+
? result.slackPublication.enqueue.publication.state
|
|
3707
|
+
: null,
|
|
3708
|
+
},
|
|
3709
|
+
}),
|
|
3710
|
+
);
|
|
3711
|
+
},
|
|
3712
|
+
);
|
|
3713
|
+
|
|
3714
|
+
server.registerTool(
|
|
3715
|
+
"memory_correct",
|
|
3716
|
+
{
|
|
3717
|
+
description: MEMORY_CORRECT_TOOL_DESCRIPTION,
|
|
3718
|
+
inputSchema: {
|
|
3719
|
+
id: z4.string().min(1),
|
|
3720
|
+
reason: z4.string().min(1).optional(),
|
|
3721
|
+
replacement_text: z4.string().min(1).optional(),
|
|
3722
|
+
slack_publication: publicationInputSchema.optional(),
|
|
3723
|
+
},
|
|
3724
|
+
},
|
|
3725
|
+
async ({ id, reason, replacement_text, slack_publication }) => {
|
|
3726
|
+
const actor = await requireLiveAgentAttemptAuthorization(deps.db, grant, sessionId);
|
|
3727
|
+
const result = await correctWorkspaceMemoryWithSlackPublication(
|
|
3728
|
+
deps.db,
|
|
3729
|
+
{
|
|
3730
|
+
accountId: grant.accountId,
|
|
3731
|
+
workspaceId: grant.workspaceId,
|
|
3732
|
+
sessionId,
|
|
3733
|
+
id,
|
|
3734
|
+
...(reason ? { reason } : {}),
|
|
3735
|
+
...(replacement_text ? { replacementText: replacement_text } : {}),
|
|
3736
|
+
origin: "agent",
|
|
3737
|
+
},
|
|
3738
|
+
slack_publication
|
|
3739
|
+
? {
|
|
3740
|
+
distribution: MemorySlackPublicationDistribution.parse(slack_publication),
|
|
3741
|
+
actor: memorySlackPublicationActor(actor, sessionId, grant.subjectLabel ?? null)
|
|
3742
|
+
.actor,
|
|
3743
|
+
ownerLabel: actor.initiator.label ?? grant.subjectLabel ?? null,
|
|
3744
|
+
}
|
|
3745
|
+
: null,
|
|
3746
|
+
deps.getDocumentServices().embedder,
|
|
3747
|
+
);
|
|
3748
|
+
let timelineWarning: string | null = null;
|
|
3749
|
+
try {
|
|
3750
|
+
await appendAndPublishEvents(deps.db, deps.bus, grant.workspaceId, sessionId, [
|
|
3751
|
+
{
|
|
3752
|
+
type: "memory.corrected",
|
|
3753
|
+
payload: {
|
|
3754
|
+
memoryId: result.memory.id,
|
|
3755
|
+
kind: result.memory.kind,
|
|
3756
|
+
preview: memoryPreview(result.memory.text),
|
|
3757
|
+
action: result.action,
|
|
3758
|
+
...(reason ? { reason: memoryPreview(reason) } : {}),
|
|
3759
|
+
...(result.replacement
|
|
3760
|
+
? {
|
|
3761
|
+
replacementMemoryId: result.replacement.id,
|
|
3762
|
+
replacementPreview: memoryPreview(result.replacement.text),
|
|
3763
|
+
}
|
|
3764
|
+
: {}),
|
|
3765
|
+
},
|
|
3766
|
+
},
|
|
3767
|
+
]);
|
|
3768
|
+
} catch {
|
|
3769
|
+
timelineWarning =
|
|
3770
|
+
"Memory correction committed, but its session timeline event could not be recorded.";
|
|
3771
|
+
console.warn("workspace memory correction: committed without session timeline event", {
|
|
3772
|
+
errorClass: "MemoryTimelineOperationError",
|
|
3773
|
+
errorCode: "memory_correct_timeline_append_failed",
|
|
3774
|
+
origin: "api",
|
|
3775
|
+
workspaceId: grant.workspaceId,
|
|
3776
|
+
sessionId,
|
|
3777
|
+
memoryId: result.memory.id,
|
|
3778
|
+
});
|
|
3779
|
+
}
|
|
3780
|
+
return json(
|
|
3781
|
+
mcpMutationReceipt({
|
|
3782
|
+
operation: "memory_correct",
|
|
3783
|
+
committed: true,
|
|
3784
|
+
outcome: "updated",
|
|
3785
|
+
changed: true,
|
|
3786
|
+
resource: {
|
|
3787
|
+
type: "knowledge_memory",
|
|
3788
|
+
id: result.memory.id,
|
|
3789
|
+
state: result.memory.status,
|
|
3790
|
+
},
|
|
3791
|
+
relatedResources: result.replacement
|
|
3792
|
+
? [
|
|
3793
|
+
{
|
|
3794
|
+
type: "knowledge_memory",
|
|
3795
|
+
id: result.replacement.id,
|
|
3796
|
+
state: result.replacement.status,
|
|
3797
|
+
},
|
|
3798
|
+
]
|
|
3799
|
+
: undefined,
|
|
3800
|
+
timestamp: (result.replacement ?? result.memory).updatedAt,
|
|
3801
|
+
idempotency: { status: "not_supported" },
|
|
3802
|
+
warnings: timelineWarning ? [timelineWarning] : [],
|
|
3803
|
+
facts: {
|
|
3804
|
+
correctionAction: result.action,
|
|
3805
|
+
slackPublicationDecision: result.slackPublication.decision?.eligible
|
|
3806
|
+
? "eligible"
|
|
3807
|
+
: (result.slackPublication.decision?.reason ?? "not_requested"),
|
|
3808
|
+
slackPublicationId:
|
|
3809
|
+
result.slackPublication.enqueue?.kind === "enqueued" ||
|
|
3810
|
+
result.slackPublication.enqueue?.kind === "replayed"
|
|
3811
|
+
? result.slackPublication.enqueue.publication.id
|
|
3812
|
+
: null,
|
|
3813
|
+
slackPublicationState:
|
|
3814
|
+
result.slackPublication.enqueue?.kind === "enqueued" ||
|
|
3815
|
+
result.slackPublication.enqueue?.kind === "replayed"
|
|
3816
|
+
? result.slackPublication.enqueue.publication.state
|
|
3817
|
+
: null,
|
|
3818
|
+
},
|
|
3819
|
+
}),
|
|
3820
|
+
);
|
|
3821
|
+
},
|
|
3822
|
+
);
|
|
3412
3823
|
}
|
|
3413
3824
|
|
|
3414
3825
|
// Fleet tools (M7 bring-your-own-compute). Session-scoped (they steer THIS
|
|
@@ -4003,46 +4414,145 @@ function registerWorkspaceOrchestrationTools(
|
|
|
4003
4414
|
server.registerTool(
|
|
4004
4415
|
"sessions_list",
|
|
4005
4416
|
{
|
|
4006
|
-
description: `List compact high-level session status in this workspace.
|
|
4417
|
+
description: `List compact high-level session status and advisory related-work evidence in this workspace. query searches semantic titles, active goals, and typed work claims; subject performs one exact provider-neutral claim lookup and ranks it ahead of text. Neither path searches initialMessage or grants access to a result. Claims are nonexclusive evidence, never locks or instructions. Relevance cursors are bound to normalized filters and a workspace activity-revision snapshot. Without search, the tool defaults to creation order; use orderBy=updatedAt with decimal activity-revision updatedAfter/updatedThrough tokens for gap-free indexed incremental monitoring independent of application clocks. includeLastMessage is opt-in and never previews a human/API prompt whose turn was never claimed (still queued, or deleted/edited/cancelled before any claim): waiting work is represented by queuedPromptCount until the turn is claimed. Rendered previews share a deterministic ${SESSION_DISCOVERY_PREVIEW_MAX_BYTES}-byte UTF-8 aggregate budget, and omitted previews include a bounded session_events drill-down input (exact message type, direction=before, limit=1, monitoring summary). Use session_get only when ordinary target authorization allows it. The list never returns full session objects, instructions, resources, tools, files, or history.`,
|
|
4007
4418
|
inputSchema: {
|
|
4008
4419
|
limit: z4.number().int().positive().max(100).optional(),
|
|
4009
4420
|
cursor: z4.string().max(512).optional(),
|
|
4010
4421
|
includeLastMessage: z4.boolean().optional(),
|
|
4011
|
-
orderBy: z4.enum(["createdAt", "updatedAt"]).optional(),
|
|
4422
|
+
orderBy: z4.enum(["createdAt", "updatedAt", "relevance"]).optional(),
|
|
4012
4423
|
updatedAfter: z4.string().max(64).optional(),
|
|
4424
|
+
query: z4.string().max(MCP_DISCOVERY_QUERY_MAX_UTF16_CODE_UNITS).optional(),
|
|
4425
|
+
statuses: z4
|
|
4426
|
+
.array(
|
|
4427
|
+
z4.enum([
|
|
4428
|
+
"queued",
|
|
4429
|
+
"running",
|
|
4430
|
+
"idle",
|
|
4431
|
+
"requires_action",
|
|
4432
|
+
"recovering",
|
|
4433
|
+
"waiting_capacity",
|
|
4434
|
+
"failed",
|
|
4435
|
+
"cancelled",
|
|
4436
|
+
]),
|
|
4437
|
+
)
|
|
4438
|
+
.max(8)
|
|
4439
|
+
.optional(),
|
|
4440
|
+
activeOnly: z4.boolean().optional(),
|
|
4441
|
+
recentHours: z4.number().int().positive().max(WORK_DISCOVERY_RECENT_HOURS_MAX).optional(),
|
|
4442
|
+
rootSessionId: z4.string().uuid().optional(),
|
|
4443
|
+
parentSessionId: z4.string().uuid().nullable().optional(),
|
|
4444
|
+
subject: z4
|
|
4445
|
+
.object({
|
|
4446
|
+
namespace: z4.string().min(1).max(WORK_CLAIM_NAMESPACE_MAX_BYTES),
|
|
4447
|
+
type: WorkClaimSubjectType,
|
|
4448
|
+
canonicalKey: z4.string().min(1).max(WORK_CLAIM_CANONICAL_KEY_MAX_BYTES),
|
|
4449
|
+
})
|
|
4450
|
+
.strict()
|
|
4451
|
+
.optional(),
|
|
4452
|
+
claimLimit: z4.number().int().positive().max(WORK_CLAIM_DISCOVERY_LIMIT).optional(),
|
|
4013
4453
|
},
|
|
4014
4454
|
},
|
|
4015
|
-
async ({
|
|
4455
|
+
async ({
|
|
4456
|
+
limit,
|
|
4457
|
+
cursor,
|
|
4458
|
+
includeLastMessage,
|
|
4459
|
+
orderBy: requestedOrderBy,
|
|
4460
|
+
updatedAfter,
|
|
4461
|
+
query,
|
|
4462
|
+
statuses,
|
|
4463
|
+
activeOnly,
|
|
4464
|
+
recentHours,
|
|
4465
|
+
rootSessionId,
|
|
4466
|
+
parentSessionId,
|
|
4467
|
+
subject,
|
|
4468
|
+
claimLimit,
|
|
4469
|
+
}) => {
|
|
4016
4470
|
const authorizationScope = await requireSessionAuthorizationListScope(
|
|
4017
4471
|
deps,
|
|
4018
4472
|
grant,
|
|
4019
4473
|
"first_party_mcp",
|
|
4020
4474
|
);
|
|
4475
|
+
const startedAtMs = performance.now();
|
|
4476
|
+
const mode = subject ? "subject" : query?.trim() ? "query" : "browse";
|
|
4477
|
+
const metricAuthorizationScope =
|
|
4478
|
+
authorizationScope?.kind === "scoped" ? "scoped" : "workspace";
|
|
4021
4479
|
const decodedCursor = cursor ? decodeSessionDiscoveryCursor(cursor) : undefined;
|
|
4022
|
-
const
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
:
|
|
4031
|
-
|
|
4032
|
-
|
|
4480
|
+
const relevanceRequested = Boolean(query?.trim() || subject);
|
|
4481
|
+
if (relevanceRequested && !deps.settings.workDiscoveryEnabled) {
|
|
4482
|
+
observeWorkDiscovery(deps.observability, {
|
|
4483
|
+
surface: "first_party_mcp",
|
|
4484
|
+
mode,
|
|
4485
|
+
outcome: "disabled",
|
|
4486
|
+
authorizationScope: metricAuthorizationScope,
|
|
4487
|
+
durationMs: performance.now() - startedAtMs,
|
|
4488
|
+
responseBytes: 0,
|
|
4489
|
+
resultCount: 0,
|
|
4490
|
+
overlapCount: 0,
|
|
4491
|
+
matchCounts: {},
|
|
4492
|
+
});
|
|
4493
|
+
throw new Error("sessions_list work discovery is disabled by the operator");
|
|
4033
4494
|
}
|
|
4034
|
-
|
|
4035
|
-
|
|
4495
|
+
try {
|
|
4496
|
+
const orderBy: SessionDiscoveryOrderBy =
|
|
4497
|
+
requestedOrderBy ??
|
|
4498
|
+
decodedCursor?.orderBy ??
|
|
4499
|
+
(relevanceRequested ? "relevance" : "createdAt");
|
|
4500
|
+
if (decodedCursor && decodedCursor.orderBy !== orderBy) {
|
|
4501
|
+
throw new Error("sessions_list cursor order does not match orderBy");
|
|
4502
|
+
}
|
|
4503
|
+
const normalizedUpdatedAfter =
|
|
4504
|
+
updatedAfter !== undefined
|
|
4505
|
+
? normalizeSessionDiscoveryRevision(updatedAfter, "updatedAfter")
|
|
4506
|
+
: (decodedCursor?.updatedAfter ?? undefined);
|
|
4507
|
+
if (normalizedUpdatedAfter !== undefined && orderBy !== "updatedAt") {
|
|
4508
|
+
throw new Error("sessions_list updatedAfter requires orderBy=updatedAt");
|
|
4509
|
+
}
|
|
4510
|
+
if (decodedCursor && decodedCursor.updatedAfter !== (normalizedUpdatedAfter ?? null)) {
|
|
4511
|
+
throw new Error("sessions_list cursor does not match updatedAfter");
|
|
4512
|
+
}
|
|
4513
|
+
const page = await listSessionDiscoverySummaries(deps.db, grant.workspaceId, {
|
|
4514
|
+
limit: boundedSessionDiscoveryLimit(limit),
|
|
4515
|
+
...(decodedCursor ? { cursor: decodedCursor } : {}),
|
|
4516
|
+
includeLastMessage: includeLastMessage === true,
|
|
4517
|
+
orderBy,
|
|
4518
|
+
...(normalizedUpdatedAfter ? { updatedAfter: normalizedUpdatedAfter } : {}),
|
|
4519
|
+
...(query?.trim() ? { query } : {}),
|
|
4520
|
+
...(statuses ? { statuses: statuses as SessionStatus[] } : {}),
|
|
4521
|
+
activeOnly: activeOnly === true,
|
|
4522
|
+
...(recentHours !== undefined ? { recentHours } : {}),
|
|
4523
|
+
...(rootSessionId ? { rootSessionId } : {}),
|
|
4524
|
+
...(parentSessionId !== undefined ? { parentSessionId } : {}),
|
|
4525
|
+
...(subject ? { subject: subject as WorkClaimSubjectFilter } : {}),
|
|
4526
|
+
...(claimLimit !== undefined ? { claimLimit } : {}),
|
|
4527
|
+
includeWorkDiscovery: deps.settings.workDiscoveryEnabled,
|
|
4528
|
+
subjectId: grant.subjectId,
|
|
4529
|
+
...(authorizationScope ? { authorizationScope } : {}),
|
|
4530
|
+
});
|
|
4531
|
+
const result = capSessionDiscoveryPage(page, includeLastMessage === true);
|
|
4532
|
+
observeWorkDiscovery(deps.observability, {
|
|
4533
|
+
surface: "first_party_mcp",
|
|
4534
|
+
mode,
|
|
4535
|
+
outcome: result.sessions.length === 0 ? "empty" : "ok",
|
|
4536
|
+
authorizationScope: metricAuthorizationScope,
|
|
4537
|
+
durationMs: performance.now() - startedAtMs,
|
|
4538
|
+
responseBytes: result.bytes,
|
|
4539
|
+
...summarizeWorkDiscoveryRows(result.sessions),
|
|
4540
|
+
});
|
|
4541
|
+
return json(result);
|
|
4542
|
+
} catch (error) {
|
|
4543
|
+
observeWorkDiscovery(deps.observability, {
|
|
4544
|
+
surface: "first_party_mcp",
|
|
4545
|
+
mode,
|
|
4546
|
+
outcome: "error",
|
|
4547
|
+
authorizationScope: metricAuthorizationScope,
|
|
4548
|
+
durationMs: performance.now() - startedAtMs,
|
|
4549
|
+
responseBytes: 0,
|
|
4550
|
+
resultCount: 0,
|
|
4551
|
+
overlapCount: 0,
|
|
4552
|
+
matchCounts: {},
|
|
4553
|
+
});
|
|
4554
|
+
throw error;
|
|
4036
4555
|
}
|
|
4037
|
-
const page = await listSessionDiscoverySummaries(deps.db, grant.workspaceId, {
|
|
4038
|
-
limit: boundedSessionDiscoveryLimit(limit),
|
|
4039
|
-
...(decodedCursor ? { cursor: decodedCursor } : {}),
|
|
4040
|
-
includeLastMessage: includeLastMessage === true,
|
|
4041
|
-
orderBy,
|
|
4042
|
-
...(normalizedUpdatedAfter ? { updatedAfter: normalizedUpdatedAfter } : {}),
|
|
4043
|
-
...(authorizationScope ? { authorizationScope } : {}),
|
|
4044
|
-
});
|
|
4045
|
-
return json(capSessionDiscoveryPage(page, includeLastMessage === true));
|
|
4046
4556
|
},
|
|
4047
4557
|
);
|
|
4048
4558
|
|
|
@@ -4199,7 +4709,7 @@ function registerWorkspaceOrchestrationTools(
|
|
|
4199
4709
|
server.registerTool(
|
|
4200
4710
|
"session_wait",
|
|
4201
4711
|
{
|
|
4202
|
-
description: `Block until a watched session has new durable events after your cursor, until your own session has pending machine input (a child result, an agent message, a steer), or until maxWaitSeconds (default ${SESSION_WAIT_DEFAULT_SECONDS}, max ${SESSION_WAIT_MAX_SECONDS}) elapses. Use this for short waits inside the current turn instead of sleeping and polling session_events/session_get/sessions_list while a child or peer session works; for long waits end this turn with goal_wait rather than looping session_wait for hours while holding the turn and sandbox. Pass each target's sessionId and afterSequence (its last seen sequence, 0 for a new session)
|
|
4712
|
+
description: `Block until a watched session has new durable events after your cursor, until your own session has pending machine input (a child result, an agent message, a steer), or until maxWaitSeconds (default ${SESSION_WAIT_DEFAULT_SECONDS}, max ${SESSION_WAIT_MAX_SECONDS}) elapses. Use this for short waits inside the current turn instead of sleeping and polling session_events/session_get/sessions_list while a child or peer session works; for long waits end this turn with goal_wait rather than looping session_wait for hours while holding the turn and sandbox. Pass each target's sessionId and afterSequence (its last seen sequence, 0 for a new session). waitFor=change is the backward-compatible default and returns on turn lifecycle, agent.message.completed, blocking failures, goal facts, or session status/control changes. waitFor=completion is the child-result join: it ignores progress, completed commentary messages, goal facts, maintenance turns, and continuation segment settlements and returns only for a result-bearing final turn or a blocking state. A goal.completed event records goal state but is not a terminal child result. Raw deltas, tool receipts, sandbox diagnostics, and unrelated progress never wake either mode. Each changed target returns a bounded compact summary of up to ${SESSION_WAIT_EVENTS_PER_TARGET} exact durable events plus latestSequence (pass it back as the next afterSequence) and hasMore (drill down with session_events after=latestSequence). ownPendingUpdates > 0 means your own session has machine input that is delivered only when your next turn is claimed: finish this turn to receive it, or pass includeOwnPendingUpdates=false to keep waiting on the targets. timedOut=true means nothing changed; liveFanout=false means the live bus was unavailable and the wait relied on the deadline re-check. The whole result is byte-bounded: summaries are shortened first, then newest rows dropped, so a changed target may come back with events=[] and hasMore=true; read those rows with session_events after=latestSequence. The wait cannot exceed ${SESSION_WAIT_MAX_SECONDS} seconds because the MCP client request timeout is 60 seconds.`,
|
|
4203
4713
|
inputSchema: {
|
|
4204
4714
|
targets: z4
|
|
4205
4715
|
.array(
|
|
@@ -4216,10 +4726,16 @@ function registerWorkspaceOrchestrationTools(
|
|
|
4216
4726
|
.describe(
|
|
4217
4727
|
"Also return when your own session has pending machine input (default true).",
|
|
4218
4728
|
),
|
|
4729
|
+
waitFor: z4
|
|
4730
|
+
.enum(["change", "completion"])
|
|
4731
|
+
.optional()
|
|
4732
|
+
.describe(
|
|
4733
|
+
"change (default) returns on relevant activity; completion ignores messages, goal/progress, maintenance, and continuation segments until a result-bearing final turn or blocker.",
|
|
4734
|
+
),
|
|
4219
4735
|
maxWaitSeconds: z4.number().int().min(1).max(SESSION_WAIT_MAX_SECONDS).optional(),
|
|
4220
4736
|
},
|
|
4221
4737
|
},
|
|
4222
|
-
async ({ targets, includeOwnPendingUpdates, maxWaitSeconds }, extra) => {
|
|
4738
|
+
async ({ targets, includeOwnPendingUpdates, waitFor, maxWaitSeconds }, extra) => {
|
|
4223
4739
|
const distinct = new Set(targets.map((target) => target.sessionId));
|
|
4224
4740
|
if (distinct.size !== targets.length) {
|
|
4225
4741
|
throw new Error("session_wait targets must name distinct sessions");
|
|
@@ -4231,6 +4747,8 @@ function registerWorkspaceOrchestrationTools(
|
|
|
4231
4747
|
await requireSession(deps.db, grant.workspaceId, target.sessionId);
|
|
4232
4748
|
}
|
|
4233
4749
|
const ownSessionId = includeOwnPendingUpdates === false ? null : callerSessionId;
|
|
4750
|
+
const targetEventTypes =
|
|
4751
|
+
waitFor === "completion" ? SESSION_WAIT_COMPLETION_EVENT_TYPES : SESSION_WAIT_EVENT_TYPES;
|
|
4234
4752
|
// The API serves one transport per POST, so the worker's MCP cancel
|
|
4235
4753
|
// notification never reaches this handler; the route binds the HTTP
|
|
4236
4754
|
// request's abort to transport.close() (mcp/request-abort.ts), which
|
|
@@ -4247,6 +4765,9 @@ function registerWorkspaceOrchestrationTools(
|
|
|
4247
4765
|
targets,
|
|
4248
4766
|
ownSessionId,
|
|
4249
4767
|
maxWaitMs: (maxWaitSeconds ?? SESSION_WAIT_DEFAULT_SECONDS) * 1_000,
|
|
4768
|
+
targetEventTypes,
|
|
4769
|
+
targetEventMatches:
|
|
4770
|
+
waitFor === "completion" ? sessionWaitCompletionEventMatches : undefined,
|
|
4250
4771
|
signal,
|
|
4251
4772
|
source: {
|
|
4252
4773
|
reauthorizeTargets: async (sessionIds) => {
|
|
@@ -4265,7 +4786,7 @@ function registerWorkspaceOrchestrationTools(
|
|
|
4265
4786
|
direction: "after",
|
|
4266
4787
|
limit: SESSION_WAIT_EVENTS_PER_TARGET,
|
|
4267
4788
|
payloadMode: "full",
|
|
4268
|
-
includeTypes:
|
|
4789
|
+
includeTypes: targetEventTypes,
|
|
4269
4790
|
maxBytes: SESSION_EVENT_MCP_MAX_BYTES * 4,
|
|
4270
4791
|
});
|
|
4271
4792
|
return { events: page.events, hasMore: page.hasMore };
|
|
@@ -4294,6 +4815,14 @@ function registerWorkspaceOrchestrationTools(
|
|
|
4294
4815
|
const sessionCreateInput = z4
|
|
4295
4816
|
.object({
|
|
4296
4817
|
initialMessage: z4.string().min(1),
|
|
4818
|
+
title: z4
|
|
4819
|
+
.string()
|
|
4820
|
+
.min(1)
|
|
4821
|
+
.max(SESSION_TITLE_MAX_CHARACTERS)
|
|
4822
|
+
.optional()
|
|
4823
|
+
.describe(
|
|
4824
|
+
"Concise semantic title for the child session. Omit only when the delegated goal or initial message already provides a suitable title; OpenGeni derives a sensitive-safe bounded fallback from that text.",
|
|
4825
|
+
),
|
|
4297
4826
|
instructions: z4.string().min(1).max(SESSION_INSTRUCTIONS_MAX_CHARACTERS).optional(),
|
|
4298
4827
|
goal: z4.unknown().optional(),
|
|
4299
4828
|
resources: z4.array(z4.unknown()).optional(),
|
|
@@ -4376,7 +4905,7 @@ function registerWorkspaceOrchestrationTools(
|
|
|
4376
4905
|
"session_create",
|
|
4377
4906
|
{
|
|
4378
4907
|
description:
|
|
4379
|
-
"Spawn a new agent session (a worker). The child inherits this session's visibility; a private session can only create a same-owner private child. Give a goal-bearing child its delegated objective. Its goal.rootConstraints may be an exact applicable subset of this accepted turn's frozen root constraints; omit that field to inherit all of them. Omit sandbox for the safe default: compatible children share the creator's box, while a different Variable Set, Rig, or machineTarget gets its own box. Use 'new' for deliberate isolation or {groupId} for a strict compatible sibling join. Put targetSandboxId and its optional workingDir together inside machineTarget; a machineTarget is always an own-box create even when the parent is backend none. To create a non-delegating leaf, pass a narrowed firstPartyMcpTools list that omits session_create; do not use a child-local depth override. Public REST/SDK callers retain advanced absolute depth and explicit shared-placement controls.",
|
|
4908
|
+
"Spawn a new agent session (a worker) only for a concrete, bounded subtask that can run independently and has a defined integration point in your current work. Do not delegate work you will also perform yourself; track the child and join its actual result before completing dependent work. Give the child a concise semantic title; if omitted, OpenGeni derives one from its delegated goal or initial message. The child inherits this session's visibility; a private session can only create a same-owner private child. Give a goal-bearing child its delegated objective. Its goal.rootConstraints may be an exact applicable subset of this accepted turn's frozen root constraints; omit that field to inherit all of them. Omit sandbox for the safe default: compatible children share the creator's box, while a different Variable Set, Rig, or machineTarget gets its own box. Use 'new' for deliberate isolation or {groupId} for a strict compatible sibling join. Put targetSandboxId and its optional workingDir together inside machineTarget; a machineTarget is always an own-box create even when the parent is backend none. To create a non-delegating leaf, pass a narrowed firstPartyMcpTools list that omits session_create; do not use a child-local depth override. Public REST/SDK callers retain advanced absolute depth and explicit shared-placement controls.",
|
|
4380
4909
|
inputSchema: sessionCreateInput,
|
|
4381
4910
|
},
|
|
4382
4911
|
async (args) => {
|
|
@@ -4389,18 +4918,25 @@ function registerWorkspaceOrchestrationTools(
|
|
|
4389
4918
|
if (callerSessionId !== null) {
|
|
4390
4919
|
await authorizeFirstPartySession(deps, grant, callerSessionId, "session.child.create");
|
|
4391
4920
|
}
|
|
4392
|
-
const { machineTarget, ...request } = args;
|
|
4393
|
-
const result = await createSessionForRequestWithOutcome(
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4921
|
+
const { machineTarget, title, ...request } = args;
|
|
4922
|
+
const result = await createSessionForRequestWithOutcome(
|
|
4923
|
+
deps,
|
|
4924
|
+
grant,
|
|
4925
|
+
grant.workspaceId,
|
|
4926
|
+
{
|
|
4927
|
+
...request,
|
|
4928
|
+
...(machineTarget
|
|
4929
|
+
? {
|
|
4930
|
+
targetSandboxId: machineTarget.targetSandboxId,
|
|
4931
|
+
...(machineTarget.workingDir !== undefined
|
|
4932
|
+
? { workingDir: machineTarget.workingDir }
|
|
4933
|
+
: {}),
|
|
4934
|
+
}
|
|
4935
|
+
: {}),
|
|
4936
|
+
},
|
|
4937
|
+
undefined,
|
|
4938
|
+
title === undefined ? {} : { automaticTitleCandidate: title },
|
|
4939
|
+
);
|
|
4404
4940
|
return json(sessionCreateMutationReceipt(result, Boolean(request.idempotencyKey)));
|
|
4405
4941
|
} catch (error) {
|
|
4406
4942
|
return orchestrationFailureResult("session_create", error);
|
|
@@ -5470,10 +6006,12 @@ function boundedSessionDiscoveryLimit(limit: number | undefined): number {
|
|
|
5470
6006
|
}
|
|
5471
6007
|
|
|
5472
6008
|
export function encodeSessionDiscoveryCursor(cursor: SessionDiscoveryCursor): string {
|
|
6009
|
+
const relevance = cursor.orderBy === "relevance";
|
|
5473
6010
|
return Buffer.from(
|
|
5474
6011
|
JSON.stringify({
|
|
5475
|
-
v: 2,
|
|
6012
|
+
v: relevance ? 3 : 2,
|
|
5476
6013
|
orderBy: cursor.orderBy,
|
|
6014
|
+
...(relevance ? { sortRank: cursor.sortRank, filterHash: cursor.filterHash } : {}),
|
|
5477
6015
|
sortRevision: cursor.sortRevision,
|
|
5478
6016
|
sortAt: cursor.sortAt,
|
|
5479
6017
|
id: cursor.id,
|
|
@@ -5523,6 +6061,8 @@ export function decodeSessionDiscoveryCursor(value: string): SessionDiscoveryCur
|
|
|
5523
6061
|
snapshotAt?: unknown;
|
|
5524
6062
|
snapshotRevision?: unknown;
|
|
5525
6063
|
updatedAfter?: unknown;
|
|
6064
|
+
sortRank?: unknown;
|
|
6065
|
+
filterHash?: unknown;
|
|
5526
6066
|
};
|
|
5527
6067
|
if (
|
|
5528
6068
|
parsed.v === undefined &&
|
|
@@ -5536,12 +6076,14 @@ export function decodeSessionDiscoveryCursor(value: string): SessionDiscoveryCur
|
|
|
5536
6076
|
);
|
|
5537
6077
|
return {
|
|
5538
6078
|
orderBy: "createdAt",
|
|
6079
|
+
sortRank: null,
|
|
5539
6080
|
sortRevision: "0",
|
|
5540
6081
|
sortAt: createdAt,
|
|
5541
6082
|
id: parsed.id,
|
|
5542
6083
|
snapshotAt: createdAt,
|
|
5543
6084
|
snapshotRevision: "0",
|
|
5544
6085
|
updatedAfter: null,
|
|
6086
|
+
filterHash: null,
|
|
5545
6087
|
};
|
|
5546
6088
|
}
|
|
5547
6089
|
// The timestamp-fenced v1 format was never safe for updated-order
|
|
@@ -5558,17 +6100,22 @@ export function decodeSessionDiscoveryCursor(value: string): SessionDiscoveryCur
|
|
|
5558
6100
|
) {
|
|
5559
6101
|
return {
|
|
5560
6102
|
orderBy: "createdAt",
|
|
6103
|
+
sortRank: null,
|
|
5561
6104
|
sortRevision: "0",
|
|
5562
6105
|
sortAt: normalizeSessionDiscoveryTimestamp(parsed.sortAt, "cursor sortAt"),
|
|
5563
6106
|
id: parsed.id,
|
|
5564
6107
|
snapshotAt: normalizeSessionDiscoveryTimestamp(parsed.snapshotAt, "cursor snapshotAt"),
|
|
5565
6108
|
snapshotRevision: "0",
|
|
5566
6109
|
updatedAfter: null,
|
|
6110
|
+
filterHash: null,
|
|
5567
6111
|
};
|
|
5568
6112
|
}
|
|
6113
|
+
const isV2 = parsed.v === 2;
|
|
6114
|
+
const isV3 = parsed.v === 3;
|
|
5569
6115
|
if (
|
|
5570
|
-
|
|
5571
|
-
(parsed.orderBy !== "createdAt" && parsed.orderBy !== "updatedAt") ||
|
|
6116
|
+
(!isV2 && !isV3) ||
|
|
6117
|
+
(isV2 && parsed.orderBy !== "createdAt" && parsed.orderBy !== "updatedAt") ||
|
|
6118
|
+
(isV3 && parsed.orderBy !== "relevance") ||
|
|
5572
6119
|
typeof parsed.sortRevision !== "string" ||
|
|
5573
6120
|
typeof parsed.sortAt !== "string" ||
|
|
5574
6121
|
typeof parsed.snapshotAt !== "string" ||
|
|
@@ -5579,6 +6126,18 @@ export function decodeSessionDiscoveryCursor(value: string): SessionDiscoveryCur
|
|
|
5579
6126
|
) {
|
|
5580
6127
|
throw new Error("invalid cursor fields");
|
|
5581
6128
|
}
|
|
6129
|
+
if (
|
|
6130
|
+
isV3 &&
|
|
6131
|
+
(!Number.isSafeInteger(parsed.sortRank) ||
|
|
6132
|
+
(parsed.sortRank as number) < 0 ||
|
|
6133
|
+
typeof parsed.filterHash !== "string" ||
|
|
6134
|
+
!/^[0-9a-f]{64}$/.test(parsed.filterHash))
|
|
6135
|
+
) {
|
|
6136
|
+
throw new Error("invalid relevance cursor fields");
|
|
6137
|
+
}
|
|
6138
|
+
if (isV2 && (parsed.sortRank !== undefined || parsed.filterHash !== undefined)) {
|
|
6139
|
+
throw new Error("chronological cursor cannot carry relevance fields");
|
|
6140
|
+
}
|
|
5582
6141
|
const sortAt = normalizeSessionDiscoveryTimestamp(parsed.sortAt, "cursor sortAt");
|
|
5583
6142
|
const snapshotAt = normalizeSessionDiscoveryTimestamp(parsed.snapshotAt, "cursor snapshotAt");
|
|
5584
6143
|
const sortRevision = normalizeSessionDiscoveryRevision(
|
|
@@ -5599,14 +6158,19 @@ export function decodeSessionDiscoveryCursor(value: string): SessionDiscoveryCur
|
|
|
5599
6158
|
if (parsed.orderBy === "createdAt" && (sortRevision !== "0" || snapshotRevision !== "0")) {
|
|
5600
6159
|
throw new Error("creation cursor cannot carry activity revisions");
|
|
5601
6160
|
}
|
|
6161
|
+
const orderBy: SessionDiscoveryOrderBy = isV3
|
|
6162
|
+
? "relevance"
|
|
6163
|
+
: (parsed.orderBy as "createdAt" | "updatedAt");
|
|
5602
6164
|
return {
|
|
5603
|
-
orderBy
|
|
6165
|
+
orderBy,
|
|
6166
|
+
sortRank: isV3 ? (parsed.sortRank as number) : null,
|
|
5604
6167
|
sortRevision,
|
|
5605
6168
|
sortAt,
|
|
5606
6169
|
id: parsed.id,
|
|
5607
6170
|
snapshotAt,
|
|
5608
6171
|
snapshotRevision,
|
|
5609
6172
|
updatedAfter: normalizedUpdatedAfter,
|
|
6173
|
+
filterHash: isV3 ? (parsed.filterHash as string) : null,
|
|
5610
6174
|
};
|
|
5611
6175
|
} catch {
|
|
5612
6176
|
throw new Error("sessions_list cursor is invalid");
|
|
@@ -5697,6 +6261,7 @@ export function capSessionDiscoveryPage(
|
|
|
5697
6261
|
: null,
|
|
5698
6262
|
queuedPromptCount: session.queuedPromptCount,
|
|
5699
6263
|
children: session.treeStats,
|
|
6264
|
+
relatedWork: session.workDiscovery,
|
|
5700
6265
|
...(includeLastMessage
|
|
5701
6266
|
? {
|
|
5702
6267
|
latestMessage: session.latestMessage
|
|
@@ -5774,12 +6339,14 @@ export function capSessionDiscoveryPage(
|
|
|
5774
6339
|
? sourceLast
|
|
5775
6340
|
? encodeSessionDiscoveryCursor({
|
|
5776
6341
|
orderBy: page.orderBy,
|
|
6342
|
+
sortRank: sourceLast.sortRank,
|
|
5777
6343
|
sortRevision: sourceLast.sortRevision,
|
|
5778
6344
|
sortAt: sourceLast.sortAt,
|
|
5779
6345
|
id: sourceLast.id,
|
|
5780
6346
|
snapshotAt: page.snapshotAt,
|
|
5781
6347
|
snapshotRevision: page.snapshotRevision,
|
|
5782
6348
|
updatedAfter: page.updatedAfter,
|
|
6349
|
+
filterHash: page.filterHash,
|
|
5783
6350
|
})
|
|
5784
6351
|
: null
|
|
5785
6352
|
: page.nextCursor
|