@opengeni/api-router 2.3.2-canary.2 → 2.5.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.
Files changed (44) hide show
  1. package/dist/app.js +1 -1
  2. package/dist/auth/managed-auth-attempt-context.d.ts +4 -0
  3. package/dist/auth/managed-auth-session-adapter.d.ts +4 -0
  4. package/dist/{chunk-IBV7Z6F4.js → chunk-QESX7HDK.js} +3645 -470
  5. package/dist/chunk-QESX7HDK.js.map +1 -0
  6. package/dist/fatal-process-boundary.d.ts +25 -0
  7. package/dist/http/sse.d.ts +2 -0
  8. package/dist/index.d.ts +5 -1
  9. package/dist/index.js +168 -6
  10. package/dist/index.js.map +1 -1
  11. package/dist/integrations/slack-interactions.d.ts +1 -1
  12. package/dist/mcp/receipts.d.ts +9 -0
  13. package/dist/mcp/server.d.ts +33 -0
  14. package/dist/organization-recovery-notifications.d.ts +41 -0
  15. package/dist/routes/managed-auth-session-sets.d.ts +19 -0
  16. package/dist/routes/organization-recovery.d.ts +19 -0
  17. package/dist/routes/sessions.d.ts +17 -5
  18. package/dist/routes/workspaces.d.ts +1 -0
  19. package/dist/work-discovery-observability.d.ts +33 -0
  20. package/package.json +18 -18
  21. package/src/app.ts +133 -1
  22. package/src/auth/managed-auth-attempt-context.ts +24 -0
  23. package/src/auth/managed-auth-session-adapter.ts +205 -0
  24. package/src/auth/managed-auth.ts +52 -2
  25. package/src/fatal-process-boundary.ts +231 -0
  26. package/src/http/sse.ts +7 -0
  27. package/src/index.ts +25 -5
  28. package/src/integrations/slack-interactions.ts +30 -17
  29. package/src/mcp/receipts.ts +34 -0
  30. package/src/mcp/server.ts +349 -68
  31. package/src/organization-recovery-notifications.ts +103 -0
  32. package/src/routes/canonical-human-identities.ts +29 -14
  33. package/src/routes/codex.ts +5 -1
  34. package/src/routes/environments.ts +23 -0
  35. package/src/routes/interaction-resources.ts +3 -0
  36. package/src/routes/managed-auth-session-sets.ts +994 -0
  37. package/src/routes/managed-onboarding.ts +2 -0
  38. package/src/routes/organization-memberships.ts +2 -0
  39. package/src/routes/organization-recovery.ts +325 -0
  40. package/src/routes/sessions.ts +401 -120
  41. package/src/routes/supergrok.ts +5 -1
  42. package/src/routes/workspaces.ts +23 -1
  43. package/src/work-discovery-observability.ts +121 -0
  44. package/dist/chunk-IBV7Z6F4.js.map +0 -1
package/src/mcp/server.ts CHANGED
@@ -45,6 +45,16 @@ import {
45
45
  TASK_NOTE_MAX_LIFETIME_DAYS,
46
46
  TASK_NOTE_REASON_MAX_BYTES,
47
47
  TASK_NOTE_TEXT_MAX_BYTES,
48
+ WORK_CLAIM_CANONICAL_KEY_MAX_BYTES,
49
+ WORK_CLAIM_DISPLAY_LABEL_MAX_BYTES,
50
+ WORK_CLAIM_DISCOVERY_LIMIT,
51
+ WORK_CLAIM_NAMESPACE_MAX_BYTES,
52
+ WORK_CLAIM_VERSION_VALUE_MAX_BYTES,
53
+ WORK_DISCOVERY_QUERY_MAX_CHARS,
54
+ WORK_DISCOVERY_RECENT_HOURS_MAX,
55
+ WorkClaimSubjectType,
56
+ type WorkClaimSubjectFilter,
57
+ type SessionStatus,
48
58
  SubmitHumanInputResponseRequest,
49
59
  } from "@opengeni/contracts";
