hoi4-agent-tools 3.0.9 → 3.1.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 (124) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.md +4 -0
  3. package/dist/bin/stdio.js +23 -12
  4. package/dist/hoi4_agent_tools/core/analysis-checkpoints.js +82 -0
  5. package/dist/hoi4_agent_tools/core/artifact-resource.js +173 -0
  6. package/dist/hoi4_agent_tools/core/artifacts.js +84 -2
  7. package/dist/hoi4_agent_tools/core/dependency-pages.js +216 -0
  8. package/dist/hoi4_agent_tools/core/dependency-walk.js +266 -0
  9. package/dist/hoi4_agent_tools/core/domain-validation.js +188 -0
  10. package/dist/hoi4_agent_tools/core/engine.js +48 -5
  11. package/dist/hoi4_agent_tools/core/helper-expansion.js +186 -0
  12. package/dist/hoi4_agent_tools/core/index-segments.js +137 -0
  13. package/dist/hoi4_agent_tools/core/index.js +47 -4
  14. package/dist/hoi4_agent_tools/core/job-controls.js +109 -0
  15. package/dist/hoi4_agent_tools/core/job-executor.js +190 -0
  16. package/dist/hoi4_agent_tools/core/job-service.js +112 -0
  17. package/dist/hoi4_agent_tools/core/job-store.js +641 -0
  18. package/dist/hoi4_agent_tools/core/job-transactions.js +126 -0
  19. package/dist/hoi4_agent_tools/core/job-worker-host.js +200 -0
  20. package/dist/hoi4_agent_tools/core/job-worker.js +62 -0
  21. package/dist/hoi4_agent_tools/core/operation-context.js +31 -0
  22. package/dist/hoi4_agent_tools/core/operation-result.js +412 -0
  23. package/dist/hoi4_agent_tools/core/operation-tasks.js +382 -0
  24. package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +383 -0
  25. package/dist/hoi4_agent_tools/core/result.js +1 -0
  26. package/dist/hoi4_agent_tools/core/scanner.js +15 -75
  27. package/dist/hoi4_agent_tools/core/semantic-dependencies.js +233 -0
  28. package/dist/hoi4_agent_tools/core/shared-request-capacity.js +43 -11
  29. package/dist/hoi4_agent_tools/core/source/cache.js +107 -0
  30. package/dist/hoi4_agent_tools/core/source/parser.js +11 -1
  31. package/dist/hoi4_agent_tools/core/transaction-execution.js +134 -0
  32. package/dist/hoi4_agent_tools/core/transactions.js +80 -27
  33. package/dist/hoi4_agent_tools/event/graph.js +214 -290
  34. package/dist/hoi4_agent_tools/event/helper-expansion.js +33 -0
  35. package/dist/hoi4_agent_tools/event/job-operations.js +51 -0
  36. package/dist/hoi4_agent_tools/event/operations.js +179 -0
  37. package/dist/hoi4_agent_tools/event/service.js +67 -15
  38. package/dist/hoi4_agent_tools/event/source-analysis.js +5 -4
  39. package/dist/hoi4_agent_tools/focus/job-operations.js +77 -0
  40. package/dist/hoi4_agent_tools/focus/tool-operations.js +669 -0
  41. package/dist/hoi4_agent_tools/focus/tool-support.js +123 -0
  42. package/dist/hoi4_agent_tools/gui/job-operations.js +81 -0
  43. package/dist/hoi4_agent_tools/gui/tool-operations.js +270 -0
  44. package/dist/hoi4_agent_tools/map/job-operations.js +76 -0
  45. package/dist/hoi4_agent_tools/map/tool-operations.js +493 -0
  46. package/dist/hoi4_agent_tools/mcp/prompts/probability.js +25 -0
  47. package/dist/hoi4_agent_tools/mcp/prompts/register-modern.js +29 -0
  48. package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +34 -0
  49. package/dist/hoi4_agent_tools/mcp/resources/register.js +4 -150
  50. package/dist/hoi4_agent_tools/mcp/server/base-tools.js +3 -219
  51. package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +188 -0
  52. package/dist/hoi4_agent_tools/mcp/server/create.js +16 -6
  53. package/dist/hoi4_agent_tools/mcp/server/instructions.js +2 -0
  54. package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +67 -0
  55. package/dist/hoi4_agent_tools/mcp/server/legacy-task-tools.js +20 -0
  56. package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +168 -0
  57. package/dist/hoi4_agent_tools/mcp/server/progress.js +17 -15
  58. package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +43 -0
  59. package/dist/hoi4_agent_tools/mcp/server/result.js +2 -412
  60. package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +9 -0
  61. package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +14 -0
  62. package/dist/hoi4_agent_tools/mcp/server/task-tool-definition.js +1 -0
  63. package/dist/hoi4_agent_tools/mcp/server/transaction-execution.js +1 -123
  64. package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +38 -21
  65. package/dist/hoi4_agent_tools/mcp/tools/event.js +24 -315
  66. package/dist/hoi4_agent_tools/mcp/tools/focus.js +34 -1039
  67. package/dist/hoi4_agent_tools/mcp/tools/gui.js +19 -504
  68. package/dist/hoi4_agent_tools/mcp/tools/job.js +46 -0
  69. package/dist/hoi4_agent_tools/mcp/tools/map.js +21 -600
  70. package/dist/hoi4_agent_tools/mcp/tools/probability.js +39 -463
  71. package/dist/hoi4_agent_tools/mcp/tools/technology.js +40 -304
  72. package/dist/hoi4_agent_tools/mcp/transports/http.js +58 -1
  73. package/dist/hoi4_agent_tools/mcp/transports/modern-task-routing.js +109 -0
  74. package/dist/hoi4_agent_tools/mcp/transports/negotiated-stdio.js +121 -0
  75. package/dist/hoi4_agent_tools/probability/job-operations.js +58 -0
  76. package/dist/hoi4_agent_tools/probability/operations.js +183 -0
  77. package/dist/hoi4_agent_tools/probability/service.js +24 -3
  78. package/dist/hoi4_agent_tools/schemas/common.js +2 -0
  79. package/dist/hoi4_agent_tools/schemas/event.js +72 -1
  80. package/dist/hoi4_agent_tools/schemas/focus-requests.js +189 -0
  81. package/dist/hoi4_agent_tools/schemas/gui-requests.js +193 -0
  82. package/dist/hoi4_agent_tools/schemas/helper-expansion.js +58 -0
  83. package/dist/hoi4_agent_tools/schemas/map-requests.js +85 -0
  84. package/dist/hoi4_agent_tools/schemas/probability-requests.js +168 -0
  85. package/dist/hoi4_agent_tools/schemas/technology.js +90 -1
  86. package/dist/hoi4_agent_tools/schemas/transaction.js +1 -0
  87. package/dist/hoi4_agent_tools/technology/graph.js +83 -78
  88. package/dist/hoi4_agent_tools/technology/helper-expansion.js +33 -0
  89. package/dist/hoi4_agent_tools/technology/job-operations.js +60 -0
  90. package/dist/hoi4_agent_tools/technology/operations.js +125 -0
  91. package/dist/hoi4_agent_tools/technology/service.js +89 -15
  92. package/dist/hoi4_agent_tools/technology/source-analysis.js +2 -1
  93. package/dist/hoi4_agent_tools/version.js +1 -1
  94. package/docs/README.md +2 -0
  95. package/docs/development.md +2 -0
  96. package/docs/events.md +7 -2
  97. package/docs/helper-expansion.md +78 -0
  98. package/docs/http.md +2 -0
  99. package/docs/jobs.md +85 -0
  100. package/docs/technology.md +6 -2
  101. package/package.json +11 -5
  102. package/schemas/configuration.schema.json +1 -1
  103. package/schemas/continuous-focus-palette.schema.json +1 -1
  104. package/schemas/custom-weighted-pool.schema.json +1 -1
  105. package/schemas/event-feature-manifest.schema.json +1 -1
  106. package/schemas/focus-plan.schema.json +1 -1
  107. package/schemas/focus-planning-sidecar.schema.json +1 -1
  108. package/schemas/gui-animation-source.schema.json +1 -1
  109. package/schemas/gui-helper.schema.json +1 -1
  110. package/schemas/gui-scenario.schema.json +1 -1
  111. package/schemas/helper-expansion-request.schema.json +44 -0
  112. package/schemas/helper-expansion-summary.schema.json +70 -0
  113. package/schemas/map-operation.schema.json +1 -1
  114. package/schemas/operation-result.schema.json +1 -1
  115. package/schemas/probability-analysis-result.schema.json +1 -1
  116. package/schemas/probability-compare-input.schema.json +1 -1
  117. package/schemas/probability-evaluate-input.schema.json +1 -1
  118. package/schemas/probability-inspect-input.schema.json +1 -1
  119. package/schemas/probability-render-input.schema.json +1 -1
  120. package/schemas/probability-scenario-set.schema.json +1 -1
  121. package/schemas/probability-sequence-input.schema.json +1 -1
  122. package/schemas/probability-simulate-input.schema.json +1 -1
  123. package/schemas/probability-sweep-input.schema.json +1 -1
  124. package/server.json +2 -2
