@tomflow/proflow-platform-host 0.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.
@@ -0,0 +1,1928 @@
1
+ import { randomBytes, timingSafeEqual } from "node:crypto";
2
+ import { existsSync } from "node:fs";
3
+ import { appendFile, chmod, mkdir, readFile, stat, writeFile, } from "node:fs/promises";
4
+ import { createServer } from "node:http";
5
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
6
+ import { createAgentRuntime } from "@tomflow/proflow-agent-runtime";
7
+ import { applyMigrations } from "@tomflow/proflow-task-migration-runner";
8
+ import { createTaskServices, publicOperationNames, } from "@tomflow/proflow-task-orchestration";
9
+ import { SqliteTaskStore } from "@tomflow/proflow-task-store-sqlite";
10
+ import { taskMigrations } from "@tomflow/proflow-task-store-sqlite/migrations";
11
+ import { z } from "zod";
12
+ import { roleOperations, rolePackageRefs, } from "./role-operations.js";
13
+ const loopbackHosts = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
14
+ const systemObserverReasonResultSchema = z
15
+ .object({
16
+ scope: z.string().min(1).max(240).optional(),
17
+ health: z.enum(["HEALTHY", "DEGRADED", "CRITICAL", "UNKNOWN"]),
18
+ findings: z.array(z.string()).max(128),
19
+ risks: z.array(z.string()).max(128),
20
+ anomalies: z.array(z.string()).max(128),
21
+ hypotheses: z.array(z.string()).max(128),
22
+ unresolved: z.array(z.string()).max(128),
23
+ needsDrilldown: z.array(z.string()).max(128),
24
+ evidenceRefs: z.array(z.string()).max(128),
25
+ confidence: z.number().min(0).max(1),
26
+ carryForward: z
27
+ .array(z
28
+ .object({
29
+ hypothesis: z.string().min(1),
30
+ risk: z.string().min(1).optional(),
31
+ evidenceRef: z.string().min(1).optional(),
32
+ confidence: z.number().min(0).max(1),
33
+ })
34
+ .strict())
35
+ .max(64),
36
+ rationale: z.string().min(1),
37
+ })
38
+ .strict();
39
+ const browserStructuredLogSchema = z
40
+ .object({
41
+ timestamp: z.string().datetime(),
42
+ level: z.enum(["DEBUG", "INFO", "WARN", "ERROR"]),
43
+ component: z.string().min(1).max(100),
44
+ capability: z.string().min(1).max(160).optional(),
45
+ operation: z.string().min(1).max(160).optional(),
46
+ status: z.string().min(1).max(80).optional(),
47
+ errorCode: z.string().min(1).max(160).optional(),
48
+ correlationId: z.string().min(1).max(240).optional(),
49
+ taskId: z.string().min(1).max(240).optional(),
50
+ nodeId: z.string().min(1).max(240).optional(),
51
+ runNo: z.number().int().nonnegative().optional(),
52
+ agentPackageRef: z.string().min(1).max(240).optional(),
53
+ roleRef: z.string().min(1).max(240).optional(),
54
+ workerRef: z.string().min(1).max(240).optional(),
55
+ executionRef: z.string().min(1).max(240).optional(),
56
+ messageRef: z.string().min(1).max(240).optional(),
57
+ artifactRef: z.string().min(1).max(240).optional(),
58
+ evidenceRef: z.string().min(1).max(240).optional(),
59
+ conversationLocator: z.string().min(1).max(1_000).optional(),
60
+ operationRef: z.string().min(1).max(240).optional(),
61
+ attemptNo: z.number().int().nonnegative().optional(),
62
+ tabId: z.number().int().nonnegative().optional(),
63
+ })
64
+ .strict();
65
+ const taskDiagnosticReasonResultSchema = z
66
+ .object({
67
+ finding: z.string().min(1).max(1_000),
68
+ probableCause: z.string().min(1).max(1_000),
69
+ confidence: z.number().min(0).max(1),
70
+ recommendedNextObservation: z.string().min(1).max(1_000),
71
+ recommendedRecoveryAction: z.string().min(1).max(1_000),
72
+ needsHumanAttention: z.boolean(),
73
+ })
74
+ .strict();
75
+ const taskDocumentFileSchema = z.object({
76
+ taskId: z.string().min(1),
77
+ documentType: z.string().min(1),
78
+ contentHash: z.string().min(1),
79
+ content: z.string().min(1),
80
+ });
81
+ function fileBridgeOutputForTaskResult(result) {
82
+ if (typeof result !== "object" || result === null || !("ok" in result))
83
+ return result;
84
+ const record = result;
85
+ if (record.ok !== true)
86
+ return result;
87
+ const document = taskDocumentFileSchema.parse(record.data);
88
+ const output = {
89
+ fileArtifacts: [
90
+ {
91
+ artifactRef: `document:${document.taskId}:${document.documentType}`,
92
+ name: `${document.documentType.toLowerCase().replaceAll("_", "-")}.md`,
93
+ mimeType: "text/markdown",
94
+ content: document.content,
95
+ },
96
+ ],
97
+ };
98
+ return output;
99
+ }
100
+ const loopbackUrl = z
101
+ .url()
102
+ .transform((value) => new URL(value))
103
+ .refine((value) => value.protocol === "http:" && loopbackHosts.has(value.hostname), "owner service URL must be loopback HTTP")
104
+ .transform((value) => value.href.replace(/\/$/, ""));
105
+ const configSchema = z
106
+ .object({
107
+ stateRoot: z.string().min(1),
108
+ workspaceRoot: z.string().min(1),
109
+ host: z.string().min(1).default("127.0.0.1"),
110
+ port: z.number().int().min(0).max(65_535).default(0),
111
+ executionBaseUrl: loopbackUrl,
112
+ executionTransportCredentialFile: z.string().min(1).optional(),
113
+ modelBaseUrl: loopbackUrl,
114
+ modelTransportCredentialFile: z.string().min(1).optional(),
115
+ gatewayTransportCredentialFile: z.string().min(1).optional(),
116
+ roles: z
117
+ .array(z
118
+ .object({
119
+ agentPackageRef: z.enum(rolePackageRefs),
120
+ registeredPackageVersion: z.string().regex(/^\d+\.\d+\.\d+$/),
121
+ roleRef: z.string().regex(/^g-[A-Za-z0-9_-]+$/),
122
+ carrierUrl: z
123
+ .url()
124
+ .refine((value) => value.startsWith("https://chatgpt.com/g/")),
125
+ })
126
+ .strict())
127
+ .default([]),
128
+ })
129
+ .strict()
130
+ .superRefine((value, context) => {
131
+ if (!loopbackHosts.has(value.host))
132
+ context.addIssue({
133
+ code: "custom",
134
+ path: ["host"],
135
+ message: "platform-host transport must bind to loopback",
136
+ });
137
+ const stateRoot = resolve(value.stateRoot);
138
+ if (!isAbsolute(value.stateRoot) || basename(stateRoot) !== ".proflow")
139
+ context.addIssue({
140
+ code: "custom",
141
+ path: ["stateRoot"],
142
+ message: "stateRoot must be an absolute .proflow directory",
143
+ });
144
+ if (!isAbsolute(value.workspaceRoot))
145
+ context.addIssue({
146
+ code: "custom",
147
+ path: ["workspaceRoot"],
148
+ message: "workspaceRoot must be absolute",
149
+ });
150
+ for (const role of value.roles)
151
+ if (role.carrierUrl !== `https://chatgpt.com/g/${role.roleRef}`)
152
+ context.addIssue({
153
+ code: "custom",
154
+ path: ["roles"],
155
+ message: "carrierUrl must exactly bind its roleRef",
156
+ });
157
+ if (new Set(value.roles.map((role) => role.agentPackageRef)).size !==
158
+ value.roles.length)
159
+ context.addIssue({
160
+ code: "custom",
161
+ path: ["roles"],
162
+ message: "agentPackageRef registrations must be unique",
163
+ });
164
+ if (new Set(value.roles.map((role) => role.roleRef)).size !==
165
+ value.roles.length)
166
+ context.addIssue({
167
+ code: "custom",
168
+ path: ["roles"],
169
+ message: "roleRef registrations must be unique",
170
+ });
171
+ })
172
+ .transform((value) => ({
173
+ ...value,
174
+ stateRoot: resolve(value.stateRoot),
175
+ workspaceRoot: resolve(value.workspaceRoot),
176
+ ...(value.modelTransportCredentialFile
177
+ ? {
178
+ modelTransportCredentialFile: resolve(value.modelTransportCredentialFile),
179
+ }
180
+ : {}),
181
+ ...(value.executionTransportCredentialFile
182
+ ? {
183
+ executionTransportCredentialFile: resolve(value.executionTransportCredentialFile),
184
+ }
185
+ : {}),
186
+ ...(value.gatewayTransportCredentialFile
187
+ ? {
188
+ gatewayTransportCredentialFile: resolve(value.gatewayTransportCredentialFile),
189
+ }
190
+ : {}),
191
+ }));
192
+ export const parsePlatformHostConfig = (value) => configSchema.parse(value);
193
+ export async function loadPlatformHostConfig(path) {
194
+ return parsePlatformHostConfig(JSON.parse(await readFile(resolve(path), "utf8")));
195
+ }
196
+ async function responseJson(response) {
197
+ const body = await response.text();
198
+ const value = body.length ? JSON.parse(body) : undefined;
199
+ if (!response.ok)
200
+ throw Object.assign(new Error("OWNER_SERVICE_UNAVAILABLE"), {
201
+ httpStatus: response.status,
202
+ ownerResponse: value,
203
+ });
204
+ return value;
205
+ }
206
+ function createOwnerHttpClient(owner, baseUrl, credential) {
207
+ return Object.freeze({
208
+ async readiness() {
209
+ try {
210
+ const response = await fetch(`${baseUrl}/ready`, {
211
+ headers: credential ? { authorization: `Bearer ${credential}` } : {},
212
+ signal: AbortSignal.timeout(2_000),
213
+ });
214
+ const text = await response.text();
215
+ const detail = text.length ? JSON.parse(text) : undefined;
216
+ return {
217
+ owner,
218
+ status: response.ok ? "READY" : "NOT_READY",
219
+ liveness: "UP",
220
+ detail,
221
+ };
222
+ }
223
+ catch (error) {
224
+ return {
225
+ owner,
226
+ status: "NOT_READY",
227
+ liveness: "DOWN",
228
+ detail: error instanceof Error ? { error: error.message } : undefined,
229
+ };
230
+ }
231
+ },
232
+ async invoke(operationId, input) {
233
+ let path;
234
+ let method = "POST";
235
+ let callerContext;
236
+ let requestBody = input;
237
+ if (owner === "model") {
238
+ if (operationId === "getRuntimeStatus") {
239
+ path = "/status";
240
+ method = "GET";
241
+ }
242
+ else if (operationId === "infer")
243
+ path = "/infer";
244
+ else
245
+ throw new Error("MODEL_OPERATION_NOT_ROUTED");
246
+ }
247
+ else if (operationId === "materializeExternalFiles")
248
+ path = "/external-files/materialize";
249
+ else if (operationId === "executeCapability")
250
+ path = "/executions";
251
+ else if (operationId === "lookupExecutionIntent")
252
+ path = "/executions/lookup";
253
+ else if (operationId === "listExecutionObserverSignals")
254
+ path = "/observer-signals/list";
255
+ else if (operationId === "getCarrierSummary") {
256
+ path = "/carrier/summary";
257
+ method = "GET";
258
+ }
259
+ else if (operationId === "getArtifactSummary") {
260
+ path = "/artifacts/summary";
261
+ method = "GET";
262
+ }
263
+ else if (operationId === "acknowledgeExecutionObserverSignal")
264
+ path = "/observer-signals/ack";
265
+ else if (operationId === "cancelExecution")
266
+ path = "/executions/cancel";
267
+ else if (operationId === "readExecutionOutput") {
268
+ const value = object(input, "readExecutionOutput input");
269
+ callerContext = string(value.callerRef, "callerRef");
270
+ requestBody = Object.fromEntries(Object.entries(value).filter(([key]) => key !== "callerRef"));
271
+ path = "/executions/output";
272
+ }
273
+ else if (operationId === "requestExecutionApproval")
274
+ path = "/approvals/request";
275
+ else if (operationId === "decideExecutionApproval")
276
+ path = "/approvals/decide";
277
+ else if (operationId === "revokeExecutionApproval")
278
+ path = "/approvals/revoke";
279
+ else if (operationId === "listExecutionApprovals")
280
+ path = "/approvals/list";
281
+ else if (operationId === "getExecutionApproval") {
282
+ const value = object(input, "getExecutionApproval input");
283
+ path = `/approvals/${encodeURIComponent(string(value.approvalRef, "approvalRef"))}`;
284
+ method = "GET";
285
+ }
286
+ else if (operationId === "getExecution") {
287
+ const value = object(input, "getExecution input");
288
+ callerContext = string(value.callerRef, "callerRef");
289
+ path = `/executions/${encodeURIComponent(string(value.executionRef, "executionRef"))}`;
290
+ method = "GET";
291
+ }
292
+ else
293
+ throw new Error("EXECUTION_OPERATION_NOT_ROUTED");
294
+ return responseJson(await fetch(`${baseUrl}${path}`, {
295
+ method,
296
+ headers: {
297
+ ...(credential ? { authorization: `Bearer ${credential}` } : {}),
298
+ ...(callerContext ? { "x-proflow-caller-ref": callerContext } : {}),
299
+ ...(method === "POST"
300
+ ? { "content-type": "application/json" }
301
+ : {}),
302
+ },
303
+ ...(method === "POST"
304
+ ? {
305
+ body: JSON.stringify(requestBody),
306
+ }
307
+ : {}),
308
+ signal: AbortSignal.timeout(45_000),
309
+ }));
310
+ },
311
+ });
312
+ }
313
+ function object(value, name) {
314
+ if (typeof value !== "object" || value === null || Array.isArray(value))
315
+ throw new TypeError(`${name} must be an object`);
316
+ return value;
317
+ }
318
+ function string(value, name) {
319
+ if (typeof value !== "string" || value.length === 0)
320
+ throw new TypeError(`${name} must be a non-empty string`);
321
+ return value;
322
+ }
323
+ function unwrap(result) {
324
+ if (!result.ok || result.data === undefined)
325
+ throw new Error(`OWNER_CONTRACT_FAILED:${JSON.stringify(result.error)}`);
326
+ return result.data;
327
+ }
328
+ // Bounded read of the Deployment owner's explicit System Observer projection.
329
+ // Platform Host never reads Deployment's internal state.json or derives health;
330
+ // it consumes only the bounded summary emitted by Deployment status/verify/doctor.
331
+ async function readDeploymentOwnerSummary(stateRoot) {
332
+ try {
333
+ const raw = await readFile(join(stateRoot, "deployment", "observer-summary.json"), "utf8");
334
+ const value = JSON.parse(raw);
335
+ if (typeof value !== "object" || value === null || Array.isArray(value))
336
+ return undefined;
337
+ const record = value;
338
+ if (record.contract !== "proflow.deployment-observer-summary.v1" ||
339
+ record.scope !== "PLATFORM")
340
+ return undefined;
341
+ if (!new Set(["READY", "DEGRADED", "ACTION_REQUIRED", "NOT_READY"]).has(String(record.state)) ||
342
+ !new Set(["status", "verify", "doctor"]).has(String(record.source)) ||
343
+ typeof record.selectedModuleCount !== "number" ||
344
+ typeof record.totalModuleCount !== "number" ||
345
+ typeof record.observedModuleCount !== "number" ||
346
+ typeof record.blockingModuleCount !== "number" ||
347
+ typeof record.observedAt !== "string" ||
348
+ typeof record.freshUntil !== "string" ||
349
+ Number.isNaN(Date.parse(record.observedAt)) ||
350
+ Number.isNaN(Date.parse(record.freshUntil)) ||
351
+ record.selectedModuleCount !== record.totalModuleCount ||
352
+ Date.parse(record.freshUntil) <= Date.now()) {
353
+ return undefined;
354
+ }
355
+ return {
356
+ state: record.state,
357
+ source: record.source,
358
+ selectedModuleCount: record.selectedModuleCount,
359
+ totalModuleCount: record.totalModuleCount,
360
+ observedModuleCount: record.observedModuleCount,
361
+ blockingModuleCount: record.blockingModuleCount,
362
+ observedAt: record.observedAt,
363
+ freshUntil: record.freshUntil,
364
+ };
365
+ }
366
+ catch {
367
+ return undefined;
368
+ }
369
+ }
370
+ async function constructGraph(config, executionCredential, modelCredential) {
371
+ const databasePath = join(config.stateRoot, "state", "task.sqlite");
372
+ const migration = applyMigrations({
373
+ databasePath,
374
+ migrations: taskMigrations,
375
+ });
376
+ if (!migration.ok)
377
+ throw new Error(`TASK_MIGRATION_FAILED:${migration.error?.message}`);
378
+ const taskStore = new SqliteTaskStore({ databasePath });
379
+ const task = createTaskServices({
380
+ store: taskStore,
381
+ workspaceRoot: config.workspaceRoot,
382
+ });
383
+ const taskFacts = (taskId) => {
384
+ const value = unwrap(task.queries.getTask({ taskId }));
385
+ return {
386
+ taskId: value.taskId,
387
+ status: value.status,
388
+ roleBindings: value.roleBindings,
389
+ };
390
+ };
391
+ const taskIsTerminal = (status) => status === "SUCCEEDED" || status === "TERMINATED";
392
+ let agent;
393
+ try {
394
+ agent = await createAgentRuntime({
395
+ proflowRoot: config.stateRoot,
396
+ task: {
397
+ async getTask(taskId) {
398
+ return taskFacts(taskId);
399
+ },
400
+ async hasNonTerminalRoleUsage(roleRef) {
401
+ const summaries = unwrap(task.queries.listTasks({})).tasks;
402
+ return summaries.some((summary) => {
403
+ const current = taskFacts(summary.taskId);
404
+ return (current.status !== "SUCCEEDED" &&
405
+ current.status !== "TERMINATED" &&
406
+ current.roleBindings.some((binding) => binding.roleRef === roleRef));
407
+ });
408
+ },
409
+ },
410
+ });
411
+ }
412
+ catch (error) {
413
+ taskStore.close();
414
+ throw error;
415
+ }
416
+ try {
417
+ for (const role of config.roles) {
418
+ const existing = agent
419
+ .listRegisteredRoles()
420
+ .find((candidate) => candidate.agentPackageRef === role.agentPackageRef);
421
+ if (existing) {
422
+ if (existing.roleRef !== role.roleRef ||
423
+ existing.carrierUrl !== role.carrierUrl ||
424
+ existing.registeredPackageVersion !== role.registeredPackageVersion)
425
+ throw new Error(`ROLE_REGISTRATION_DRIFT:${role.agentPackageRef}`);
426
+ }
427
+ else
428
+ await agent.registerRole(role);
429
+ }
430
+ }
431
+ catch (error) {
432
+ agent.close();
433
+ taskStore.close();
434
+ throw error;
435
+ }
436
+ const execution = createOwnerHttpClient("execution", config.executionBaseUrl, executionCredential);
437
+ const model = createOwnerHttpClient("model", config.modelBaseUrl, modelCredential);
438
+ const boundedSystemView = async (view) => {
439
+ if (view === "task") {
440
+ const tasks = unwrap(task.queries.listTasks({})).tasks;
441
+ return {
442
+ summary: `tasks=${tasks.length}; active=${tasks.filter((item) => item.status === "ACTIVE").length}; ready=${tasks.filter((item) => item.status === "READY").length}`,
443
+ health: "HEALTHY",
444
+ };
445
+ }
446
+ if (view === "worker") {
447
+ const tasks = unwrap(task.queries.listTasks({})).tasks;
448
+ let bindings = 0;
449
+ let missingLocator = 0;
450
+ for (const summary of tasks)
451
+ for (const binding of taskFacts(summary.taskId).roleBindings) {
452
+ if (binding.workerRef)
453
+ bindings += 1;
454
+ if (binding.workerRef && !binding.conversationLocator)
455
+ missingLocator += 1;
456
+ }
457
+ return {
458
+ summary: `boundWorkers=${bindings}; missingConversationLocator=${missingLocator}`,
459
+ health: missingLocator === 0 ? "HEALTHY" : "DEGRADED",
460
+ };
461
+ }
462
+ if (view === "collaboration") {
463
+ const pending = await agent.listPendingCollaborationMessages({
464
+ limit: 100,
465
+ });
466
+ return {
467
+ summary: `pendingMessages=${pending.length}`,
468
+ health: pending.length < 100 ? "HEALTHY" : "DEGRADED",
469
+ };
470
+ }
471
+ if (view === "model") {
472
+ const status = object(await model.invoke("getRuntimeStatus", {}), "model status");
473
+ return {
474
+ summary: JSON.stringify(status).slice(0, 2_000),
475
+ health: status.runtime === "READY"
476
+ ? "HEALTHY"
477
+ : "DEGRADED",
478
+ };
479
+ }
480
+ if (view === "execution") {
481
+ const readiness = await execution.readiness();
482
+ const signalResult = object(await execution.invoke("listExecutionObserverSignals", { limit: 50 }), "execution observer signals");
483
+ const signals = Array.isArray(signalResult.signals)
484
+ ? signalResult.signals
485
+ : [];
486
+ return {
487
+ summary: `execution owner readiness=${readiness.status}; liveness=${readiness.liveness}; pendingObserverSignals=${signals.length}`,
488
+ health: readiness.status === "READY"
489
+ ? "HEALTHY"
490
+ : "DEGRADED",
491
+ };
492
+ }
493
+ if (view === "carrier") {
494
+ const carrier = object(await execution.invoke("getCarrierSummary", {}), "carrier summary");
495
+ const online = carrier.online === true;
496
+ return {
497
+ summary: `bridgeOnline=${online}; queuedCommands=${carrier.queuedCommands ?? 0}; pendingCommands=${carrier.pendingCommands ?? 0}`,
498
+ health: online ? "HEALTHY" : "DEGRADED",
499
+ };
500
+ }
501
+ if (view === "deployment") {
502
+ const deployment = await readDeploymentOwnerSummary(config.stateRoot);
503
+ if (deployment === undefined) {
504
+ return {
505
+ summary: "deployment owner summary unavailable",
506
+ health: "UNKNOWN",
507
+ projectionStatus: "UNAVAILABLE",
508
+ findings: [
509
+ "Deployment current state is not materialized; no substitute owner readiness is inferred",
510
+ ],
511
+ };
512
+ }
513
+ return {
514
+ summary: `deploymentOwnerState=${deployment.state}; source=${deployment.source}; selectedModules=${deployment.selectedModuleCount}; observedModules=${deployment.observedModuleCount}; blockingModules=${deployment.blockingModuleCount}; observedAt=${deployment.observedAt}`,
515
+ health: deployment.state === "READY"
516
+ ? "HEALTHY"
517
+ : "DEGRADED",
518
+ findings: deployment.state === "READY"
519
+ ? []
520
+ : [`Deployment owner reports ${deployment.state}`],
521
+ };
522
+ }
523
+ if (view === "artifact") {
524
+ const artifacts = object(await execution.invoke("getArtifactSummary", {}), "artifact summary");
525
+ const byKind = typeof artifacts.byKind === "object" && artifacts.byKind !== null
526
+ ? JSON.stringify(artifacts.byKind)
527
+ : "none";
528
+ return {
529
+ summary: `totalArtifacts=${artifacts.totalArtifacts ?? 0}; byKind=${byKind}; latest=${artifacts.latestCreatedAt ?? "none"}`,
530
+ health: "HEALTHY",
531
+ };
532
+ }
533
+ return {
534
+ summary: `${view} owner aggregate projection unavailable`,
535
+ health: "UNKNOWN",
536
+ projectionStatus: "UNAVAILABLE",
537
+ findings: [
538
+ `${view} has no formal bounded aggregate read API in the current composition; no substitute owner readiness is inferred`,
539
+ ],
540
+ };
541
+ };
542
+ const systemViewForTopic = (topic) => {
543
+ const normalized = topic.toLowerCase();
544
+ return normalized.includes("collab")
545
+ ? "collaboration"
546
+ : normalized.includes("model")
547
+ ? "model"
548
+ : normalized.includes("worker")
549
+ ? "worker"
550
+ : normalized.includes("task")
551
+ ? "task"
552
+ : normalized.includes("carrier")
553
+ ? "carrier"
554
+ : normalized.includes("deploy")
555
+ ? "deployment"
556
+ : normalized.includes("artifact")
557
+ ? "artifact"
558
+ : normalized.includes("execution")
559
+ ? "execution"
560
+ : null;
561
+ };
562
+ const taskOperations = new Map();
563
+ for (const name of publicOperationNames) {
564
+ const candidate = Reflect.get(task.commands, name) ??
565
+ Reflect.get(task.queries, name) ??
566
+ Reflect.get(task.documents, name);
567
+ if (typeof candidate === "function")
568
+ taskOperations.set(name, candidate);
569
+ }
570
+ const queryOperations = new Set([
571
+ "getTaskGroup",
572
+ "listTasks",
573
+ "getTask",
574
+ "getTaskDriveProjection",
575
+ "getNodeContext",
576
+ "listPendingMessages",
577
+ "listTaskEvents",
578
+ "getTaskDocument",
579
+ ]);
580
+ const taskMutationOperations = new Set([
581
+ "startNode",
582
+ "completeNode",
583
+ "waitNode",
584
+ "failNode",
585
+ "reopenNode",
586
+ "putTaskDocument",
587
+ ]);
588
+ // Unified external Task-scoped Action admission. TaskRoleBinding is a Task
589
+ // owner fact, so participant + canonical Worker identity are derived from the
590
+ // Task owner facts, never from an untrusted body roleRef/workerRef. Reads only
591
+ // gate participation (terminal Tasks stay readable); mutations and Execution
592
+ // additionally run the full Agent Worker validation (which rejects terminal
593
+ // Tasks) before acting.
594
+ const admitTaskParticipant = async (taskId, authenticatedRoleRef, suppliedWorkerRef) => {
595
+ const binding = taskFacts(taskId).roleBindings.find((candidate) => candidate.roleRef === authenticatedRoleRef);
596
+ if (!binding?.workerRef)
597
+ throw Object.assign(new Error("TASK_ROLE_BINDING_REQUIRED"), {
598
+ httpStatus: 403,
599
+ });
600
+ if (suppliedWorkerRef !== undefined &&
601
+ suppliedWorkerRef !== binding.workerRef)
602
+ throw Object.assign(new Error("TASK_WORKER_BINDING_MISMATCH"), {
603
+ httpStatus: 403,
604
+ });
605
+ return binding.workerRef;
606
+ };
607
+ const admitExecutionRead = async (authenticatedRoleRef, rawRecord) => {
608
+ const record = object(rawRecord, "execution read record");
609
+ if (record.callerRef !== authenticatedRoleRef)
610
+ throw Object.assign(new Error("EXECUTION_CALLER_MISMATCH"), {
611
+ httpStatus: 403,
612
+ });
613
+ if (typeof record.taskId === "string") {
614
+ if (record.roleRef !== authenticatedRoleRef)
615
+ throw Object.assign(new Error("EXECUTION_ROLE_SCOPE_MISMATCH"), {
616
+ httpStatus: 403,
617
+ });
618
+ if (typeof record.workerRef !== "string")
619
+ throw Object.assign(new Error("EXECUTION_WORKER_SCOPE_REQUIRED"), {
620
+ httpStatus: 403,
621
+ });
622
+ await admitTaskParticipant(record.taskId, authenticatedRoleRef, record.workerRef);
623
+ }
624
+ return rawRecord;
625
+ };
626
+ const route = async (operationId, authenticatedRoleRef, rawInput, context) => {
627
+ const role = agent.getRegisteredRole(authenticatedRoleRef);
628
+ if (!roleOperations[role.agentPackageRef]?.has(operationId))
629
+ throw Object.assign(new Error("ROLE_OPERATION_DENIED"), {
630
+ httpStatus: 403,
631
+ });
632
+ const input = object(rawInput, "action input");
633
+ if (operationId === "askPeer")
634
+ return agent.askPeer({ ...input, authenticatedRoleRef });
635
+ if (operationId === "replyPeer")
636
+ return agent.replyPeer({ ...input, authenticatedRoleRef });
637
+ const taskOperation = taskOperations.get(operationId);
638
+ if (taskOperation) {
639
+ let actorRef = authenticatedRoleRef;
640
+ if (typeof input.taskId === "string") {
641
+ const workerRef = await admitTaskParticipant(input.taskId, authenticatedRoleRef, typeof input.workerRef === "string" ? input.workerRef : undefined);
642
+ if (taskMutationOperations.has(operationId))
643
+ await agent.validateWorker({
644
+ authenticatedRoleRef,
645
+ taskId: input.taskId,
646
+ workerRef,
647
+ });
648
+ actorRef = workerRef;
649
+ }
650
+ let canonicalTaskInput = input;
651
+ if (context?.fileMaterializationInputs !== undefined) {
652
+ if (operationId !== "putTaskDocument")
653
+ throw Object.assign(new Error("FILE_MATERIALIZATION_UNSUPPORTED_OPERATION"), { httpStatus: 400 });
654
+ const taskMutationIdempotencyKey = string(input.idempotencyKey, "idempotencyKey");
655
+ const result = object(await execution.invoke("materializeExternalFiles", {
656
+ contract: "execution.external-file-materialization",
657
+ contractVersion: "1.0.0",
658
+ callerRef: authenticatedRoleRef,
659
+ idempotencyKey: `${taskMutationIdempotencyKey}:carrier-file-materialization`,
660
+ correlationId: taskMutationIdempotencyKey,
661
+ ...(typeof input.taskId === "string"
662
+ ? { taskId: input.taskId }
663
+ : {}),
664
+ roleRef: authenticatedRoleRef,
665
+ ...(typeof input.taskId === "string"
666
+ ? { workerRef: actorRef }
667
+ : {}),
668
+ files: context.fileMaterializationInputs,
669
+ }), "carrier file materialization result");
670
+ if (!Array.isArray(result.files) || result.files.length !== 1)
671
+ throw Object.assign(new Error("FILE_MATERIALIZATION_COUNT_INVALID"), {
672
+ httpStatus: 400,
673
+ });
674
+ const file = object(result.files[0], "materialized carrier file");
675
+ const content = string(file.content, "materialized carrier file content");
676
+ canonicalTaskInput = { ...input, content };
677
+ }
678
+ const taskResult = await taskOperation(queryOperations.has(operationId)
679
+ ? canonicalTaskInput
680
+ : { ...canonicalTaskInput, actorRef });
681
+ if (operationId === "getTaskDocument")
682
+ return fileBridgeOutputForTaskResult(taskResult);
683
+ return taskResult;
684
+ }
685
+ if (operationId === "executeCapability") {
686
+ const taskId = typeof input.taskId === "string" ? input.taskId : undefined;
687
+ let canonicalWorkerRef;
688
+ if (taskId) {
689
+ canonicalWorkerRef = await admitTaskParticipant(taskId, authenticatedRoleRef, typeof input.workerRef === "string" ? input.workerRef : undefined);
690
+ await agent.validateWorker({
691
+ authenticatedRoleRef,
692
+ taskId,
693
+ workerRef: canonicalWorkerRef,
694
+ });
695
+ }
696
+ const result = await execution.invoke(operationId, {
697
+ ...input,
698
+ callerRef: authenticatedRoleRef,
699
+ roleRef: authenticatedRoleRef,
700
+ ...(canonicalWorkerRef !== undefined
701
+ ? { workerRef: canonicalWorkerRef }
702
+ : {}),
703
+ });
704
+ // A normal Action may complete synchronously inside the current Worker Turn.
705
+ // Do not manufacture a Browser RESUME for every terminal Execution record.
706
+ // Only a future explicit async-completion signal may emit EXECUTION_RESULT_READY.
707
+ return result;
708
+ }
709
+ if (operationId === "getExecution") {
710
+ const record = await execution.invoke(operationId, {
711
+ ...input,
712
+ callerRef: authenticatedRoleRef,
713
+ });
714
+ return admitExecutionRead(authenticatedRoleRef, record);
715
+ }
716
+ if (operationId === "readExecutionOutput") {
717
+ const record = await execution.invoke("getExecution", {
718
+ contract: "execution",
719
+ contractVersion: "1.0.0",
720
+ executionRef: string(input.executionRef, "executionRef"),
721
+ callerRef: authenticatedRoleRef,
722
+ });
723
+ await admitExecutionRead(authenticatedRoleRef, record);
724
+ return execution.invoke(operationId, {
725
+ ...input,
726
+ callerRef: authenticatedRoleRef,
727
+ });
728
+ }
729
+ throw new Error("OPERATION_NOT_ROUTED");
730
+ };
731
+ const browserOwnerPorts = Object.freeze({
732
+ task: Object.freeze({
733
+ async getWorkerBinding(taskId, roleRef) {
734
+ const binding = taskFacts(taskId).roleBindings.find((candidate) => candidate.roleRef === roleRef);
735
+ return binding?.workerRef
736
+ ? {
737
+ workerRef: binding.workerRef,
738
+ conversationLocator: binding.conversationLocator,
739
+ }
740
+ : null;
741
+ },
742
+ async bindWorker(binding) {
743
+ const current = unwrap(task.queries.getTask({ taskId: binding.taskId }));
744
+ const declared = current.roleBindings.find((item) => item.roleRef === binding.roleRef);
745
+ if (!declared)
746
+ throw new Error("AGENT_PACKAGE_NOT_ELIGIBLE");
747
+ if (declared.workerRef === binding.workerRef)
748
+ return;
749
+ if (declared.workerRef)
750
+ throw new Error("TASK_ROLE_BINDING_CONFLICT");
751
+ unwrap(task.commands.bindTaskWorker({
752
+ taskId: binding.taskId,
753
+ agentPackageRef: declared.agentPackageRef,
754
+ roleRef: binding.roleRef,
755
+ workerRef: binding.workerRef,
756
+ conversationLocator: binding.conversationLocator,
757
+ expectedTaskVersion: current.version,
758
+ actorRef: "platform-host:worker-provisioning",
759
+ idempotencyKey: `browser-bind:${binding.taskId}:${binding.roleRef}:${binding.workerRef}`,
760
+ }));
761
+ },
762
+ }),
763
+ agent: Object.freeze({
764
+ async listPendingMessages(limit) {
765
+ const messages = await agent.listPendingCollaborationMessages({
766
+ limit,
767
+ });
768
+ return messages
769
+ .filter((message) => message.lastDeliveryErrorCode !== "UNKNOWN")
770
+ .map((message) => ({ ...message, status: "PENDING" }));
771
+ },
772
+ async getPendingMessage(messageRef) {
773
+ const message = agent.getCollaborationMessage({
774
+ messageId: messageRef,
775
+ });
776
+ if (message.status !== "PENDING")
777
+ throw new Error("COLLABORATION_MESSAGE_NOT_PENDING");
778
+ // A terminal Task must never re-enter the physical Browser delivery path.
779
+ if (taskIsTerminal(taskFacts(message.taskId).status))
780
+ throw new Error("TASK_TERMINAL");
781
+ return { ...message, status: "PENDING" };
782
+ },
783
+ async reportDeliveryOutcome(input) {
784
+ const message = agent.getCollaborationMessage({
785
+ messageId: input.messageRef,
786
+ });
787
+ if (message.status === "DELIVERED")
788
+ return;
789
+ await agent.reportCollaborationDelivery({
790
+ messageId: message.messageId,
791
+ expectedMessageVersion: message.version,
792
+ outcome: input.outcome,
793
+ observedRoleRef: message.targetRoleRef,
794
+ observedWorkerRef: message.targetWorkerRef,
795
+ ...(input.executionRef ? { executionRef: input.executionRef } : {}),
796
+ ...(input.evidenceRef ? { evidenceRef: input.evidenceRef } : {}),
797
+ ...(input.errorCode ? { errorCode: input.errorCode } : {}),
798
+ });
799
+ },
800
+ }),
801
+ });
802
+ const authorizeExecution = async (request) => {
803
+ try {
804
+ const browserCapability = request.capability === "worker.create" ||
805
+ request.capability === "worker.restore" ||
806
+ request.capability === "worker.wake" ||
807
+ request.capability === "collaboration.deliver";
808
+ const internalBrowserCaller = request.callerRef === "platform-host:task-observer" ||
809
+ request.callerRef === "platform-host:carrier-controller" ||
810
+ request.callerRef === "extension:task-observer" ||
811
+ request.callerRef === "extension:collaboration-carrier";
812
+ if (!internalBrowserCaller)
813
+ agent.getRegisteredRole(request.callerRef);
814
+ if (request.roleRef &&
815
+ !internalBrowserCaller &&
816
+ request.roleRef !== request.callerRef)
817
+ return false;
818
+ if (request.projectRoot &&
819
+ resolve(request.projectRoot) !== config.workspaceRoot)
820
+ return false;
821
+ if ((request.nodeId || request.runNo) && !request.taskId)
822
+ return false;
823
+ if (request.workerRef && !request.taskId)
824
+ return false;
825
+ if (internalBrowserCaller && (!browserCapability || !request.taskId))
826
+ return false;
827
+ if (browserCapability && !request.taskId)
828
+ return false;
829
+ if (request.taskId) {
830
+ const taskFact = taskFacts(request.taskId);
831
+ if (request.capability === "collaboration.deliver" &&
832
+ taskIsTerminal(taskFact.status))
833
+ return false;
834
+ if (request.workerRef && !internalBrowserCaller)
835
+ await agent.validateWorker({
836
+ authenticatedRoleRef: request.callerRef,
837
+ taskId: request.taskId,
838
+ workerRef: request.workerRef,
839
+ });
840
+ if (request.nodeId)
841
+ unwrap(task.queries.getNodeContext({
842
+ taskId: request.taskId,
843
+ nodeId: request.nodeId,
844
+ ...(request.runNo ? { runNo: request.runNo } : {}),
845
+ }));
846
+ const browserInput = object(request.input, "execution input");
847
+ if (browserCapability) {
848
+ const targetRoleRef = string(browserInput.roleRef, "input.roleRef");
849
+ agent.getRegisteredRole(targetRoleRef);
850
+ const binding = taskFact.roleBindings.find((candidate) => candidate.roleRef === targetRoleRef);
851
+ if (!binding)
852
+ return false;
853
+ if (!internalBrowserCaller && targetRoleRef !== request.callerRef)
854
+ return false;
855
+ if (request.capability !== "worker.create") {
856
+ const targetWorkerRef = string(browserInput.workerRef, "input.workerRef");
857
+ if (binding.workerRef !== targetWorkerRef)
858
+ return false;
859
+ }
860
+ }
861
+ }
862
+ return true;
863
+ }
864
+ catch {
865
+ return false;
866
+ }
867
+ };
868
+ const taskDriverPorts = Object.freeze({
869
+ async getTask(taskId) {
870
+ const current = unwrap(task.queries.getTask({ taskId }));
871
+ return {
872
+ taskId: current.taskId,
873
+ status: current.status,
874
+ version: current.version,
875
+ currentNodeId: current.currentNodeId,
876
+ roleBindings: current.roleBindings,
877
+ };
878
+ },
879
+ async getNodeContext(taskId, nodeId) {
880
+ const current = unwrap(task.queries.getNodeContext({ taskId, nodeId }));
881
+ return {
882
+ task: {
883
+ taskId: current.task.taskId,
884
+ status: current.task.status,
885
+ version: current.task.version,
886
+ },
887
+ node: {
888
+ nodeId: current.node.nodeId,
889
+ status: current.node.status,
890
+ version: current.node.version,
891
+ runNo: current.node.runNo,
892
+ requiredAgentPackageRef: current.node.requiredAgentPackageRef,
893
+ workerRef: current.node.workerRef,
894
+ },
895
+ };
896
+ },
897
+ async getTaskDriveProjection(taskId) {
898
+ return unwrap(task.queries.getTaskDriveProjection({ taskId }));
899
+ },
900
+ async startTask(input) {
901
+ return unwrap(task.commands.startTask({
902
+ ...input,
903
+ actorRef: "platform-host:task-observer",
904
+ }));
905
+ },
906
+ async startNode(input) {
907
+ return unwrap(task.commands.startNode({
908
+ ...input,
909
+ actorRef: "platform-host:task-observer",
910
+ }));
911
+ },
912
+ });
913
+ const agentIdentityPorts = Object.freeze({
914
+ async getRegisteredRole(roleRef) {
915
+ const role = agent.getRegisteredRole(roleRef);
916
+ return { roleRef: role.roleRef };
917
+ },
918
+ });
919
+ const roleForPackage = (agentPackageRef) => {
920
+ const role = agent
921
+ .listRegisteredRoles()
922
+ .find((candidate) => candidate.agentPackageRef === agentPackageRef);
923
+ if (!role)
924
+ throw new Error("ROLE_NOT_FOUND");
925
+ return role;
926
+ };
927
+ const roleManagement = Object.freeze({
928
+ async invoke(operation, rawInput) {
929
+ const value = object(rawInput, "role management input");
930
+ if (operation === "role.register") {
931
+ const result = await agent.registerRole(value);
932
+ return { role: result.role, credential: result.credential };
933
+ }
934
+ if (operation === "role.list")
935
+ return agent.listRegisteredRoles();
936
+ const agentPackageRef = string(value.agentPackageRef, "agentPackageRef");
937
+ const role = roleForPackage(agentPackageRef);
938
+ if (operation === "role.show")
939
+ return role;
940
+ if (operation === "role.validate") {
941
+ const doctor = agent.doctorRoleStore();
942
+ const issuePrefix = `${role.roleRef}`;
943
+ const issues = doctor.issues.filter((issue) => issue.includes(issuePrefix));
944
+ if (role.carrierUrl !== `https://chatgpt.com/g/${role.roleRef}`)
945
+ issues.push(`ROLE_CARRIER_URL_MISMATCH:${role.roleRef}`);
946
+ const expectedPackageVersion = Reflect.get(value, "expectedPackageVersion");
947
+ if (expectedPackageVersion !== undefined &&
948
+ (typeof expectedPackageVersion !== "string" ||
949
+ expectedPackageVersion.length === 0))
950
+ throw new TypeError("expectedPackageVersion must be a non-empty string");
951
+ if (typeof expectedPackageVersion === "string" &&
952
+ role.registeredPackageVersion !== expectedPackageVersion)
953
+ issues.push(`ROLE_PACKAGE_VERSION_DRIFT:${role.roleRef}:${role.registeredPackageVersion}:${expectedPackageVersion}`);
954
+ return {
955
+ status: issues.length === 0 ? "PASS" : "FAIL",
956
+ role,
957
+ issues,
958
+ };
959
+ }
960
+ if (operation === "role.delete") {
961
+ await agent.deleteRole(role.roleRef);
962
+ return { deleted: true, roleRef: role.roleRef };
963
+ }
964
+ if (operation === "role.key.show")
965
+ return agent.showCredential(role.roleRef);
966
+ if (operation === "role.key.rotate")
967
+ return agent.rotateCredential(role.roleRef);
968
+ throw new Error("UNSUPPORTED_ROLE_MANAGEMENT_OPERATION");
969
+ },
970
+ });
971
+ const ensureTaskWorkers = async (taskId, options) => {
972
+ const provision = async (agentPackageRef) => {
973
+ let current = unwrap(task.queries.getTask({ taskId }));
974
+ const binding = current.roleBindings.find((candidate) => candidate.agentPackageRef === agentPackageRef);
975
+ if (!binding)
976
+ throw new Error("TASK_ROLE_BINDING_REQUIRED");
977
+ if (binding.workerRef && binding.conversationLocator)
978
+ return;
979
+ const role = roleForPackage(agentPackageRef);
980
+ const executionRecord = object(await execution.invoke("executeCapability", {
981
+ contract: "execution",
982
+ contractVersion: "1.0.0",
983
+ idempotencyKey: `new-task-worker:${taskId}:${role.roleRef}`,
984
+ callerRef: "platform-host:carrier-controller",
985
+ correlationId: `new-task:${taskId}`,
986
+ taskId,
987
+ roleRef: role.roleRef,
988
+ capability: "worker.create",
989
+ input: {
990
+ roleRef: role.roleRef,
991
+ roleUrl: role.carrierUrl,
992
+ bootstrapFingerprint: `new-task:${taskId}:${agentPackageRef}`,
993
+ },
994
+ }), "worker create execution");
995
+ if (executionRecord.status !== "SUCCEEDED")
996
+ throw new Error(`WORKER_CREATE_NOT_CONFIRMED:${String(executionRecord.status)}`);
997
+ current = unwrap(task.queries.getTask({ taskId }));
998
+ const persisted = current.roleBindings.find((candidate) => candidate.agentPackageRef === agentPackageRef);
999
+ if (!persisted?.workerRef || !persisted.conversationLocator)
1000
+ throw new Error("WORKER_CREATE_BINDING_NOT_PERSISTED");
1001
+ };
1002
+ // Dispatch all three fixed Workers concurrently. When `waitFor` names a
1003
+ // subset of roleRefs (the J1 Product path), only those results gate the
1004
+ // return; the remaining Worker creation is a durable, idempotent Execution
1005
+ // effect whose completion the ensureWorkers recovery reconciles from the
1006
+ // durable Task binding facts — never a bare in-memory promise.
1007
+ const waitFor = new Set(options?.waitFor ?? []);
1008
+ const shouldWait = (agentPackageRef) => waitFor.size === 0 ||
1009
+ waitFor.has(roleForPackage(agentPackageRef).roleRef);
1010
+ const pending = rolePackageRefs.map((agentPackageRef) => ({
1011
+ agentPackageRef,
1012
+ promise: provision(agentPackageRef),
1013
+ }));
1014
+ const awaited = pending.filter((entry) => shouldWait(entry.agentPackageRef));
1015
+ const deferred = pending.filter((entry) => !shouldWait(entry.agentPackageRef));
1016
+ for (const entry of deferred) {
1017
+ entry.promise.catch(() => {
1018
+ // Deferred Worker creation failure is recoverable: the durable
1019
+ // binding stays unset, so a later ensureWorkers pass re-provisions
1020
+ // only the missing role.
1021
+ });
1022
+ }
1023
+ const results = await Promise.allSettled(awaited.map((entry) => entry.promise));
1024
+ const failure = results.find((result) => result.status === "rejected");
1025
+ if (failure)
1026
+ throw failure.reason;
1027
+ return unwrap(task.queries.getTask({ taskId }));
1028
+ };
1029
+ const taskApplication = Object.freeze({
1030
+ async invoke(operation, rawInput) {
1031
+ const value = object(rawInput, "task application input");
1032
+ if (operation === "task.create") {
1033
+ const idempotencyKey = string(value.idempotencyKey, "idempotencyKey");
1034
+ const created = unwrap(task.commands.createTask({
1035
+ ...(typeof value.taskId === "string"
1036
+ ? { taskId: value.taskId }
1037
+ : {}),
1038
+ title: string(value.title, "title"),
1039
+ objective: string(value.objective, "objective"),
1040
+ plan: object(value.plan, "plan"),
1041
+ initialDocuments: Array.isArray(value.initialDocuments)
1042
+ ? value.initialDocuments
1043
+ : [],
1044
+ roleBindings: rolePackageRefs.map((agentPackageRef) => {
1045
+ const role = roleForPackage(agentPackageRef);
1046
+ return {
1047
+ agentPackageRef,
1048
+ roleRef: role.roleRef,
1049
+ workerRef: null,
1050
+ conversationLocator: null,
1051
+ };
1052
+ }),
1053
+ actorRef: "extension:human",
1054
+ idempotencyKey,
1055
+ }));
1056
+ // J1: return once the Product Worker is durably bound; Dev/Test
1057
+ // continue as recoverable durable effects without blocking Product
1058
+ // requirement discussion.
1059
+ return ensureTaskWorkers(created.taskId, {
1060
+ waitFor: [roleForPackage("@tomflow/proflow-agent-product").roleRef],
1061
+ });
1062
+ }
1063
+ if (operation === "task.ensureWorkers")
1064
+ return ensureTaskWorkers(string(value.taskId, "taskId"));
1065
+ if (operation === "task.list")
1066
+ return unwrap(task.queries.listTasks({
1067
+ ...(Array.isArray(value.statuses)
1068
+ ? { statuses: value.statuses }
1069
+ : {}),
1070
+ }));
1071
+ if (operation === "task.get")
1072
+ return unwrap(task.queries.getTask({ taskId: string(value.taskId, "taskId") }));
1073
+ if (operation === "task.start")
1074
+ return unwrap(task.commands.startTask({
1075
+ taskId: string(value.taskId, "taskId"),
1076
+ expectedTaskVersion: Number(value.expectedTaskVersion),
1077
+ actorRef: "extension:human",
1078
+ idempotencyKey: string(value.idempotencyKey, "idempotencyKey"),
1079
+ }));
1080
+ if (operation === "node.reopen")
1081
+ return unwrap(task.commands.reopenNode({
1082
+ taskId: string(value.taskId, "taskId"),
1083
+ nodeId: string(value.nodeId, "nodeId"),
1084
+ reason: string(value.reason, "reason"),
1085
+ expectedTaskVersion: Number(value.expectedTaskVersion),
1086
+ actorRef: "extension:human",
1087
+ idempotencyKey: string(value.idempotencyKey, "idempotencyKey"),
1088
+ }));
1089
+ throw new Error("UNSUPPORTED_TASK_APPLICATION_OPERATION");
1090
+ },
1091
+ });
1092
+ const approvalApplication = Object.freeze({
1093
+ async invoke(operation, rawInput) {
1094
+ const value = object(rawInput, "approval application input");
1095
+ if (operation === "approval.list")
1096
+ return execution.invoke("listExecutionApprovals", value);
1097
+ if (operation === "approval.get")
1098
+ return execution.invoke("getExecutionApproval", {
1099
+ approvalRef: string(value.approvalRef, "approvalRef"),
1100
+ });
1101
+ if (operation === "approval.request")
1102
+ return execution.invoke("requestExecutionApproval", {
1103
+ ...value,
1104
+ actorRef: "extension:human",
1105
+ });
1106
+ if (operation === "approval.allow" || operation === "approval.deny")
1107
+ return execution.invoke("decideExecutionApproval", {
1108
+ contract: "execution.approval",
1109
+ contractVersion: "1.0.0",
1110
+ approvalRef: string(value.approvalRef, "approvalRef"),
1111
+ actorRef: "extension:human",
1112
+ expectedVersion: Number(value.expectedVersion),
1113
+ decision: operation === "approval.allow" ? "ALLOW" : "DENY",
1114
+ ...(typeof value.reason === "string" ? { reason: value.reason } : {}),
1115
+ });
1116
+ if (operation === "approval.revoke")
1117
+ return execution.invoke("revokeExecutionApproval", {
1118
+ contract: "execution.approval",
1119
+ contractVersion: "1.0.0",
1120
+ approvalRef: string(value.approvalRef, "approvalRef"),
1121
+ actorRef: "extension:human",
1122
+ expectedVersion: Number(value.expectedVersion),
1123
+ reason: string(value.reason, "reason"),
1124
+ });
1125
+ throw new Error("UNSUPPORTED_APPROVAL_APPLICATION_OPERATION");
1126
+ },
1127
+ });
1128
+ const observerApplication = Object.freeze({
1129
+ async invoke(operation, rawInput) {
1130
+ const value = object(rawInput, "observer application input");
1131
+ if (operation === "task.projection")
1132
+ return taskDriverPorts.getTaskDriveProjection(string(value.taskId, "taskId"));
1133
+ if (operation === "browser.binding")
1134
+ return browserOwnerPorts.task.getWorkerBinding(string(value.taskId, "taskId"), string(value.roleRef, "roleRef"));
1135
+ if (operation === "browser.bindWorker") {
1136
+ await browserOwnerPorts.task.bindWorker({
1137
+ taskId: string(value.taskId, "taskId"),
1138
+ roleRef: string(value.roleRef, "roleRef"),
1139
+ workerRef: string(value.workerRef, "workerRef"),
1140
+ conversationLocator: string(value.conversationLocator, "conversationLocator"),
1141
+ });
1142
+ return { bound: true };
1143
+ }
1144
+ if (operation === "collaboration.binding")
1145
+ return browserOwnerPorts.task.getWorkerBinding(string(value.taskId, "taskId"), string(value.roleRef, "roleRef"));
1146
+ if (operation === "collaboration.listPending")
1147
+ return browserOwnerPorts.agent.listPendingMessages(Number(value.limit ?? 50));
1148
+ if (operation === "collaboration.getPending")
1149
+ return browserOwnerPorts.agent.getPendingMessage(string(value.messageRef, "messageRef"));
1150
+ if (operation === "collaboration.execute") {
1151
+ const request = object(value.request, "collaboration execution request");
1152
+ if (request.capability !== "collaboration.deliver")
1153
+ throw new Error("COLLABORATION_CARRIER_CAPABILITY_DENIED");
1154
+ return execution.invoke("executeCapability", {
1155
+ ...request,
1156
+ callerRef: "extension:collaboration-carrier",
1157
+ capability: "collaboration.deliver",
1158
+ });
1159
+ }
1160
+ if (operation === "collaboration.reportDelivery") {
1161
+ const outcome = string(value.outcome, "outcome");
1162
+ if (outcome !== "DELIVERED" &&
1163
+ outcome !== "FAILED" &&
1164
+ outcome !== "UNKNOWN")
1165
+ throw new Error("COLLABORATION_DELIVERY_OUTCOME_INVALID");
1166
+ await browserOwnerPorts.agent.reportDeliveryOutcome({
1167
+ messageRef: string(value.messageRef, "messageRef"),
1168
+ outcome,
1169
+ ...(typeof value.evidenceRef === "string"
1170
+ ? { evidenceRef: value.evidenceRef }
1171
+ : {}),
1172
+ ...(typeof value.executionRef === "string"
1173
+ ? { executionRef: value.executionRef }
1174
+ : {}),
1175
+ ...(typeof value.errorCode === "string"
1176
+ ? { errorCode: value.errorCode }
1177
+ : {}),
1178
+ });
1179
+ return { reported: true };
1180
+ }
1181
+ if (operation === "execution.listSignals")
1182
+ return execution.invoke("listExecutionObserverSignals", {
1183
+ limit: Number(value.limit ?? 50),
1184
+ });
1185
+ if (operation === "execution.ackSignal")
1186
+ return execution.invoke("acknowledgeExecutionObserverSignal", {
1187
+ signalRef: string(value.signalRef, "signalRef"),
1188
+ });
1189
+ if (operation === "task.wake") {
1190
+ const taskId = string(value.taskId, "taskId");
1191
+ const nodeId = string(value.nodeId, "nodeId");
1192
+ const runNo = Number(value.runNo);
1193
+ const roleRef = string(value.roleRef, "roleRef");
1194
+ const workerRef = string(value.workerRef, "workerRef");
1195
+ const trigger = string(value.trigger, "trigger");
1196
+ const underlyingRef = typeof value.underlyingRef === "string"
1197
+ ? value.underlyingRef
1198
+ : "none";
1199
+ return execution.invoke("executeCapability", {
1200
+ contract: "execution",
1201
+ contractVersion: "1.0.0",
1202
+ idempotencyKey: `task-observer-wake:${taskId}:${nodeId}:${runNo}:${trigger}:${underlyingRef}`,
1203
+ callerRef: "extension:task-observer",
1204
+ correlationId: `task-observer:${taskId}:${nodeId}:${runNo}`,
1205
+ taskId,
1206
+ nodeId,
1207
+ runNo,
1208
+ roleRef,
1209
+ workerRef,
1210
+ capability: "worker.wake",
1211
+ input: {
1212
+ roleRef,
1213
+ workerRef,
1214
+ taskId,
1215
+ nodeId,
1216
+ runNo,
1217
+ trigger,
1218
+ fingerprint: `wake:${taskId}:${nodeId}:${runNo}:${trigger}:${underlyingRef}`,
1219
+ },
1220
+ });
1221
+ }
1222
+ if (operation === "task.diagnostic") {
1223
+ const response = object(await model.invoke("infer", {
1224
+ contractVersion: "1.0.0",
1225
+ specRef: "task.diagnostic.v1",
1226
+ mode: "reason",
1227
+ priority: "business",
1228
+ trace: {
1229
+ callerRef: "extension:task-observer",
1230
+ correlationId: string(value.correlationId, "correlationId"),
1231
+ taskId: string(value.taskId, "taskId"),
1232
+ nodeId: string(value.nodeId, "nodeId"),
1233
+ },
1234
+ payload: value.payload,
1235
+ }), "task diagnostic inference");
1236
+ if (response.status !== "SUCCEEDED") {
1237
+ const error = typeof response.error === "object" && response.error !== null
1238
+ ? response.error
1239
+ : {};
1240
+ const code = error.code === "CONTEXT_TOO_LARGE"
1241
+ ? "CONTEXT_TOO_LARGE"
1242
+ : error.code === "MODEL_UNAVAILABLE" ||
1243
+ error.code === "CAPABILITY_UNSUPPORTED"
1244
+ ? "REASON_UNAVAILABLE"
1245
+ : "REASON_FAILED";
1246
+ return { ok: false, errorCode: code };
1247
+ }
1248
+ return taskDiagnosticReasonResultSchema.parse(response.data);
1249
+ }
1250
+ if (operation === "system.view")
1251
+ return boundedSystemView(string(value.view, "view"));
1252
+ if (operation === "system.drilldown") {
1253
+ const topic = string(value.topic, "topic");
1254
+ const view = systemViewForTopic(topic);
1255
+ if (view)
1256
+ return boundedSystemView(view);
1257
+ return {
1258
+ summary: `drilldown topic ${topic} cannot be attributed to a formal owner view`,
1259
+ health: "UNKNOWN",
1260
+ projectionStatus: "UNAVAILABLE",
1261
+ findings: [
1262
+ "unknown drilldown topic is not defaulted to execution or another owner",
1263
+ ],
1264
+ };
1265
+ }
1266
+ if (operation === "system.reason") {
1267
+ const assessmentRef = string(value.assessmentRef, "assessmentRef");
1268
+ const observerPayload = object(value.payload, "system observer reason payload");
1269
+ const payloadAssessmentRef = string(observerPayload.assessmentRef, "payload.assessmentRef");
1270
+ if (payloadAssessmentRef !== assessmentRef) {
1271
+ throw new Error("SYSTEM_OBSERVER_ASSESSMENT_REF_MISMATCH");
1272
+ }
1273
+ const { assessmentRef: _assessmentRef, kind, ...modelPayload } = observerPayload;
1274
+ const response = object(await model.invoke("infer", {
1275
+ contractVersion: "1.0.0",
1276
+ specRef: "system.health-assessment.v1",
1277
+ mode: "reason",
1278
+ priority: "background",
1279
+ trace: { callerRef: "extension:system-observer", assessmentRef },
1280
+ payload: {
1281
+ ...modelPayload,
1282
+ assessmentKind: string(kind, "payload.kind"),
1283
+ },
1284
+ }), "system observer inference");
1285
+ if (response.status !== "SUCCEEDED") {
1286
+ const error = typeof response.error === "object" && response.error !== null
1287
+ ? response.error
1288
+ : {};
1289
+ const code = error.code === "CONTEXT_TOO_LARGE"
1290
+ ? "CONTEXT_TOO_LARGE"
1291
+ : error.code === "MODEL_UNAVAILABLE" ||
1292
+ error.code === "CAPABILITY_UNSUPPORTED"
1293
+ ? "REASON_UNAVAILABLE"
1294
+ : "REASON_FAILED";
1295
+ return { ok: false, errorCode: code };
1296
+ }
1297
+ return systemObserverReasonResultSchema.parse(response.data);
1298
+ }
1299
+ throw new Error("UNSUPPORTED_OBSERVER_APPLICATION_OPERATION");
1300
+ },
1301
+ });
1302
+ return Object.freeze({
1303
+ route,
1304
+ browserOwnerPorts,
1305
+ authorizeExecution,
1306
+ taskDriverPorts,
1307
+ agentIdentityPorts,
1308
+ roleManagement,
1309
+ taskApplication,
1310
+ approvalApplication,
1311
+ observerApplication,
1312
+ async lookup(operationId, authenticatedRoleRef, input) {
1313
+ const value = object(input, "lookup input");
1314
+ if (operationId === "executeCapability") {
1315
+ if (value.executionRef) {
1316
+ const record = await execution.invoke("getExecution", {
1317
+ ...value,
1318
+ callerRef: authenticatedRoleRef,
1319
+ });
1320
+ return admitExecutionRead(authenticatedRoleRef, record);
1321
+ }
1322
+ const taskId = typeof value.taskId === "string" ? value.taskId : undefined;
1323
+ let canonicalWorkerRef;
1324
+ if (taskId)
1325
+ canonicalWorkerRef = await admitTaskParticipant(taskId, authenticatedRoleRef, typeof value.workerRef === "string" ? value.workerRef : undefined);
1326
+ const record = await execution.invoke("lookupExecutionIntent", {
1327
+ ...value,
1328
+ callerRef: authenticatedRoleRef,
1329
+ roleRef: authenticatedRoleRef,
1330
+ ...(canonicalWorkerRef ? { workerRef: canonicalWorkerRef } : {}),
1331
+ });
1332
+ return admitExecutionRead(authenticatedRoleRef, record);
1333
+ }
1334
+ return route(operationId, authenticatedRoleRef, value);
1335
+ },
1336
+ async readiness() {
1337
+ const diagnostics = taskStore.diagnostics();
1338
+ const taskStatus = {
1339
+ owner: "task",
1340
+ status: diagnostics.integrity === "ok" ? "READY" : "NOT_READY",
1341
+ liveness: "UP",
1342
+ detail: diagnostics,
1343
+ };
1344
+ const agentDoctor = agent.doctorRoleStore();
1345
+ const agentStatus = {
1346
+ owner: "agent",
1347
+ status: agentDoctor.status === "PASS" ? "READY" : "NOT_READY",
1348
+ liveness: "UP",
1349
+ detail: agentDoctor,
1350
+ };
1351
+ const [executionStatus, modelStatus] = await Promise.all([
1352
+ execution.readiness(),
1353
+ model.readiness(),
1354
+ ]);
1355
+ return {
1356
+ task: taskStatus,
1357
+ agent: agentStatus,
1358
+ execution: executionStatus,
1359
+ model: modelStatus,
1360
+ };
1361
+ },
1362
+ close() {
1363
+ agent.close();
1364
+ taskStore.close();
1365
+ },
1366
+ });
1367
+ }
1368
+ async function ensureRoleManagementCredential(stateRoot) {
1369
+ const path = join(stateRoot, "agent", "secrets", "role-management.token");
1370
+ await mkdir(join(stateRoot, "agent", "secrets"), {
1371
+ recursive: true,
1372
+ mode: 0o700,
1373
+ });
1374
+ if (!existsSync(path)) {
1375
+ const generated = randomBytes(32).toString("base64url");
1376
+ try {
1377
+ await writeFile(path, `${generated}\n`, { mode: 0o600, flag: "wx" });
1378
+ }
1379
+ catch (error) {
1380
+ if (!existsSync(path))
1381
+ throw error;
1382
+ }
1383
+ }
1384
+ await chmod(join(stateRoot, "agent", "secrets"), 0o700);
1385
+ await chmod(path, 0o600);
1386
+ const credential = (await readFile(path, "utf8")).trim();
1387
+ if (credential.length < 32)
1388
+ throw new Error("ROLE_MANAGEMENT_CREDENTIAL_INVALID");
1389
+ return credential;
1390
+ }
1391
+ async function ensureExecutionIdentityCredential(stateRoot) {
1392
+ const directory = join(stateRoot, "execution", "secrets");
1393
+ const path = join(directory, "execution-identity.token");
1394
+ await mkdir(directory, { recursive: true, mode: 0o700 });
1395
+ if (!existsSync(path)) {
1396
+ const generated = randomBytes(32).toString("base64url");
1397
+ try {
1398
+ await writeFile(path, `${generated}\n`, { mode: 0o600, flag: "wx" });
1399
+ }
1400
+ catch (error) {
1401
+ if (!existsSync(path))
1402
+ throw error;
1403
+ }
1404
+ }
1405
+ await chmod(directory, 0o700);
1406
+ await chmod(path, 0o600);
1407
+ const credential = (await readFile(path, "utf8")).trim();
1408
+ if (credential.length < 32)
1409
+ throw new Error("EXECUTION_IDENTITY_CREDENTIAL_INVALID");
1410
+ return credential;
1411
+ }
1412
+ async function ensureApprovalApplicationCredential(stateRoot) {
1413
+ const directory = join(stateRoot, "browser", "secrets");
1414
+ const path = join(directory, "approval-application.token");
1415
+ await mkdir(directory, { recursive: true, mode: 0o700 });
1416
+ if (!existsSync(path)) {
1417
+ const generated = randomBytes(32).toString("base64url");
1418
+ try {
1419
+ await writeFile(path, `${generated}\n`, { mode: 0o600, flag: "wx" });
1420
+ }
1421
+ catch (error) {
1422
+ if (!existsSync(path))
1423
+ throw error;
1424
+ }
1425
+ }
1426
+ await chmod(directory, 0o700);
1427
+ await chmod(path, 0o600);
1428
+ const credential = (await readFile(path, "utf8")).trim();
1429
+ if (credential.length < 32)
1430
+ throw new Error("APPROVAL_APPLICATION_CREDENTIAL_INVALID");
1431
+ return credential;
1432
+ }
1433
+ async function ensureTaskApplicationCredential(stateRoot) {
1434
+ const directory = join(stateRoot, "browser", "secrets");
1435
+ const path = join(directory, "task-application.token");
1436
+ await mkdir(directory, { recursive: true, mode: 0o700 });
1437
+ if (!existsSync(path)) {
1438
+ const generated = randomBytes(32).toString("base64url");
1439
+ try {
1440
+ await writeFile(path, `${generated}\n`, { mode: 0o600, flag: "wx" });
1441
+ }
1442
+ catch (error) {
1443
+ if (!existsSync(path))
1444
+ throw error;
1445
+ }
1446
+ }
1447
+ await chmod(directory, 0o700);
1448
+ await chmod(path, 0o600);
1449
+ const credential = (await readFile(path, "utf8")).trim();
1450
+ if (credential.length < 32)
1451
+ throw new Error("TASK_APPLICATION_CREDENTIAL_INVALID");
1452
+ return credential;
1453
+ }
1454
+ async function readPrivateTransportCredential(file, name) {
1455
+ const info = await stat(file);
1456
+ if (process.platform !== "win32" && (info.mode & 0o077) !== 0)
1457
+ throw new Error(`${name}_TRANSPORT_CREDENTIAL_PERMISSIONS_INVALID`);
1458
+ const credential = (await readFile(file, "utf8")).trim();
1459
+ if (credential.length < 32)
1460
+ throw new Error(`${name}_TRANSPORT_CREDENTIAL_INVALID`);
1461
+ return credential;
1462
+ }
1463
+ async function readGatewayTransportCredential(file) {
1464
+ return readPrivateTransportCredential(file, "GATEWAY");
1465
+ }
1466
+ async function readModelTransportCredential(file) {
1467
+ return readPrivateTransportCredential(file, "MODEL");
1468
+ }
1469
+ async function readExecutionTransportCredential(file) {
1470
+ return readPrivateTransportCredential(file, "EXECUTION");
1471
+ }
1472
+ function managementCredentialMatches(header, expected) {
1473
+ if (!header?.startsWith("Bearer "))
1474
+ return false;
1475
+ const supplied = Buffer.from(header.slice("Bearer ".length));
1476
+ const target = Buffer.from(expected);
1477
+ return supplied.length === target.length && timingSafeEqual(supplied, target);
1478
+ }
1479
+ export function createPlatformHost(input) {
1480
+ if (input.executionCredential && input.executionCredential.length < 32)
1481
+ throw new TypeError("execution credential must contain at least 32 characters");
1482
+ let lifecycle = "STOPPED";
1483
+ let accepting = false;
1484
+ let graph;
1485
+ let server;
1486
+ let roleManagementCredential;
1487
+ let taskApplicationCredential;
1488
+ let approvalApplicationCredential;
1489
+ let executionIdentityCredential;
1490
+ let gatewayTransportCredential;
1491
+ let modelTransportCredential;
1492
+ let executionTransportCredential;
1493
+ const active = new Set();
1494
+ const log = (event, detail = {}) => input.log?.({
1495
+ timestamp: new Date().toISOString(),
1496
+ component: "platform-host-process",
1497
+ event,
1498
+ ...detail,
1499
+ });
1500
+ const stoppedDependencies = () => ({
1501
+ task: { owner: "task", status: "NOT_READY", liveness: "DOWN" },
1502
+ agent: { owner: "agent", status: "NOT_READY", liveness: "DOWN" },
1503
+ execution: {
1504
+ owner: "execution",
1505
+ status: "NOT_READY",
1506
+ liveness: "DOWN",
1507
+ },
1508
+ model: { owner: "model", status: "NOT_READY", liveness: "DOWN" },
1509
+ });
1510
+ const status = async () => {
1511
+ const dependencies = graph
1512
+ ? await graph.readiness()
1513
+ : stoppedDependencies();
1514
+ return {
1515
+ process: lifecycle,
1516
+ liveness: lifecycle === "STOPPED" ? "DOWN" : "UP",
1517
+ transport: server ? "UP" : "DOWN",
1518
+ readiness: accepting &&
1519
+ Object.values(dependencies).every((item) => item.status === "READY")
1520
+ ? "READY"
1521
+ : "NOT_READY",
1522
+ accepting,
1523
+ inFlight: active.size,
1524
+ dependencies,
1525
+ };
1526
+ };
1527
+ const browserOwnerPorts = Object.freeze({
1528
+ task: Object.freeze({
1529
+ async getWorkerBinding(taskId, roleRef) {
1530
+ if (!graph)
1531
+ throw new Error("PLATFORM_HOST_NOT_RUNNING");
1532
+ return graph.browserOwnerPorts.task.getWorkerBinding(taskId, roleRef);
1533
+ },
1534
+ async bindWorker(binding) {
1535
+ if (!graph)
1536
+ throw new Error("PLATFORM_HOST_NOT_RUNNING");
1537
+ return graph.browserOwnerPorts.task.bindWorker(binding);
1538
+ },
1539
+ }),
1540
+ agent: Object.freeze({
1541
+ async listPendingMessages(limit) {
1542
+ if (!graph)
1543
+ throw new Error("PLATFORM_HOST_NOT_RUNNING");
1544
+ return graph.browserOwnerPorts.agent.listPendingMessages(limit);
1545
+ },
1546
+ async getPendingMessage(messageRef) {
1547
+ if (!graph)
1548
+ throw new Error("PLATFORM_HOST_NOT_RUNNING");
1549
+ return graph.browserOwnerPorts.agent.getPendingMessage(messageRef);
1550
+ },
1551
+ async reportDeliveryOutcome(input) {
1552
+ if (!graph)
1553
+ throw new Error("PLATFORM_HOST_NOT_RUNNING");
1554
+ return graph.browserOwnerPorts.agent.reportDeliveryOutcome(input);
1555
+ },
1556
+ }),
1557
+ });
1558
+ const executionIdentity = Object.freeze({
1559
+ async authorize(request) {
1560
+ if (!graph)
1561
+ return false;
1562
+ return graph.authorizeExecution(request);
1563
+ },
1564
+ });
1565
+ const taskDriverPorts = Object.freeze({
1566
+ async getTask(taskId) {
1567
+ if (!graph)
1568
+ throw new Error("PLATFORM_HOST_NOT_RUNNING");
1569
+ return graph.taskDriverPorts.getTask(taskId);
1570
+ },
1571
+ async getTaskDriveProjection(taskId) {
1572
+ if (!graph)
1573
+ throw new Error("PLATFORM_HOST_NOT_RUNNING");
1574
+ return graph.taskDriverPorts.getTaskDriveProjection(taskId);
1575
+ },
1576
+ async getNodeContext(taskId, nodeId) {
1577
+ if (!graph)
1578
+ throw new Error("PLATFORM_HOST_NOT_RUNNING");
1579
+ return graph.taskDriverPorts.getNodeContext(taskId, nodeId);
1580
+ },
1581
+ async startTask(input) {
1582
+ if (!graph)
1583
+ throw new Error("PLATFORM_HOST_NOT_RUNNING");
1584
+ return graph.taskDriverPorts.startTask(input);
1585
+ },
1586
+ async startNode(input) {
1587
+ if (!graph)
1588
+ throw new Error("PLATFORM_HOST_NOT_RUNNING");
1589
+ return graph.taskDriverPorts.startNode(input);
1590
+ },
1591
+ });
1592
+ const agentIdentityPorts = Object.freeze({
1593
+ async getRegisteredRole(roleRef) {
1594
+ if (!graph)
1595
+ throw new Error("PLATFORM_HOST_NOT_RUNNING");
1596
+ return graph.agentIdentityPorts.getRegisteredRole(roleRef);
1597
+ },
1598
+ });
1599
+ const respond = (response, code, value) => {
1600
+ response.writeHead(code, {
1601
+ "content-type": "application/json; charset=utf-8",
1602
+ "cache-control": "no-store",
1603
+ });
1604
+ response.end(JSON.stringify(value));
1605
+ };
1606
+ const start = async () => {
1607
+ if (lifecycle !== "STOPPED")
1608
+ throw new Error("platform-host is not stopped");
1609
+ lifecycle = "STARTING";
1610
+ try {
1611
+ roleManagementCredential = await ensureRoleManagementCredential(input.config.stateRoot);
1612
+ taskApplicationCredential = await ensureTaskApplicationCredential(input.config.stateRoot);
1613
+ approvalApplicationCredential = await ensureApprovalApplicationCredential(input.config.stateRoot);
1614
+ executionIdentityCredential = await ensureExecutionIdentityCredential(input.config.stateRoot);
1615
+ gatewayTransportCredential = input.config.gatewayTransportCredentialFile
1616
+ ? await readGatewayTransportCredential(input.config.gatewayTransportCredentialFile)
1617
+ : undefined;
1618
+ modelTransportCredential = input.config.modelTransportCredentialFile
1619
+ ? await readModelTransportCredential(input.config.modelTransportCredentialFile)
1620
+ : undefined;
1621
+ executionTransportCredential = input.config
1622
+ .executionTransportCredentialFile
1623
+ ? await readExecutionTransportCredential(input.config.executionTransportCredentialFile)
1624
+ : input.executionCredential;
1625
+ log("DEPENDENCY_INITIALIZATION_STARTED", {
1626
+ order: ["task", "agent", "execution-client", "model-client"],
1627
+ });
1628
+ graph = await constructGraph(input.config, executionTransportCredential, modelTransportCredential);
1629
+ await graph.readiness();
1630
+ server = createServer((request, response) => {
1631
+ const work = (async () => {
1632
+ const url = new URL(request.url ?? "/", "http://platform-host.local");
1633
+ if (request.method === "GET" && url.pathname === "/health")
1634
+ return respond(response, 200, {
1635
+ status: lifecycle === "STOPPED" ? "DOWN" : "UP",
1636
+ });
1637
+ if (request.method === "GET" && url.pathname === "/ready") {
1638
+ const current = await status();
1639
+ return respond(response, current.readiness === "READY" ? 200 : 503, current);
1640
+ }
1641
+ if (!accepting || !graph)
1642
+ return respond(response, 503, { error: "SERVICE_DRAINING" });
1643
+ try {
1644
+ if (request.method === "GET" &&
1645
+ url.pathname === "/internal/execution/identity/ready") {
1646
+ if (!executionIdentityCredential ||
1647
+ !managementCredentialMatches(request.headers.authorization, executionIdentityCredential))
1648
+ return respond(response, 401, {
1649
+ error: "EXECUTION_IDENTITY_AUTH_FAILED",
1650
+ });
1651
+ return respond(response, 200, { ready: true });
1652
+ }
1653
+ if (request.method === "POST" &&
1654
+ url.pathname === "/internal/execution/authorize") {
1655
+ if (!executionIdentityCredential ||
1656
+ !managementCredentialMatches(request.headers.authorization, executionIdentityCredential))
1657
+ return respond(response, 401, {
1658
+ error: "EXECUTION_IDENTITY_AUTH_FAILED",
1659
+ });
1660
+ const chunks = [];
1661
+ for await (const chunk of request)
1662
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1663
+ const body = object(JSON.parse(Buffer.concat(chunks).toString("utf8")), "execution identity request");
1664
+ return respond(response, 200, {
1665
+ authorized: await graph.authorizeExecution(body),
1666
+ });
1667
+ }
1668
+ if (request.method === "POST" &&
1669
+ url.pathname === "/application/approval") {
1670
+ if (!approvalApplicationCredential ||
1671
+ !managementCredentialMatches(request.headers.authorization, approvalApplicationCredential))
1672
+ return respond(response, 401, {
1673
+ error: "APPROVAL_APPLICATION_AUTH_FAILED",
1674
+ });
1675
+ const chunks = [];
1676
+ let bytes = 0;
1677
+ for await (const chunk of request) {
1678
+ const buffer = Buffer.isBuffer(chunk)
1679
+ ? chunk
1680
+ : Buffer.from(chunk);
1681
+ bytes += buffer.byteLength;
1682
+ if (bytes > 262_144)
1683
+ throw new TypeError("REQUEST_BODY_TOO_LARGE");
1684
+ chunks.push(buffer);
1685
+ }
1686
+ const body = object(JSON.parse(Buffer.concat(chunks).toString("utf8")), "approval application request");
1687
+ try {
1688
+ const result = await graph.approvalApplication.invoke(string(body.operation, "operation"), body.input ?? {});
1689
+ return respond(response, 200, result);
1690
+ }
1691
+ catch (error) {
1692
+ return respond(response, 400, {
1693
+ error: error instanceof Error ? error.message : "INVALID_REQUEST",
1694
+ });
1695
+ }
1696
+ }
1697
+ if (request.method === "POST" &&
1698
+ url.pathname === "/application/log") {
1699
+ if (!taskApplicationCredential ||
1700
+ !managementCredentialMatches(request.headers.authorization, taskApplicationCredential))
1701
+ return respond(response, 401, {
1702
+ error: "BROWSER_LOG_AUTH_FAILED",
1703
+ });
1704
+ const chunks = [];
1705
+ let bytes = 0;
1706
+ for await (const chunk of request) {
1707
+ const buffer = Buffer.isBuffer(chunk)
1708
+ ? chunk
1709
+ : Buffer.from(chunk);
1710
+ bytes += buffer.byteLength;
1711
+ if (bytes > 32_768)
1712
+ throw new TypeError("REQUEST_BODY_TOO_LARGE");
1713
+ chunks.push(buffer);
1714
+ }
1715
+ try {
1716
+ const entry = browserStructuredLogSchema.parse(JSON.parse(Buffer.concat(chunks).toString("utf8")));
1717
+ const logPath = join(input.config.stateRoot, "logs", "browser-extension", "events.jsonl");
1718
+ await mkdir(dirname(logPath), { recursive: true, mode: 0o700 });
1719
+ await appendFile(logPath, `${JSON.stringify({ ...entry, receivedAt: new Date().toISOString() })}\n`, { mode: 0o600 });
1720
+ return respond(response, 200, { accepted: true });
1721
+ }
1722
+ catch (error) {
1723
+ return respond(response, 400, {
1724
+ error: error instanceof Error
1725
+ ? error.message
1726
+ : "INVALID_LOG_ENTRY",
1727
+ });
1728
+ }
1729
+ }
1730
+ if (request.method === "POST" &&
1731
+ url.pathname === "/application/observer") {
1732
+ if (!taskApplicationCredential ||
1733
+ !managementCredentialMatches(request.headers.authorization, taskApplicationCredential))
1734
+ return respond(response, 401, {
1735
+ error: "OBSERVER_APPLICATION_AUTH_FAILED",
1736
+ });
1737
+ const chunks = [];
1738
+ let bytes = 0;
1739
+ for await (const chunk of request) {
1740
+ const buffer = Buffer.isBuffer(chunk)
1741
+ ? chunk
1742
+ : Buffer.from(chunk);
1743
+ bytes += buffer.byteLength;
1744
+ if (bytes > 262_144)
1745
+ throw new TypeError("REQUEST_BODY_TOO_LARGE");
1746
+ chunks.push(buffer);
1747
+ }
1748
+ const body = object(JSON.parse(Buffer.concat(chunks).toString("utf8")), "observer application request");
1749
+ try {
1750
+ const result = await graph.observerApplication.invoke(string(body.operation, "operation"), body.input ?? {});
1751
+ return respond(response, 200, result);
1752
+ }
1753
+ catch (error) {
1754
+ return respond(response, 400, {
1755
+ error: error instanceof Error ? error.message : "INVALID_REQUEST",
1756
+ });
1757
+ }
1758
+ }
1759
+ if (request.method === "POST" &&
1760
+ url.pathname === "/application/task") {
1761
+ if (!taskApplicationCredential ||
1762
+ !managementCredentialMatches(request.headers.authorization, taskApplicationCredential))
1763
+ return respond(response, 401, {
1764
+ error: "TASK_APPLICATION_AUTH_FAILED",
1765
+ });
1766
+ const chunks = [];
1767
+ let bytes = 0;
1768
+ for await (const chunk of request) {
1769
+ const buffer = Buffer.isBuffer(chunk)
1770
+ ? chunk
1771
+ : Buffer.from(chunk);
1772
+ bytes += buffer.byteLength;
1773
+ if (bytes > 262_144)
1774
+ throw new TypeError("REQUEST_BODY_TOO_LARGE");
1775
+ chunks.push(buffer);
1776
+ }
1777
+ const body = object(JSON.parse(Buffer.concat(chunks).toString("utf8")), "task application request");
1778
+ try {
1779
+ const result = await graph.taskApplication.invoke(string(body.operation, "operation"), body.input ?? {});
1780
+ return respond(response, 200, result);
1781
+ }
1782
+ catch (error) {
1783
+ return respond(response, 400, {
1784
+ error: error instanceof Error ? error.message : "INVALID_REQUEST",
1785
+ });
1786
+ }
1787
+ }
1788
+ if (request.method === "POST" &&
1789
+ url.pathname === "/management/agent") {
1790
+ if (!roleManagementCredential ||
1791
+ !managementCredentialMatches(request.headers.authorization, roleManagementCredential))
1792
+ return respond(response, 401, {
1793
+ error: "MANAGEMENT_AUTH_FAILED",
1794
+ });
1795
+ const chunks = [];
1796
+ let bytes = 0;
1797
+ for await (const chunk of request) {
1798
+ const buffer = Buffer.isBuffer(chunk)
1799
+ ? chunk
1800
+ : Buffer.from(chunk);
1801
+ bytes += buffer.byteLength;
1802
+ if (bytes > 65_536)
1803
+ throw new TypeError("REQUEST_BODY_TOO_LARGE");
1804
+ chunks.push(buffer);
1805
+ }
1806
+ const body = object(JSON.parse(Buffer.concat(chunks).toString("utf8")), "management request");
1807
+ const operation = string(body.operation, "operation");
1808
+ try {
1809
+ const result = await graph.roleManagement.invoke(operation, body.input ?? {});
1810
+ return respond(response, 200, result);
1811
+ }
1812
+ catch (error) {
1813
+ const code = typeof error === "object" && error !== null
1814
+ ? Reflect.get(error, "code")
1815
+ : undefined;
1816
+ return respond(response, 400, {
1817
+ error: typeof code === "string"
1818
+ ? code
1819
+ : error instanceof Error
1820
+ ? error.message
1821
+ : "INVALID_REQUEST",
1822
+ });
1823
+ }
1824
+ }
1825
+ if (request.method !== "POST" ||
1826
+ !url.pathname.startsWith("/actions/"))
1827
+ return respond(response, 404, { error: "NOT_FOUND" });
1828
+ if (gatewayTransportCredential &&
1829
+ !managementCredentialMatches(request.headers.authorization, gatewayTransportCredential))
1830
+ return respond(response, 401, {
1831
+ error: "GATEWAY_TRANSPORT_AUTH_FAILED",
1832
+ });
1833
+ const chunks = [];
1834
+ let bytes = 0;
1835
+ for await (const chunk of request) {
1836
+ const buffer = Buffer.isBuffer(chunk)
1837
+ ? chunk
1838
+ : Buffer.from(chunk);
1839
+ bytes += buffer.byteLength;
1840
+ if (bytes > 1_048_576)
1841
+ throw new TypeError("REQUEST_BODY_TOO_LARGE");
1842
+ chunks.push(buffer);
1843
+ }
1844
+ const body = object(JSON.parse(Buffer.concat(chunks).toString("utf8")), "owner request");
1845
+ const authenticatedRoleRef = string(body.authenticatedRoleRef, "authenticatedRoleRef");
1846
+ const suffix = url.pathname.slice("/actions/".length);
1847
+ const lookup = suffix.endsWith("/result");
1848
+ const operationId = decodeURIComponent(lookup ? suffix.slice(0, -"/result".length) : suffix);
1849
+ const result = lookup
1850
+ ? await graph.lookup(operationId, authenticatedRoleRef, body.input)
1851
+ : await graph.route(operationId, authenticatedRoleRef, body.input, body.fileMaterializationInputs === undefined
1852
+ ? undefined
1853
+ : {
1854
+ fileMaterializationInputs: body.fileMaterializationInputs,
1855
+ });
1856
+ respond(response, 200, result);
1857
+ }
1858
+ catch (error) {
1859
+ const httpStatus = typeof error === "object" && error !== null
1860
+ ? Reflect.get(error, "httpStatus")
1861
+ : undefined;
1862
+ respond(response, typeof httpStatus === "number" ? httpStatus : 400, {
1863
+ error: error instanceof Error ? error.message : "INVALID_REQUEST",
1864
+ });
1865
+ }
1866
+ })();
1867
+ active.add(work);
1868
+ void work.finally(() => active.delete(work));
1869
+ });
1870
+ await new Promise((resolveStart, reject) => {
1871
+ server?.once("error", reject);
1872
+ server?.listen(input.config.port, input.config.host, resolveStart);
1873
+ });
1874
+ accepting = true;
1875
+ lifecycle = "RUNNING";
1876
+ const address = server.address();
1877
+ if (!address || typeof address === "string")
1878
+ throw new Error("platform-host missing TCP address");
1879
+ log("SERVICE_STARTED", { host: input.config.host, port: address.port });
1880
+ return { host: input.config.host, port: address.port };
1881
+ }
1882
+ catch (error) {
1883
+ accepting = false;
1884
+ server?.close();
1885
+ server = undefined;
1886
+ graph?.close();
1887
+ graph = undefined;
1888
+ roleManagementCredential = undefined;
1889
+ taskApplicationCredential = undefined;
1890
+ approvalApplicationCredential = undefined;
1891
+ executionIdentityCredential = undefined;
1892
+ lifecycle = "STOPPED";
1893
+ throw error;
1894
+ }
1895
+ };
1896
+ const stop = async () => {
1897
+ if (lifecycle === "STOPPED")
1898
+ return;
1899
+ lifecycle = "DRAINING";
1900
+ accepting = false;
1901
+ const running = server;
1902
+ server = undefined;
1903
+ if (running)
1904
+ await new Promise((resolveStop, reject) => running.close((error) => (error ? reject(error) : resolveStop())));
1905
+ await Promise.allSettled([...active]);
1906
+ graph?.close();
1907
+ graph = undefined;
1908
+ roleManagementCredential = undefined;
1909
+ taskApplicationCredential = undefined;
1910
+ approvalApplicationCredential = undefined;
1911
+ executionIdentityCredential = undefined;
1912
+ lifecycle = "STOPPED";
1913
+ log("SERVICE_STOPPED");
1914
+ };
1915
+ return Object.freeze({
1916
+ start,
1917
+ stop,
1918
+ status,
1919
+ browserOwnerPorts,
1920
+ executionIdentity,
1921
+ taskDriverPorts,
1922
+ agentIdentityPorts,
1923
+ async restart() {
1924
+ await stop();
1925
+ return start();
1926
+ },
1927
+ });
1928
+ }