50
60
  import {
@@ -104,6 +114,8 @@ import {
104
114
  createTaskNote,
105
115
  listTaskNotes,
106
116
  replaceTaskNote,
117
+ releaseWorkClaim,
118
+ upsertWorkClaim,
107
119
  acceptSessionHumanInputResponse,
108
120
  HumanInputResponseValidationError,
109
121
  } from "@opengeni/db";
@@ -186,7 +198,7 @@ import {
186
198
  import {
187
199
  acceptSessionUserMessageWithOutcome,
188
200
  controlAgentSessionWorkstream,
189
- controlHumanSessionWorkstream,
201
+ controlHumanSessionWorkstreamWithOutcome,
190
202
  createSessionForRequestWithOutcome,
191
203
  SessionSpawnDeniedError,
192
204
  sessionSpawnDenialEnvelope,
@@ -223,7 +235,11 @@ import {
223
235
  SESSION_WAIT_MAX_TARGETS,
224
236
  waitForSessionChanges,
225
237
  } from "./session-wait";
226
- import { mcpMutationReceipt, sessionCreateMutationReceipt } from "./receipts";
238
+ import {
239
+ mcpMutationReceipt,
240
+ sessionControlMutationReceipt,
241
+ sessionCreateMutationReceipt,
242
+ } from "./receipts";
227
243
  import {
228
244
  boundScheduledTaskDetailMcp,
229
245
  boundScheduledTaskMcpPage,
@@ -247,6 +263,7 @@ import { registerCompanyProfileAgentAdminTools } from "./company-profile-agent-a
247
263
  import { registerRememberTools } from "./remember";
248
264
  import { mintSandboxCodemodeToken } from "@opengeni/runtime/sandbox";
249
265
  import { deleteScheduledTaskWithDurableCleanup } from "../scheduled-task-deletion";
266
+ import { observeWorkDiscovery, summarizeWorkDiscoveryRows } from "../work-discovery-observability";
250
267
 
251
268
  export type McpServerOptions = {
252
269
  // Origin of the HTTP request that reached the MCP route. Browser-oriented
@@ -406,6 +423,8 @@ const FIRST_PARTY_TOOL_AUTHORIZATION = {
406
423
  task_note_save: { sessionRequired: true, allOf: ["sessions:control"] },
407
424
  task_note_archive: { sessionRequired: true, allOf: ["sessions:control"] },
408
425
  task_note_replace: { sessionRequired: true, allOf: ["sessions:control"] },
426
+ work_claim_upsert: { sessionRequired: true, allOf: ["sessions:control"] },
427
+ work_claim_release: { sessionRequired: true, allOf: ["sessions:control"] },
409
428
  knowledge_propose: { sessionRequired: true, allOf: ["documents:search"] },
410
429
  knowledge_correct: { sessionRequired: true, allOf: ["documents:search"] },
411
430
  task_note_promote_knowledge: {
@@ -787,6 +806,9 @@ export function buildOpenGeniMcpServer(
787
806
  if (sessionId !== null && exactAgentAttemptClaims(grant) !== null) {
788
807
  registerPreferenceRegistryTools(server, deps, grant, json);
789
808
  registerTaskNoteTools(server, deps, grant, sessionId, json);
809
+ if (deps.settings.workClaimMutationsEnabled) {
810
+ registerWorkClaimTools(server, deps, grant, sessionId, json);
811
+ }
790
812
  const attempt = exactAgentAttemptClaims(grant)!;
791
813
  registerCompanyBrainGovernedWriteTools({
792
814
  server,
@@ -3237,6 +3259,133 @@ function registerTaskNoteTools(
3237
3259
  );
3238
3260
  }
3239
3261
 
3262
+ function registerWorkClaimTools(
3263
+ server: McpServer,
3264
+ deps: ApiRouteDeps,
3265
+ grant: AccessGrant,
3266
+ sessionId: string,
3267
+ json: JsonResult,
3268
+ ): void {
3269
+ const attemptClaims = () => {
3270
+ const resolved = exactAgentAttemptClaims(grant);
3271
+ if (!resolved || resolved.sessionId !== sessionId) {
3272
+ throw new Error("Exact signed work-claim attempt authority is required.");
3273
+ }
3274
+ return {
3275
+ accountId: grant.accountId,
3276
+ workspaceId: grant.workspaceId,
3277
+ ...resolved,
3278
+ };
3279
+ };
3280
+ const authorize = async () => {
3281
+ await authorizeFirstPartySession(deps, grant, sessionId, "session.first_party_mcp.call");
3282
+ };
3283
+
3284
+ server.registerTool(
3285
+ "work_claim_upsert",
3286
+ {
3287
+ description:
3288
+ "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.",
3289
+ inputSchema: {
3290
+ operationId: z4.string().uuid(),
3291
+ expectedRevision: z4.number().int().min(0),
3292
+ subjectNamespace: z4.string().min(1).max(WORK_CLAIM_NAMESPACE_MAX_BYTES),
3293
+ subjectType: z4.enum([
3294
+ "repository",
3295
+ "branch",
3296
+ "pull_request",
3297
+ "issue",
3298
+ "artifact",
3299
+ "release",
3300
+ "ci_run",
3301
+ "other",
3302
+ ]),
3303
+ canonicalKey: z4.string().min(1).max(WORK_CLAIM_CANONICAL_KEY_MAX_BYTES),
3304
+ displayLabel: z4.string().min(1).max(WORK_CLAIM_DISPLAY_LABEL_MAX_BYTES).optional(),
3305
+ role: z4.enum(["working", "reviewing", "monitoring", "delivering"]),
3306
+ versionKind: z4
3307
+ .enum([
3308
+ "git_commit",
3309
+ "branch_head",
3310
+ "pull_request_head",
3311
+ "artifact_version",
3312
+ "release_version",
3313
+ "ci_run",
3314
+ "other",
3315
+ ])
3316
+ .optional(),
3317
+ versionValue: z4.string().min(1).max(WORK_CLAIM_VERSION_VALUE_MAX_BYTES).optional(),
3318
+ },
3319
+ },
3320
+ async ({
3321
+ operationId,
3322
+ expectedRevision,
3323
+ subjectNamespace,
3324
+ subjectType,
3325
+ canonicalKey,
3326
+ displayLabel,
3327
+ role,
3328
+ versionKind,
3329
+ versionValue,
3330
+ }) => {
3331
+ await authorize();
3332
+ if ((versionKind === undefined) !== (versionValue === undefined)) {
3333
+ throw new Error("work_claim_upsert versionKind and versionValue must be supplied together");
3334
+ }
3335
+ return json(
3336
+ await upsertWorkClaim(deps.db, {
3337
+ ...attemptClaims(),
3338
+ operationId,
3339
+ expectedRevision,
3340
+ subjectNamespace,
3341
+ subjectType,
3342
+ canonicalKey,
3343
+ ...(displayLabel === undefined ? {} : { displayLabel }),
3344
+ role,
3345
+ ...(versionKind && versionValue
3346
+ ? { version: { kind: versionKind, value: versionValue } }
3347
+ : {}),
3348
+ }),
3349
+ );
3350
+ },
3351
+ );
3352
+
3353
+ server.registerTool(
3354
+ "work_claim_release",
3355
+ {
3356
+ description:
3357
+ "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.",
3358
+ inputSchema: {
3359
+ operationId: z4.string().uuid(),
3360
+ claimId: z4.string().uuid(),
3361
+ expectedRevision: z4.number().int().min(1),
3362
+ reason: z4.enum([
3363
+ "completed",
3364
+ "cancelled",
3365
+ "failed",
3366
+ "superseded",
3367
+ "no_longer_active",
3368
+ "corrected",
3369
+ "external_state_changed",
3370
+ "other",
3371
+ ]),
3372
+ },
3373
+ },
3374
+ async ({ operationId, claimId, expectedRevision, reason }) => {
3375
+ await authorize();
3376
+ return json(
3377
+ await releaseWorkClaim(deps.db, {
3378
+ ...attemptClaims(),
3379
+ operationId,
3380
+ claimId,
3381
+ expectedRevision,
3382
+ reason,
3383
+ }),
3384
+ );
3385
+ },
3386
+ );
3387
+ }
3388
+
3240
3389
  function registerPreferenceRegistryTools(
3241
3390
  server: McpServer,
3242
3391
  deps: ApiRouteDeps,
@@ -3999,46 +4148,145 @@ function registerWorkspaceOrchestrationTools(
3999
4148
  server.registerTool(
4000
4149
  "sessions_list",
4001
4150
  {
4002
- description: `List compact high-level session status in this workspace. Defaults to creation order; use orderBy=updatedAt with decimal activity-revision updatedAfter/updatedThrough tokens for gap-free indexed incremental monitoring independent of application clocks. Cursors are opaque revision-fenced keysets. 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 for exact known targets and detailed resources/tools/settings. The list never returns full session objects or history.`,
4151
+ 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.`,
4003
4152
  inputSchema: {
4004
4153
  limit: z4.number().int().positive().max(100).optional(),
4005
4154
  cursor: z4.string().max(512).optional(),
4006
4155
  includeLastMessage: z4.boolean().optional(),
4007
- orderBy: z4.enum(["createdAt", "updatedAt"]).optional(),
4156
+ orderBy: z4.enum(["createdAt", "updatedAt", "relevance"]).optional(),
4008
4157
  updatedAfter: z4.string().max(64).optional(),
4158
+ query: z4.string().max(WORK_DISCOVERY_QUERY_MAX_CHARS).optional(),
4159
+ statuses: z4
4160
+ .array(
4161
+ z4.enum([
4162
+ "queued",
4163
+ "running",
4164
+ "idle",
4165
+ "requires_action",
4166
+ "recovering",
4167
+ "waiting_capacity",
4168
+ "failed",
4169
+ "cancelled",
4170
+ ]),
4171
+ )
4172
+ .max(8)
4173
+ .optional(),
4174
+ activeOnly: z4.boolean().optional(),
4175
+ recentHours: z4.number().int().positive().max(WORK_DISCOVERY_RECENT_HOURS_MAX).optional(),
4176
+ rootSessionId: z4.string().uuid().optional(),
4177
+ parentSessionId: z4.string().uuid().nullable().optional(),
4178
+ subject: z4
4179
+ .object({
4180
+ namespace: z4.string().min(1).max(WORK_CLAIM_NAMESPACE_MAX_BYTES),
4181
+ type: WorkClaimSubjectType,
4182
+ canonicalKey: z4.string().min(1).max(WORK_CLAIM_CANONICAL_KEY_MAX_BYTES),
4183
+ })
4184
+ .strict()
4185
+ .optional(),
4186
+ claimLimit: z4.number().int().positive().max(WORK_CLAIM_DISCOVERY_LIMIT).optional(),
4009
4187
  },
4010
4188
  },
4011
- async ({ limit, cursor, includeLastMessage, orderBy: requestedOrderBy, updatedAfter }) => {
4189
+ async ({
4190
+ limit,
4191
+ cursor,
4192
+ includeLastMessage,
4193
+ orderBy: requestedOrderBy,
4194
+ updatedAfter,
4195
+ query,
4196
+ statuses,
4197
+ activeOnly,
4198
+ recentHours,
4199
+ rootSessionId,
4200
+ parentSessionId,
4201
+ subject,
4202
+ claimLimit,
4203
+ }) => {
4012
4204
  const authorizationScope = await requireSessionAuthorizationListScope(
4013
4205
  deps,
4014
4206
  grant,
4015
4207
  "first_party_mcp",
4016
4208
  );
4209
+ const startedAtMs = performance.now();
4210
+ const mode = subject ? "subject" : query?.trim() ? "query" : "browse";
4211
+ const metricAuthorizationScope =
4212
+ authorizationScope?.kind === "scoped" ? "scoped" : "workspace";
4017
4213
  const decodedCursor = cursor ? decodeSessionDiscoveryCursor(cursor) : undefined;
4018
- const orderBy: SessionDiscoveryOrderBy =
4019
- requestedOrderBy ?? decodedCursor?.orderBy ?? "createdAt";
4020
- if (decodedCursor && decodedCursor.orderBy !== orderBy) {
4021
- throw new Error("sessions_list cursor order does not match orderBy");
4022
- }
4023
- const normalizedUpdatedAfter =
4024
- updatedAfter !== undefined
4025
- ? normalizeSessionDiscoveryRevision(updatedAfter, "updatedAfter")
4026
- : (decodedCursor?.updatedAfter ?? undefined);
4027
- if (normalizedUpdatedAfter !== undefined && orderBy !== "updatedAt") {
4028
- throw new Error("sessions_list updatedAfter requires orderBy=updatedAt");
4214
+ const relevanceRequested = Boolean(query?.trim() || subject);
4215
+ if (relevanceRequested && !deps.settings.workDiscoveryEnabled) {
4216
+ observeWorkDiscovery(deps.observability, {
4217
+ surface: "first_party_mcp",
4218
+ mode,
4219
+ outcome: "disabled",
4220
+ authorizationScope: metricAuthorizationScope,
4221
+ durationMs: performance.now() - startedAtMs,
4222
+ responseBytes: 0,
4223
+ resultCount: 0,
4224
+ overlapCount: 0,
4225
+ matchCounts: {},
4226
+ });
4227
+ throw new Error("sessions_list work discovery is disabled by the operator");
4029
4228
  }
4030
- if (decodedCursor && decodedCursor.updatedAfter !== (normalizedUpdatedAfter ?? null)) {
4031
- throw new Error("sessions_list cursor does not match updatedAfter");
4229
+ try {
4230
+ const orderBy: SessionDiscoveryOrderBy =
4231
+ requestedOrderBy ??
4232
+ decodedCursor?.orderBy ??
4233
+ (relevanceRequested ? "relevance" : "createdAt");
4234
+ if (decodedCursor && decodedCursor.orderBy !== orderBy) {
4235
+ throw new Error("sessions_list cursor order does not match orderBy");
4236
+ }
4237
+ const normalizedUpdatedAfter =
4238
+ updatedAfter !== undefined
4239
+ ? normalizeSessionDiscoveryRevision(updatedAfter, "updatedAfter")
4240
+ : (decodedCursor?.updatedAfter ?? undefined);
4241
+ if (normalizedUpdatedAfter !== undefined && orderBy !== "updatedAt") {
4242
+ throw new Error("sessions_list updatedAfter requires orderBy=updatedAt");
4243
+ }
4244
+ if (decodedCursor && decodedCursor.updatedAfter !== (normalizedUpdatedAfter ?? null)) {
4245
+ throw new Error("sessions_list cursor does not match updatedAfter");
4246
+ }
4247
+ const page = await listSessionDiscoverySummaries(deps.db, grant.workspaceId, {
4248
+ limit: boundedSessionDiscoveryLimit(limit),
4249
+ ...(decodedCursor ? { cursor: decodedCursor } : {}),
4250
+ includeLastMessage: includeLastMessage === true,
4251
+ orderBy,
4252
+ ...(normalizedUpdatedAfter ? { updatedAfter: normalizedUpdatedAfter } : {}),
4253
+ ...(query?.trim() ? { query } : {}),
4254
+ ...(statuses ? { statuses: statuses as SessionStatus[] } : {}),
4255
+ activeOnly: activeOnly === true,
4256
+ ...(recentHours !== undefined ? { recentHours } : {}),
4257
+ ...(rootSessionId ? { rootSessionId } : {}),
4258
+ ...(parentSessionId !== undefined ? { parentSessionId } : {}),
4259
+ ...(subject ? { subject: subject as WorkClaimSubjectFilter } : {}),
4260
+ ...(claimLimit !== undefined ? { claimLimit } : {}),
4261
+ includeWorkDiscovery: deps.settings.workDiscoveryEnabled,
4262
+ subjectId: grant.subjectId,
4263
+ ...(authorizationScope ? { authorizationScope } : {}),
4264
+ });
4265
+ const result = capSessionDiscoveryPage(page, includeLastMessage === true);
4266
+ observeWorkDiscovery(deps.observability, {
4267
+ surface: "first_party_mcp",
4268
+ mode,
4269
+ outcome: result.sessions.length === 0 ? "empty" : "ok",
4270
+ authorizationScope: metricAuthorizationScope,
4271
+ durationMs: performance.now() - startedAtMs,
4272
+ responseBytes: result.bytes,
4273
+ ...summarizeWorkDiscoveryRows(result.sessions),
4274
+ });
4275
+ return json(result);
4276
+ } catch (error) {
4277
+ observeWorkDiscovery(deps.observability, {
4278
+ surface: "first_party_mcp",
4279
+ mode,
4280
+ outcome: "error",
4281
+ authorizationScope: metricAuthorizationScope,
4282
+ durationMs: performance.now() - startedAtMs,
4283
+ responseBytes: 0,
4284
+ resultCount: 0,
4285
+ overlapCount: 0,
4286
+ matchCounts: {},
4287
+ });
4288
+ throw error;
4032
4289
  }
4033
- const page = await listSessionDiscoverySummaries(deps.db, grant.workspaceId, {
4034
- limit: boundedSessionDiscoveryLimit(limit),
4035
- ...(decodedCursor ? { cursor: decodedCursor } : {}),
4036
- includeLastMessage: includeLastMessage === true,
4037
- orderBy,
4038
- ...(normalizedUpdatedAfter ? { updatedAfter: normalizedUpdatedAfter } : {}),
4039
- ...(authorizationScope ? { authorizationScope } : {}),
4040
- });
4041
- return json(capSessionDiscoveryPage(page, includeLastMessage === true));
4042
4290
  },
4043
4291
  );
4044
4292
 
@@ -4537,27 +4785,18 @@ function registerWorkspaceOrchestrationTools(
4537
4785
  controlled.authorization?.relatedSessionAccess ?? "root",
4538
4786
  );
4539
4787
  return json(
4540
- mcpMutationReceipt({
4788
+ sessionControlMutationReceipt({
4541
4789
  operation: "session_pause",
4542
- committed: true,
4543
- outcome: controlled.replay ? "replayed" : "updated",
4544
- changed: !controlled.replay,
4545
- resource: {
4546
- type: "session",
4547
- id: sessionId,
4548
- state: effectiveControl.state,
4549
- },
4550
- relatedResources: [{ type: "session_command_receipt", id: controlled.receipt.id }],
4790
+ sessionId,
4791
+ state: effectiveControl.state,
4792
+ receiptId: controlled.receipt.id,
4551
4793
  timestamp: controlled.receipt.createdAt.toISOString(),
4552
- idempotency: {
4553
- status: controlled.replay ? "replayed" : "applied",
4554
- },
4555
- facts: { interruptionCount: controlled.interruptionCount },
4556
- nextAction: { tool: "session_get", arguments: { sessionId } },
4794
+ outcome: controlled.outcome,
4795
+ interruptionCount: controlled.interruptionCount,
4557
4796
  }),
4558
4797
  );
4559
4798
  }
4560
- const controlled = await controlHumanSessionWorkstream(
4799
+ const controlled = await controlHumanSessionWorkstreamWithOutcome(
4561
4800
  deps,
4562
4801
  {
4563
4802
  accountId: grant.accountId,
@@ -4572,7 +4811,17 @@ function registerWorkspaceOrchestrationTools(
4572
4811
  ...(reason ? { reason } : {}),
4573
4812
  },
4574
4813
  );
4575
- return json(controlled);
4814
+ return json(
4815
+ sessionControlMutationReceipt({
4816
+ operation: "session_pause",
4817
+ sessionId,
4818
+ state: controlled.response.effectiveControl.state,
4819
+ receiptId: controlled.response.receipt.id,
4820
+ timestamp: controlled.response.receipt.createdAt,
4821
+ outcome: controlled.outcome,
4822
+ interruptionCount: controlled.response.interruptionCount,
4823
+ }),
4824
+ );
4576
4825
  },
4577
4826
  );
4578
4827
 
@@ -4605,27 +4854,18 @@ function registerWorkspaceOrchestrationTools(
4605
4854
  controlled.authorization?.relatedSessionAccess ?? "root",
4606
4855
  );
4607
4856
  return json(
4608
- mcpMutationReceipt({
4857
+ sessionControlMutationReceipt({
4609
4858
  operation: "session_resume",
4610
- committed: true,
4611
- outcome: controlled.replay ? "replayed" : "updated",
4612
- changed: !controlled.replay,
4613
- resource: {
4614
- type: "session",
4615
- id: sessionId,
4616
- state: effectiveControl.state,
4617
- },
4618
- relatedResources: [{ type: "session_command_receipt", id: controlled.receipt.id }],
4859
+ sessionId,
4860
+ state: effectiveControl.state,
4861
+ receiptId: controlled.receipt.id,
4619
4862
  timestamp: controlled.receipt.createdAt.toISOString(),
4620
- idempotency: {
4621
- status: controlled.replay ? "replayed" : "applied",
4622
- },
4623
- facts: { interruptionCount: controlled.interruptionCount },
4624
- nextAction: { tool: "session_get", arguments: { sessionId } },
4863
+ outcome: controlled.outcome,
4864
+ interruptionCount: controlled.interruptionCount,
4625
4865
  }),
4626
4866
  );
4627
4867
  }
4628
- const controlled = await controlHumanSessionWorkstream(
4868
+ const controlled = await controlHumanSessionWorkstreamWithOutcome(
4629
4869
  deps,
4630
4870
  {
4631
4871
  accountId: grant.accountId,
@@ -4640,7 +4880,17 @@ function registerWorkspaceOrchestrationTools(
4640
4880
  ...(reason ? { reason } : {}),
4641
4881
  },
4642
4882
  );
4643
- return json(controlled);
4883
+ return json(
4884
+ sessionControlMutationReceipt({
4885
+ operation: "session_resume",
4886
+ sessionId,
4887
+ state: controlled.response.effectiveControl.state,
4888
+ receiptId: controlled.response.receipt.id,
4889
+ timestamp: controlled.response.receipt.createdAt,
4890
+ outcome: controlled.outcome,
4891
+ interruptionCount: controlled.response.interruptionCount,
4892
+ }),
4893
+ );
4644
4894
  },
4645
4895
  );
4646
4896
 
@@ -4741,13 +4991,13 @@ function registerWorkspaceOrchestrationTools(
4741
4991
  });
4742
4992
  }
4743
4993
  if (accepted.action === "conflict") {
4744
- throw new Error(`human-input request is ${accepted.request.status}`);
4994
+ throw new Error("human-input request is not currently actionable");
4745
4995
  }
4746
4996
  return json(
4747
4997
  mcpMutationReceipt({
4748
4998
  operation: "session_human_input_respond",
4749
4999
  committed: true,
4750
- outcome: accepted.events.length === 0 ? "replayed" : "updated",
5000
+ outcome: accepted.action === "completed" ? "replayed" : "updated",
4751
5001
  changed: accepted.events.length > 0,
4752
5002
  resource: {
4753
5003
  type: "session_human_input_request",
@@ -4757,7 +5007,7 @@ function registerWorkspaceOrchestrationTools(
4757
5007
  relatedResources: [{ type: "session", id: sessionId }],
4758
5008
  timestamp: accepted.request.respondedAt ?? new Date().toISOString(),
4759
5009
  idempotency: {
4760
- status: accepted.events.length === 0 ? "replayed" : "applied",
5010
+ status: accepted.action === "completed" ? "replayed" : "applied",
4761
5011
  },
4762
5012
  facts: { outcome: accepted.request.response?.outcome ?? null },
4763
5013
  nextAction: { tool: "session_get", arguments: { sessionId } },
@@ -5464,10 +5714,12 @@ function boundedSessionDiscoveryLimit(limit: number | undefined): number {
5464
5714
  }
5465
5715
 
5466
5716
  export function encodeSessionDiscoveryCursor(cursor: SessionDiscoveryCursor): string {
5717
+ const relevance = cursor.orderBy === "relevance";
5467
5718
  return Buffer.from(
5468
5719
  JSON.stringify({
5469
- v: 2,
5720
+ v: relevance ? 3 : 2,
5470
5721
  orderBy: cursor.orderBy,
5722
+ ...(relevance ? { sortRank: cursor.sortRank, filterHash: cursor.filterHash } : {}),
5471
5723
  sortRevision: cursor.sortRevision,
5472
5724
  sortAt: cursor.sortAt,
5473
5725
  id: cursor.id,
@@ -5517,6 +5769,8 @@ export function decodeSessionDiscoveryCursor(value: string): SessionDiscoveryCur
5517
5769
  snapshotAt?: unknown;
5518
5770
  snapshotRevision?: unknown;
5519
5771
  updatedAfter?: unknown;
5772
+ sortRank?: unknown;
5773
+ filterHash?: unknown;
5520
5774
  };
5521
5775
  if (
5522
5776
  parsed.v === undefined &&
@@ -5530,12 +5784,14 @@ export function decodeSessionDiscoveryCursor(value: string): SessionDiscoveryCur
5530
5784
  );
5531
5785
  return {
5532
5786
  orderBy: "createdAt",
5787
+ sortRank: null,
5533
5788
  sortRevision: "0",
5534
5789
  sortAt: createdAt,
5535
5790
  id: parsed.id,
5536
5791
  snapshotAt: createdAt,
5537
5792
  snapshotRevision: "0",
5538
5793
  updatedAfter: null,
5794
+ filterHash: null,
5539
5795
  };
5540
5796
  }
5541
5797
  // The timestamp-fenced v1 format was never safe for updated-order
@@ -5552,17 +5808,22 @@ export function decodeSessionDiscoveryCursor(value: string): SessionDiscoveryCur
5552
5808
  ) {
5553
5809
  return {
5554
5810
  orderBy: "createdAt",
5811
+ sortRank: null,
5555
5812
  sortRevision: "0",
5556
5813
  sortAt: normalizeSessionDiscoveryTimestamp(parsed.sortAt, "cursor sortAt"),
5557
5814
  id: parsed.id,
5558
5815
  snapshotAt: normalizeSessionDiscoveryTimestamp(parsed.snapshotAt, "cursor snapshotAt"),
5559
5816
  snapshotRevision: "0",
5560
5817
  updatedAfter: null,
5818
+ filterHash: null,
5561
5819
  };
5562
5820
  }
5821
+ const isV2 = parsed.v === 2;
5822
+ const isV3 = parsed.v === 3;
5563
5823
  if (
5564
- parsed.v !== 2 ||
5565
- (parsed.orderBy !== "createdAt" && parsed.orderBy !== "updatedAt") ||
5824
+ (!isV2 && !isV3) ||
5825
+ (isV2 && parsed.orderBy !== "createdAt" && parsed.orderBy !== "updatedAt") ||
5826
+ (isV3 && parsed.orderBy !== "relevance") ||
5566
5827
  typeof parsed.sortRevision !== "string" ||
5567
5828
  typeof parsed.sortAt !== "string" ||
5568
5829
  typeof parsed.snapshotAt !== "string" ||
@@ -5573,6 +5834,18 @@ export function decodeSessionDiscoveryCursor(value: string): SessionDiscoveryCur
5573
5834
  ) {
5574
5835
  throw new Error("invalid cursor fields");
5575
5836
  }
5837
+ if (
5838
+ isV3 &&
5839
+ (!Number.isSafeInteger(parsed.sortRank) ||
5840
+ (parsed.sortRank as number) < 0 ||
5841
+ typeof parsed.filterHash !== "string" ||
5842
+ !/^[0-9a-f]{64}$/.test(parsed.filterHash))
5843
+ ) {
5844
+ throw new Error("invalid relevance cursor fields");
5845
+ }
5846
+ if (isV2 && (parsed.sortRank !== undefined || parsed.filterHash !== undefined)) {
5847
+ throw new Error("chronological cursor cannot carry relevance fields");
5848
+ }
5576
5849
  const sortAt = normalizeSessionDiscoveryTimestamp(parsed.sortAt, "cursor sortAt");
5577
5850
  const snapshotAt = normalizeSessionDiscoveryTimestamp(parsed.snapshotAt, "cursor snapshotAt");
5578
5851
  const sortRevision = normalizeSessionDiscoveryRevision(
@@ -5593,14 +5866,19 @@ export function decodeSessionDiscoveryCursor(value: string): SessionDiscoveryCur
5593
5866
  if (parsed.orderBy === "createdAt" && (sortRevision !== "0" || snapshotRevision !== "0")) {
5594
5867
  throw new Error("creation cursor cannot carry activity revisions");
5595
5868
  }
5869
+ const orderBy: SessionDiscoveryOrderBy = isV3
5870
+ ? "relevance"
5871
+ : (parsed.orderBy as "createdAt" | "updatedAt");
5596
5872
  return {
5597
- orderBy: parsed.orderBy,
5873
+ orderBy,
5874
+ sortRank: isV3 ? (parsed.sortRank as number) : null,
5598
5875
  sortRevision,
5599
5876
  sortAt,
5600
5877
  id: parsed.id,
5601
5878
  snapshotAt,
5602
5879
  snapshotRevision,
5603
5880
  updatedAfter: normalizedUpdatedAfter,
5881
+ filterHash: isV3 ? (parsed.filterHash as string) : null,
5604
5882
  };
5605
5883
  } catch {
5606
5884
  throw new Error("sessions_list cursor is invalid");
@@ -5691,6 +5969,7 @@ export function capSessionDiscoveryPage(
5691
5969
  : null,
5692
5970
  queuedPromptCount: session.queuedPromptCount,
5693
5971
  children: session.treeStats,
5972
+ relatedWork: session.workDiscovery,
5694
5973
  ...(includeLastMessage
5695
5974
  ? {
5696
5975
  latestMessage: session.latestMessage
@@ -5768,12 +6047,14 @@ export function capSessionDiscoveryPage(
5768
6047
  ? sourceLast
5769
6048
  ? encodeSessionDiscoveryCursor({
5770
6049
  orderBy: page.orderBy,
6050
+ sortRank: sourceLast.sortRank,
5771
6051
  sortRevision: sourceLast.sortRevision,
5772
6052
  sortAt: sourceLast.sortAt,
5773
6053
  id: sourceLast.id,
5774
6054
  snapshotAt: page.snapshotAt,
5775
6055
  snapshotRevision: page.snapshotRevision,
5776
6056
  updatedAfter: page.updatedAfter,
6057
+ filterHash: page.filterHash,
5777
6058
  })
5778
6059
  : null
5779
6060
  : page.nextCursor