@@ -0,0 +1,382 @@
1
+ import { z } from 'zod/v4';
2
+ import { eventCompareRequestSchema, eventInspectRequestSchema, eventRenderRequestSchema, } from '../schemas/event.js';
3
+ import { focusInspectRequestSchema, focusRenderRequestSchema, focusRewriteRequestSchema, } from '../schemas/focus-requests.js';
4
+ import { guiInspectRequestSchema, guiRenderRequestSchema, guiRewriteRequestSchema, } from '../schemas/gui-requests.js';
5
+ import { mapInspectRequestSchema, mapRenderRequestSchema, mapRewriteRequestSchema, } from '../schemas/map-requests.js';
6
+ import { probabilityCompareRequestSchema, probabilityEvaluateRequestSchema, probabilityInspectRequestSchema, probabilityRenderRequestSchema, probabilitySequenceRequestSchema, probabilitySimulateRequestSchema, probabilitySweepRequestSchema, } from '../schemas/probability-requests.js';
7
+ import { technologyCompareRequestSchema, technologyInspectRequestSchema, technologyRenderRequestSchema, } from '../schemas/technology.js';
8
+ import { currentJobOwner, jobOwnerLiveness, jobRetentionExpired, } from './job-store.js';
9
+ import { JobService } from './job-service.js';
10
+ import { JobWorkerHost } from './job-worker-host.js';
11
+ import { compareCodeUnits, hashCanonical, secureId } from './canonical.js';
12
+ import { ServiceError } from './result.js';
13
+ import { errorResult } from './operation-result.js';
14
+ import { requireOperationScope, resolveOperationWorkspaceForSource, resolveOperationWorkspaceId, } from './operation-context.js';
15
+ const TASK_POLL_INTERVAL = 250;
16
+ const TASK_PAGE_SIZE = 10;
17
+ const TASK_RETENTION_DEFAULT_MS = 24 * 60 * 60 * 1000;
18
+ const TASK_RETENTION_MIN_MS = 60 * 1000;
19
+ const TASK_RETENTION_MAX_MS = 7 * 24 * 60 * 60 * 1000;
20
+ const wireResultSchema = z.record(z.string(), z.json());
21
+ const taskIdSchema = z.string().regex(/^[a-z][a-z0-9_-]{0,63}:job_[a-f0-9]{64}$/u);
22
+ const authenticatedTaskIdSchema = z
23
+ .string()
24
+ .regex(/^[a-z][a-z0-9_-]{0,63}:job_[a-f0-9]{64}\.[a-f0-9]{64}$/u);
25
+ export const operationTaskCallSchema = z
26
+ .object({
27
+ name: z.enum([
28
+ 'hoi4.event_inspect',
29
+ 'hoi4.event_render',
30
+ 'hoi4.event_compare',
31
+ 'hoi4.tech_inspect',
32
+ 'hoi4.tech_render',
33
+ 'hoi4.tech_compare',
34
+ 'hoi4.probability_inspect',
35
+ 'hoi4.probability_evaluate',
36
+ 'hoi4.probability_sweep',
37
+ 'hoi4.probability_simulate',
38
+ 'hoi4.probability_sequence',
39
+ 'hoi4.probability_compare',
40
+ 'hoi4.probability_render',
41
+ 'hoi4.map_inspect',
42
+ 'hoi4.map_render',
43
+ 'hoi4.map_rewrite',
44
+ 'hoi4.gui_inspect',
45
+ 'hoi4.gui_render',
46
+ 'hoi4.gui_rewrite',
47
+ 'hoi4.focus_inspect',
48
+ 'hoi4.focus_render',
49
+ 'hoi4.focus_raster',
50
+ 'hoi4.focus_rewrite',
51
+ ]),
52
+ arguments: z.record(z.string(), z.json()),
53
+ })
54
+ .strict();
55
+ const taskArgumentSchemas = {
56
+ 'hoi4.event_inspect': eventInspectRequestSchema,
57
+ 'hoi4.event_render': eventRenderRequestSchema,
58
+ 'hoi4.event_compare': eventCompareRequestSchema,
59
+ 'hoi4.tech_inspect': technologyInspectRequestSchema,
60
+ 'hoi4.tech_render': technologyRenderRequestSchema,
61
+ 'hoi4.tech_compare': technologyCompareRequestSchema,
62
+ 'hoi4.probability_inspect': probabilityInspectRequestSchema,
63
+ 'hoi4.probability_evaluate': probabilityEvaluateRequestSchema,
64
+ 'hoi4.probability_sweep': probabilitySweepRequestSchema,
65
+ 'hoi4.probability_simulate': probabilitySimulateRequestSchema,
66
+ 'hoi4.probability_sequence': probabilitySequenceRequestSchema,
67
+ 'hoi4.probability_compare': probabilityCompareRequestSchema,
68
+ 'hoi4.probability_render': probabilityRenderRequestSchema,
69
+ 'hoi4.map_inspect': mapInspectRequestSchema,
70
+ 'hoi4.map_render': mapRenderRequestSchema,
71
+ 'hoi4.map_rewrite': mapRewriteRequestSchema,
72
+ 'hoi4.gui_inspect': guiInspectRequestSchema,
73
+ 'hoi4.gui_render': guiRenderRequestSchema,
74
+ 'hoi4.gui_rewrite': guiRewriteRequestSchema,
75
+ 'hoi4.focus_inspect': focusInspectRequestSchema,
76
+ 'hoi4.focus_render': focusRenderRequestSchema,
77
+ 'hoi4.focus_raster': focusRenderRequestSchema,
78
+ 'hoi4.focus_rewrite': focusRewriteRequestSchema,
79
+ };
80
+ const sourceResolvingTools = new Set([
81
+ 'hoi4.focus_inspect',
82
+ 'hoi4.focus_render',
83
+ 'hoi4.focus_raster',
84
+ 'hoi4.focus_rewrite',
85
+ ]);
86
+ const mutationTools = new Set(['hoi4.gui_rewrite', 'hoi4.map_rewrite', 'hoi4.focus_rewrite']);
87
+ export function isMutationTask(name) {
88
+ return mutationTools.has(name);
89
+ }
90
+ function taskRetention(ttl) {
91
+ if (ttl === undefined || ttl === null)
92
+ return TASK_RETENTION_DEFAULT_MS;
93
+ return Math.max(TASK_RETENTION_MIN_MS, Math.min(TASK_RETENTION_MAX_MS, ttl));
94
+ }
95
+ export function operationTaskId(record) {
96
+ return `${record.scope.workspaceId}:${record.id}`;
97
+ }
98
+ function taskReferencePayload(record) {
99
+ return { kind: 'operation-task-reference.v1', scope: record.scope, id: record.id };
100
+ }
101
+ export function parseOperationTaskId(value) {
102
+ const parsed = taskIdSchema.parse(value);
103
+ const separator = parsed.indexOf(':');
104
+ return { workspaceId: parsed.slice(0, separator), jobId: parsed.slice(separator + 1) };
105
+ }
106
+ export function operationTaskStatus(record) {
107
+ if (record.status === 'completed')
108
+ return 'completed';
109
+ if (record.status === 'cancelled')
110
+ return 'cancelled';
111
+ if (record.status === 'failed')
112
+ return 'failed';
113
+ return 'working';
114
+ }
115
+ export function operationTaskMessage(record) {
116
+ if (record.failure !== undefined)
117
+ return `${record.failure.code}: ${record.failure.message}`;
118
+ if (record.progress !== undefined)
119
+ return record.progress.message;
120
+ if (record.cancelRequested)
121
+ return 'Cancellation requested';
122
+ return undefined;
123
+ }
124
+ /** Protocol-independent task execution, ownership, retention, and recovery over durable jobs. */
125
+ export class OperationTaskService {
126
+ engine;
127
+ jobsPromise;
128
+ workerPromise;
129
+ launches = new Map();
130
+ constructor(engine) {
131
+ this.engine = engine;
132
+ }
133
+ jobs() {
134
+ this.jobsPromise ??= JobService.create(this.engine.resolver);
135
+ return this.jobsPromise;
136
+ }
137
+ worker() {
138
+ this.workerPromise ??= this.jobs().then((jobs) => JobWorkerHost.create(this.engine, jobs));
139
+ return this.workerPromise;
140
+ }
141
+ async submit(input, context, options) {
142
+ const parsed = operationTaskCallSchema.parse(input);
143
+ const validatedArguments = taskArgumentSchemas[parsed.name].parse(parsed.arguments);
144
+ const requestSignal = options.signal;
145
+ const mutation = isMutationTask(parsed.name);
146
+ const explicitTask = options.background;
147
+ const callerRequestKey = typeof validatedArguments.requestKey === 'string' ? validatedArguments.requestKey : undefined;
148
+ if (mutation && explicitTask && callerRequestKey === undefined)
149
+ throw new ServiceError('JOB_REQUEST_KEY_REQUIRED', 'Native background rewrites require a caller-stable requestKey');
150
+ const requestedWorkspaceId = typeof validatedArguments.workspaceId === 'string'
151
+ ? validatedArguments.workspaceId
152
+ : 'current';
153
+ let workspaceId;
154
+ try {
155
+ requestSignal?.throwIfAborted();
156
+ if (mutation)
157
+ requireOperationScope(context, 'hoi4:write');
158
+ workspaceId = sourceResolvingTools.has(parsed.name)
159
+ ? await resolveOperationWorkspaceForSource(this.engine, context, requestedWorkspaceId, typeof validatedArguments.relativePath === 'string'
160
+ ? validatedArguments.relativePath
161
+ : undefined, requestSignal)
162
+ : await resolveOperationWorkspaceId(this.engine, context, requestedWorkspaceId, requestSignal);
163
+ this.engine.resolver.get(workspaceId, context.principal);
164
+ requestSignal?.throwIfAborted();
165
+ }
166
+ catch (error) {
167
+ if (error instanceof ServiceError) {
168
+ return this.completeRejectedTask(parsed.name, wireResultSchema.parse(validatedArguments), requestedWorkspaceId, context, options, error);
169
+ }
170
+ throw error;
171
+ }
172
+ const arguments_ = { ...validatedArguments, workspaceId };
173
+ const jobs = await this.jobs();
174
+ const { record } = await jobs.submit(workspaceId, {
175
+ toolName: parsed.name,
176
+ arguments: arguments_,
177
+ mutation,
178
+ ...(mutation
179
+ ? {
180
+ requestKey: callerRequestKey ??
181
+ `foreground:${hashCanonical({
182
+ sessionId: options.sessionId ?? secureId('call'),
183
+ requestId: options.requestId,
184
+ })}`,
185
+ }
186
+ : {}),
187
+ protocolTask: {
188
+ ttl: taskRetention(options.ttlMs),
189
+ pollInterval: TASK_POLL_INTERVAL,
190
+ },
191
+ }, context.principal, requestSignal);
192
+ if (!explicitTask && requestSignal !== undefined) {
193
+ if (requestSignal.aborted) {
194
+ return jobs.cancel(workspaceId, record.id, context.principal);
195
+ }
196
+ const cancel = () => {
197
+ void jobs.cancel(workspaceId, record.id, context.principal).catch(() => undefined);
198
+ };
199
+ requestSignal.addEventListener('abort', cancel, { once: true });
200
+ }
201
+ await this.ensureExecution(record, context.principal);
202
+ return record;
203
+ }
204
+ async get(value, context, options = {}) {
205
+ const record = await this.find(value, context);
206
+ return this.resume(record, context, options);
207
+ }
208
+ /** A durable opaque reference, including for retained pre-hardening receipt identities. */
209
+ authenticatedTaskId(record) {
210
+ const state = this.engine.resolver.serverState();
211
+ if (state === undefined)
212
+ throw new ServiceError('JOB_STORAGE_UNAVAILABLE', 'Persistent tasks require server state');
213
+ return `${operationTaskId(record)}.${state.authenticateJournal(taskReferencePayload(record))}`;
214
+ }
215
+ async getAuthenticatedTask(value, context, options = {}) {
216
+ if (!authenticatedTaskIdSchema.safeParse(value).success)
217
+ return null;
218
+ const separator = value.lastIndexOf('.');
219
+ const record = await this.find(value.slice(0, separator), context);
220
+ if (record === null ||
221
+ this.engine.resolver
222
+ .serverState()
223
+ ?.verifyJournal(taskReferencePayload(record), value.slice(separator + 1)) !== true)
224
+ return null;
225
+ // Verify before resuming: a malformed or guessed reference must not admit execution.
226
+ return this.resume(record, context, options);
227
+ }
228
+ async resume(record, context, options) {
229
+ if (record?.request.protocolTask === undefined)
230
+ return null;
231
+ if (options.resume === false)
232
+ return record;
233
+ // Reading a retained rewrite must not resume it under a credential that lost write scope.
234
+ if (record.request.mutation &&
235
+ context.scopes !== undefined &&
236
+ !context.scopes.includes('hoi4:write'))
237
+ return record;
238
+ await this.ensureExecution(record, context.principal);
239
+ return record;
240
+ }
241
+ async assertPublishedResult(value, status, result, context) {
242
+ const record = await this.find(value, context);
243
+ if (record?.request.protocolTask === undefined)
244
+ throw new ServiceError('TASK_NOT_FOUND', 'The requested protocol task does not exist');
245
+ if (status !== operationTaskStatus(record) ||
246
+ JSON.stringify(record.result) !== JSON.stringify(result))
247
+ throw new ServiceError('TASK_RESULT_IMMUTABLE', 'Protocol task results are published only by the authenticated job executor');
248
+ }
249
+ async result(value, context) {
250
+ let record = await this.find(value, context);
251
+ if (record?.request.protocolTask === undefined)
252
+ throw new ServiceError('TASK_NOT_FOUND', 'The requested protocol task does not exist');
253
+ const launch = this.launches.get(value);
254
+ if (launch !== undefined) {
255
+ await launch;
256
+ record = await this.find(value, context);
257
+ if (record?.request.protocolTask === undefined)
258
+ throw new ServiceError('TASK_NOT_FOUND', 'The requested protocol task does not exist');
259
+ }
260
+ if (record.status === 'completed' && record.result !== undefined)
261
+ return record.result;
262
+ if (record.status === 'failed')
263
+ return errorResult(new ServiceError(record.failure?.code ?? 'JOB_FAILED', record.failure?.message ?? 'The background operation failed'), record.scope.workspaceId);
264
+ if (record.status === 'cancelled')
265
+ return errorResult(new ServiceError('JOB_CANCELLED', 'The background operation was cancelled'), record.scope.workspaceId);
266
+ throw new ServiceError('TASK_RESULT_PENDING', 'The protocol task has not reached a terminal state');
267
+ }
268
+ async cancel(value, context) {
269
+ const record = await this.find(value, context);
270
+ if (record?.request.protocolTask === undefined)
271
+ throw new ServiceError('TASK_NOT_FOUND', 'The requested protocol task does not exist');
272
+ if (record.request.mutation)
273
+ requireOperationScope(context, 'hoi4:write');
274
+ return (await this.jobs()).cancel(record.scope.workspaceId, record.id, context.principal);
275
+ }
276
+ async list(context, cursor) {
277
+ const workspaces = this.engine.resolver.list(context.principal);
278
+ const after = cursor === undefined ? undefined : parseOperationTaskId(cursor);
279
+ if (after !== undefined && !workspaces.some(({ id }) => id === after.workspaceId))
280
+ throw new ServiceError('TASK_CURSOR_INVALID', 'The task cursor is invalid for this principal');
281
+ const jobs = await this.jobs();
282
+ const tasks = [];
283
+ for (const workspace of workspaces) {
284
+ if (after !== undefined && compareCodeUnits(workspace.id, after.workspaceId) < 0)
285
+ continue;
286
+ let jobCursor = after?.workspaceId === workspace.id ? after.jobId : undefined;
287
+ while (tasks.length <= TASK_PAGE_SIZE) {
288
+ const page = await jobs.list(workspace.id, context.principal, {
289
+ ...(jobCursor === undefined ? {} : { after: jobCursor }),
290
+ limit: Math.min(100, TASK_PAGE_SIZE + 1 - tasks.length),
291
+ });
292
+ for (const record of page.records) {
293
+ if (record.request.protocolTask !== undefined && !jobRetentionExpired(record))
294
+ tasks.push(record);
295
+ if (tasks.length > TASK_PAGE_SIZE)
296
+ break;
297
+ }
298
+ if (tasks.length > TASK_PAGE_SIZE || page.next === undefined)
299
+ break;
300
+ jobCursor = page.next;
301
+ }
302
+ if (tasks.length > TASK_PAGE_SIZE)
303
+ break;
304
+ }
305
+ const selected = tasks.slice(0, TASK_PAGE_SIZE);
306
+ return {
307
+ records: selected,
308
+ ...(tasks.length > TASK_PAGE_SIZE ? { nextCursor: operationTaskId(selected.at(-1)) } : {}),
309
+ };
310
+ }
311
+ async find(value, context) {
312
+ let reference;
313
+ try {
314
+ reference = parseOperationTaskId(value);
315
+ }
316
+ catch {
317
+ return null;
318
+ }
319
+ try {
320
+ const record = await (await this.jobs()).get(reference.workspaceId, reference.jobId, context.principal);
321
+ return jobRetentionExpired(record) ? null : record;
322
+ }
323
+ catch (error) {
324
+ if (error instanceof ServiceError &&
325
+ ['JOB_NOT_FOUND', 'WORKSPACE_INACCESSIBLE', 'WORKSPACE_NOT_REGISTERED'].includes(error.code))
326
+ return null;
327
+ throw error;
328
+ }
329
+ }
330
+ /** Preserve the ordinary tool-error envelope when authorization fails before submission. */
331
+ async completeRejectedTask(toolName, arguments_, requestedWorkspaceId, context, options, error) {
332
+ const fallback = this.engine.resolver.list(context.principal)[0];
333
+ if (fallback === undefined)
334
+ throw error;
335
+ const jobs = await this.jobs();
336
+ const { record } = await jobs.submit(fallback.id, {
337
+ toolName,
338
+ arguments: arguments_,
339
+ mutation: false,
340
+ requestKey: `rejected:${secureId('request')}`,
341
+ protocolTask: {
342
+ ttl: taskRetention(options.ttlMs),
343
+ pollInterval: TASK_POLL_INTERVAL,
344
+ },
345
+ }, context.principal);
346
+ const owner = currentJobOwner();
347
+ await jobs.store.claim(jobs.scope(fallback.id, context.principal), record.id, owner);
348
+ const completed = await jobs.store.updateOwned(jobs.scope(fallback.id, context.principal), record.id, owner.token, {
349
+ status: 'completed',
350
+ result: wireResultSchema.parse(errorResult(error, requestedWorkspaceId)),
351
+ });
352
+ return completed;
353
+ }
354
+ async ensureExecution(record, principal) {
355
+ if (['completed', 'failed', 'cancelled'].includes(record.status))
356
+ return;
357
+ if (record.status !== 'queued' &&
358
+ (record.owner === undefined || jobOwnerLiveness(record.owner) !== 'dead'))
359
+ return;
360
+ const key = operationTaskId(record);
361
+ if (this.launches.has(key))
362
+ return;
363
+ const worker = await this.worker();
364
+ const launched = worker
365
+ .run(record.scope.workspaceId, record.id, principal)
366
+ .then(() => undefined)
367
+ .catch(async (error) => {
368
+ const jobs = await this.jobs();
369
+ await jobs.failInterrupted(record.scope.workspaceId, record.id, {
370
+ code: error instanceof ServiceError ? error.code : 'JOB_WORKER_FAILED',
371
+ message: error instanceof ServiceError
372
+ ? error.message
373
+ : 'The isolated worker could not be started',
374
+ }, principal);
375
+ })
376
+ .finally(() => {
377
+ this.launches.delete(key);
378
+ });
379
+ this.launches.set(key, launched);
380
+ void launched.catch(() => undefined);
381
+ }
382
+ }