@tomflow/proflow-platform-host 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/deployment/adapter.d.ts +10 -10
- package/dist/deployment/descriptor.d.ts +1 -2
- package/dist/deployment/descriptor.js +1 -2
- package/dist/src/browser-permission-context.d.ts +15 -0
- package/dist/src/browser-permission-context.js +31 -0
- package/dist/src/index.js +108 -280
- package/dist/src/reconciliation-coordinator.js +5 -2
- package/package.json +9 -9
- package/proflow.module.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @tomflow/proflow-platform-host
|
|
2
2
|
|
|
3
|
+
## 0.1.25
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Harden task-agnostic Browser Permission trust resolution so ambiguous or unreadable durable Worker bindings fail closed while preserving exact Task-scoped validation.
|
|
8
|
+
|
|
9
|
+
## 0.1.24
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Retry ordinary READY-node reconciliation with bounded task-local backoff when worker wake delivery fails, so Dev completion can reliably hand off to Test without relying only on the global sweep.
|
|
14
|
+
|
|
3
15
|
## 0.1.23
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -6,7 +6,7 @@ export declare const behaviorAdapter: {
|
|
|
6
6
|
ok: true;
|
|
7
7
|
status: "SUCCEEDED";
|
|
8
8
|
moduleRef: "platform-host";
|
|
9
|
-
moduleVersion: "0.1.
|
|
9
|
+
moduleVersion: "0.1.25";
|
|
10
10
|
data: {
|
|
11
11
|
endpoint: string;
|
|
12
12
|
stateRoot: string;
|
|
@@ -24,7 +24,7 @@ export declare const behaviorAdapter: {
|
|
|
24
24
|
readonly ok: true;
|
|
25
25
|
readonly status: "SUCCEEDED";
|
|
26
26
|
readonly moduleRef: "platform-host";
|
|
27
|
-
readonly moduleVersion: "0.1.
|
|
27
|
+
readonly moduleVersion: "0.1.25";
|
|
28
28
|
};
|
|
29
29
|
observedEffects: string[];
|
|
30
30
|
}>;
|
|
@@ -34,7 +34,7 @@ export declare const behaviorAdapter: {
|
|
|
34
34
|
ok: true;
|
|
35
35
|
status: "SUCCEEDED";
|
|
36
36
|
moduleRef: "platform-host";
|
|
37
|
-
moduleVersion: "0.1.
|
|
37
|
+
moduleVersion: "0.1.25";
|
|
38
38
|
data: {
|
|
39
39
|
setupStatus: "READY";
|
|
40
40
|
runtimeStatus: "RUNNING" | "STOPPED";
|
|
@@ -48,7 +48,7 @@ export declare const behaviorAdapter: {
|
|
|
48
48
|
ok: true;
|
|
49
49
|
status: "SUCCEEDED";
|
|
50
50
|
moduleRef: "platform-host";
|
|
51
|
-
moduleVersion: "0.1.
|
|
51
|
+
moduleVersion: "0.1.25";
|
|
52
52
|
data: {
|
|
53
53
|
endpoint: string;
|
|
54
54
|
stateRoot: string;
|
|
@@ -66,7 +66,7 @@ export declare const behaviorAdapter: {
|
|
|
66
66
|
ok: true;
|
|
67
67
|
status: "SUCCEEDED";
|
|
68
68
|
moduleRef: "platform-host";
|
|
69
|
-
moduleVersion: "0.1.
|
|
69
|
+
moduleVersion: "0.1.25";
|
|
70
70
|
data: {
|
|
71
71
|
docs: string;
|
|
72
72
|
};
|
|
@@ -79,7 +79,7 @@ export declare const behaviorAdapter: {
|
|
|
79
79
|
ok: true;
|
|
80
80
|
status: "SUCCEEDED";
|
|
81
81
|
moduleRef: "platform-host";
|
|
82
|
-
moduleVersion: "0.1.
|
|
82
|
+
moduleVersion: "0.1.25";
|
|
83
83
|
data: {
|
|
84
84
|
host: string;
|
|
85
85
|
port: number;
|
|
@@ -90,7 +90,7 @@ export declare const behaviorAdapter: {
|
|
|
90
90
|
result: {
|
|
91
91
|
contract: "deployment.result.v1";
|
|
92
92
|
moduleRef: "platform-host";
|
|
93
|
-
moduleVersion: "0.1.
|
|
93
|
+
moduleVersion: "0.1.25";
|
|
94
94
|
ok: false;
|
|
95
95
|
status: "FAILED";
|
|
96
96
|
error: {
|
|
@@ -107,7 +107,7 @@ export declare const behaviorAdapter: {
|
|
|
107
107
|
readonly ok: true;
|
|
108
108
|
readonly status: "SUCCEEDED";
|
|
109
109
|
readonly moduleRef: "platform-host";
|
|
110
|
-
readonly moduleVersion: "0.1.
|
|
110
|
+
readonly moduleVersion: "0.1.25";
|
|
111
111
|
};
|
|
112
112
|
observedEffects: string[];
|
|
113
113
|
} | {
|
|
@@ -116,11 +116,11 @@ export declare const behaviorAdapter: {
|
|
|
116
116
|
readonly ok: true;
|
|
117
117
|
readonly status: "SUCCEEDED";
|
|
118
118
|
readonly moduleRef: "platform-host";
|
|
119
|
-
readonly moduleVersion: "0.1.
|
|
119
|
+
readonly moduleVersion: "0.1.25";
|
|
120
120
|
} | {
|
|
121
121
|
contract: "deployment.result.v1";
|
|
122
122
|
moduleRef: "platform-host";
|
|
123
|
-
moduleVersion: "0.1.
|
|
123
|
+
moduleVersion: "0.1.25";
|
|
124
124
|
ok: false;
|
|
125
125
|
status: "FAILED";
|
|
126
126
|
error: {
|
|
@@ -3,7 +3,7 @@ export declare const descriptor: {
|
|
|
3
3
|
readonly contractVersion: "1.0.0";
|
|
4
4
|
readonly moduleRef: "platform-host";
|
|
5
5
|
readonly packageName: "@tomflow/proflow-platform-host";
|
|
6
|
-
readonly moduleVersion: "0.1.
|
|
6
|
+
readonly moduleVersion: "0.1.25";
|
|
7
7
|
readonly kind: "service";
|
|
8
8
|
readonly templateVersion: "1.0.0";
|
|
9
9
|
readonly platformCompatibility: ">=1.0.0 <2.0.0";
|
|
@@ -27,7 +27,6 @@ export declare const descriptor: {
|
|
|
27
27
|
}];
|
|
28
28
|
readonly requirements: readonly [{
|
|
29
29
|
readonly kind: "runtime";
|
|
30
|
-
readonly runtime: "node";
|
|
31
30
|
readonly versionRange: ">=24.19.0";
|
|
32
31
|
}];
|
|
33
32
|
readonly configSlots: readonly [];
|
|
@@ -3,7 +3,7 @@ export const descriptor = {
|
|
|
3
3
|
contractVersion: "1.0.0",
|
|
4
4
|
moduleRef: "platform-host",
|
|
5
5
|
packageName: "@tomflow/proflow-platform-host",
|
|
6
|
-
moduleVersion: "0.1.
|
|
6
|
+
moduleVersion: "0.1.25",
|
|
7
7
|
kind: "service",
|
|
8
8
|
templateVersion: "1.0.0",
|
|
9
9
|
platformCompatibility: ">=1.0.0 <2.0.0",
|
|
@@ -34,7 +34,6 @@ export const descriptor = {
|
|
|
34
34
|
requirements: [
|
|
35
35
|
{
|
|
36
36
|
kind: "runtime",
|
|
37
|
-
runtime: "node",
|
|
38
37
|
versionRange: ">=24.19.0",
|
|
39
38
|
},
|
|
40
39
|
],
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type BrowserPermissionTaskBinding = {
|
|
2
|
+
agentPackageRef: string;
|
|
3
|
+
roleRef: string;
|
|
4
|
+
workerRef: string | null;
|
|
5
|
+
conversationLocator: string | null;
|
|
6
|
+
};
|
|
7
|
+
export declare function resolveBrowserPermissionTaskBinding(input: {
|
|
8
|
+
taskId?: string;
|
|
9
|
+
agentPackageRef: string;
|
|
10
|
+
roleRef: string;
|
|
11
|
+
workerRef: string | null;
|
|
12
|
+
conversationLocator: string;
|
|
13
|
+
listTaskIds(): readonly string[];
|
|
14
|
+
getRoleBindings(taskId: string): readonly BrowserPermissionTaskBinding[] | null;
|
|
15
|
+
}): BrowserPermissionTaskBinding | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function resolveBrowserPermissionTaskBinding(input) {
|
|
2
|
+
const matchingRole = (bindings) => unique(bindings?.filter((binding) => binding.roleRef === input.roleRef &&
|
|
3
|
+
binding.agentPackageRef === input.agentPackageRef) ?? []);
|
|
4
|
+
if (input.taskId !== undefined)
|
|
5
|
+
return matchingRole(input.getRoleBindings(input.taskId));
|
|
6
|
+
if (input.workerRef === null)
|
|
7
|
+
return null;
|
|
8
|
+
let match = null;
|
|
9
|
+
for (const taskId of input.listTaskIds()) {
|
|
10
|
+
const bindings = input.getRoleBindings(taskId);
|
|
11
|
+
// An incomplete scan cannot establish an unambiguous durable identity.
|
|
12
|
+
if (bindings === null)
|
|
13
|
+
return null;
|
|
14
|
+
for (const binding of bindings) {
|
|
15
|
+
if (binding.conversationLocator !== input.conversationLocator &&
|
|
16
|
+
!(binding.roleRef === input.roleRef && binding.workerRef === input.workerRef))
|
|
17
|
+
continue;
|
|
18
|
+
if (match !== null ||
|
|
19
|
+
binding.agentPackageRef !== input.agentPackageRef ||
|
|
20
|
+
binding.roleRef !== input.roleRef ||
|
|
21
|
+
binding.workerRef !== input.workerRef ||
|
|
22
|
+
binding.conversationLocator !== input.conversationLocator)
|
|
23
|
+
return null;
|
|
24
|
+
match = binding;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return match;
|
|
28
|
+
}
|
|
29
|
+
function unique(bindings) {
|
|
30
|
+
return bindings.length === 1 ? bindings[0] ?? null : null;
|
|
31
|
+
}
|
package/dist/src/index.js
CHANGED
|
@@ -10,12 +10,11 @@ import { createTaskServices, publicOperationNames, } from "@tomflow/proflow-task
|
|
|
10
10
|
import { SqliteTaskStore } from "@tomflow/proflow-task-store-sqlite";
|
|
11
11
|
import { taskMigrations } from "@tomflow/proflow-task-store-sqlite/migrations";
|
|
12
12
|
import { z } from "zod";
|
|
13
|
+
import { resolveBrowserPermissionTaskBinding } from "./browser-permission-context.js";
|
|
13
14
|
import { classifyBrowserPermission, } from "./browser-permission-policy.js";
|
|
14
15
|
import { createReconciliationCoordinator } from "./reconciliation-coordinator.js";
|
|
15
16
|
import { directToolActionIds, roleAllowsDirectToolOperation, roleOperations, rolePackageRefs, } from "./role-operations.js";
|
|
16
17
|
const loopbackHosts = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
17
|
-
// Business owner calls are Promise-driven. This is only a last-resort transport
|
|
18
|
-
// watchdog for a hung local call, not a normal workflow deadline.
|
|
19
18
|
const OWNER_INVOKE_WATCHDOG_MS = 120_000;
|
|
20
19
|
const systemObserverReasonResultSchema = z
|
|
21
20
|
.object({
|
|
@@ -343,9 +342,7 @@ function createOwnerHttpClient(owner, baseUrl, credential, resolveConnection) {
|
|
|
343
342
|
: {}),
|
|
344
343
|
},
|
|
345
344
|
...(method === "POST"
|
|
346
|
-
? {
|
|
347
|
-
body: JSON.stringify(requestBody),
|
|
348
|
-
}
|
|
345
|
+
? { body: JSON.stringify(requestBody) }
|
|
349
346
|
: {}),
|
|
350
347
|
signal: AbortSignal.timeout(OWNER_INVOKE_WATCHDOG_MS),
|
|
351
348
|
}));
|
|
@@ -372,9 +369,6 @@ function unwrap(result) {
|
|
|
372
369
|
throw new Error(`OWNER_CONTRACT_FAILED:${JSON.stringify(result.error)}`);
|
|
373
370
|
return result.data;
|
|
374
371
|
}
|
|
375
|
-
// Bounded read of the Deployment owner's explicit System Observer projection.
|
|
376
|
-
// Platform Host never reads Deployment's internal state.json or derives health;
|
|
377
|
-
// it consumes only the bounded summary emitted by Deployment status/verify/doctor.
|
|
378
372
|
async function readDeploymentOwnerSummary(stateRoot) {
|
|
379
373
|
try {
|
|
380
374
|
const raw = await readFile(join(stateRoot, "deployment", "observer-summary.json"), "utf8");
|
|
@@ -396,9 +390,8 @@ async function readDeploymentOwnerSummary(stateRoot) {
|
|
|
396
390
|
Number.isNaN(Date.parse(record.observedAt)) ||
|
|
397
391
|
Number.isNaN(Date.parse(record.freshUntil)) ||
|
|
398
392
|
record.selectedModuleCount !== record.totalModuleCount ||
|
|
399
|
-
Date.parse(record.freshUntil) <= Date.now())
|
|
393
|
+
Date.parse(record.freshUntil) <= Date.now())
|
|
400
394
|
return undefined;
|
|
401
|
-
}
|
|
402
395
|
return {
|
|
403
396
|
state: record.state,
|
|
404
397
|
source: record.source,
|
|
@@ -416,17 +409,11 @@ async function readDeploymentOwnerSummary(stateRoot) {
|
|
|
416
409
|
}
|
|
417
410
|
async function constructGraph(config, executionCredential, modelCredential, resolveConnection, resolveLocalToolConnection) {
|
|
418
411
|
const databasePath = join(config.stateRoot, "state", "task.sqlite");
|
|
419
|
-
const migration = applyMigrations({
|
|
420
|
-
databasePath,
|
|
421
|
-
migrations: taskMigrations,
|
|
422
|
-
});
|
|
412
|
+
const migration = applyMigrations({ databasePath, migrations: taskMigrations });
|
|
423
413
|
if (!migration.ok)
|
|
424
414
|
throw new Error(`TASK_MIGRATION_FAILED:${migration.error?.message}`);
|
|
425
415
|
const taskStore = new SqliteTaskStore({ databasePath });
|
|
426
|
-
const task = createTaskServices({
|
|
427
|
-
store: taskStore,
|
|
428
|
-
workspaceRoot: config.workspaceRoot,
|
|
429
|
-
});
|
|
416
|
+
const task = createTaskServices({ store: taskStore, workspaceRoot: config.workspaceRoot });
|
|
430
417
|
const taskFacts = (taskId) => {
|
|
431
418
|
const value = unwrap(task.queries.getTask({ taskId }));
|
|
432
419
|
return {
|
|
@@ -509,9 +496,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
509
496
|
};
|
|
510
497
|
}
|
|
511
498
|
if (view === "collaboration") {
|
|
512
|
-
const pending = await agent.listPendingCollaborationMessages({
|
|
513
|
-
limit: 100,
|
|
514
|
-
});
|
|
499
|
+
const pending = await agent.listPendingCollaborationMessages({ limit: 100 });
|
|
515
500
|
return {
|
|
516
501
|
summary: `pendingMessages=${pending.length}`,
|
|
517
502
|
health: pending.length < 100 ? "HEALTHY" : "DEGRADED",
|
|
@@ -521,22 +506,16 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
521
506
|
const status = object(await model.invoke("getRuntimeStatus", {}), "model status");
|
|
522
507
|
return {
|
|
523
508
|
summary: JSON.stringify(status).slice(0, 2_000),
|
|
524
|
-
health: status.runtime === "READY"
|
|
525
|
-
? "HEALTHY"
|
|
526
|
-
: "DEGRADED",
|
|
509
|
+
health: status.runtime === "READY" ? "HEALTHY" : "DEGRADED",
|
|
527
510
|
};
|
|
528
511
|
}
|
|
529
512
|
if (view === "execution") {
|
|
530
513
|
const readiness = await execution.readiness();
|
|
531
514
|
const signalResult = object(await execution.invoke("listExecutionObserverSignals", { limit: 50 }), "execution observer signals");
|
|
532
|
-
const signals = Array.isArray(signalResult.signals)
|
|
533
|
-
? signalResult.signals
|
|
534
|
-
: [];
|
|
515
|
+
const signals = Array.isArray(signalResult.signals) ? signalResult.signals : [];
|
|
535
516
|
return {
|
|
536
517
|
summary: `execution owner readiness=${readiness.status}; liveness=${readiness.liveness}; pendingObserverSignals=${signals.length}`,
|
|
537
|
-
health: readiness.status === "READY"
|
|
538
|
-
? "HEALTHY"
|
|
539
|
-
: "DEGRADED",
|
|
518
|
+
health: readiness.status === "READY" ? "HEALTHY" : "DEGRADED",
|
|
540
519
|
};
|
|
541
520
|
}
|
|
542
521
|
if (view === "carrier") {
|
|
@@ -549,7 +528,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
549
528
|
}
|
|
550
529
|
if (view === "deployment") {
|
|
551
530
|
const deployment = await readDeploymentOwnerSummary(config.stateRoot);
|
|
552
|
-
if (deployment === undefined)
|
|
531
|
+
if (deployment === undefined)
|
|
553
532
|
return {
|
|
554
533
|
summary: "deployment owner summary unavailable",
|
|
555
534
|
health: "UNKNOWN",
|
|
@@ -558,15 +537,10 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
558
537
|
"Deployment current state is not materialized; no substitute owner readiness is inferred",
|
|
559
538
|
],
|
|
560
539
|
};
|
|
561
|
-
}
|
|
562
540
|
return {
|
|
563
541
|
summary: `deploymentOwnerState=${deployment.state}; source=${deployment.source}; selectedModules=${deployment.selectedModuleCount}; observedModules=${deployment.observedModuleCount}; blockingModules=${deployment.blockingModuleCount}; observedAt=${deployment.observedAt}`,
|
|
564
|
-
health: deployment.state === "READY"
|
|
565
|
-
|
|
566
|
-
: "DEGRADED",
|
|
567
|
-
findings: deployment.state === "READY"
|
|
568
|
-
? []
|
|
569
|
-
: [`Deployment owner reports ${deployment.state}`],
|
|
542
|
+
health: deployment.state === "READY" ? "HEALTHY" : "DEGRADED",
|
|
543
|
+
findings: deployment.state === "READY" ? [] : [`Deployment owner reports ${deployment.state}`],
|
|
570
544
|
};
|
|
571
545
|
}
|
|
572
546
|
if (view === "artifact") {
|
|
@@ -634,31 +608,18 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
634
608
|
"reopenNode",
|
|
635
609
|
"putTaskDocument",
|
|
636
610
|
]);
|
|
637
|
-
// Unified external Task-scoped Action admission. TaskRoleBinding is a Task
|
|
638
|
-
// owner fact, so participant + canonical Worker identity are derived from the
|
|
639
|
-
// Task owner facts, never from an untrusted body roleRef/workerRef. Reads only
|
|
640
|
-
// gate participation (terminal Tasks stay readable); mutations and Execution
|
|
641
|
-
// additionally run the full Agent Worker validation (which rejects terminal
|
|
642
|
-
// Tasks) before acting.
|
|
643
611
|
const admitTaskParticipant = async (taskId, authenticatedRoleRef, suppliedWorkerRef) => {
|
|
644
612
|
const binding = taskFacts(taskId).roleBindings.find((candidate) => candidate.roleRef === authenticatedRoleRef);
|
|
645
613
|
if (!binding?.workerRef)
|
|
646
|
-
throw Object.assign(new Error("TASK_ROLE_BINDING_REQUIRED"), {
|
|
647
|
-
|
|
648
|
-
});
|
|
649
|
-
if (suppliedWorkerRef !== undefined &&
|
|
650
|
-
suppliedWorkerRef !== binding.workerRef)
|
|
651
|
-
throw Object.assign(new Error("TASK_WORKER_BINDING_MISMATCH"), {
|
|
652
|
-
httpStatus: 403,
|
|
653
|
-
});
|
|
614
|
+
throw Object.assign(new Error("TASK_ROLE_BINDING_REQUIRED"), { httpStatus: 403 });
|
|
615
|
+
if (suppliedWorkerRef !== undefined && suppliedWorkerRef !== binding.workerRef)
|
|
616
|
+
throw Object.assign(new Error("TASK_WORKER_BINDING_MISMATCH"), { httpStatus: 403 });
|
|
654
617
|
return binding.workerRef;
|
|
655
618
|
};
|
|
656
619
|
const route = async (operationId, authenticatedRoleRef, rawInput, context) => {
|
|
657
620
|
const role = agent.getRegisteredRole(authenticatedRoleRef);
|
|
658
621
|
if (!roleOperations[role.agentPackageRef]?.has(operationId))
|
|
659
|
-
throw Object.assign(new Error("ROLE_OPERATION_DENIED"), {
|
|
660
|
-
httpStatus: 403,
|
|
661
|
-
});
|
|
622
|
+
throw Object.assign(new Error("ROLE_OPERATION_DENIED"), { httpStatus: 403 });
|
|
662
623
|
const input = object(rawInput, "action input");
|
|
663
624
|
if (operationId === "askPeer")
|
|
664
625
|
return agent.askPeer({ ...input, authenticatedRoleRef });
|
|
@@ -668,27 +629,18 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
668
629
|
const tool = operationId;
|
|
669
630
|
const keys = Object.keys(input).sort();
|
|
670
631
|
if (keys.length !== 2 || keys[0] !== "input" || keys[1] !== "operation")
|
|
671
|
-
throw Object.assign(new Error("DIRECT_TOOL_INPUT_INVALID"), {
|
|
672
|
-
httpStatus: 400,
|
|
673
|
-
});
|
|
632
|
+
throw Object.assign(new Error("DIRECT_TOOL_INPUT_INVALID"), { httpStatus: 400 });
|
|
674
633
|
const toolOperation = string(input.operation, "operation");
|
|
675
634
|
const toolInput = object(input.input, "input");
|
|
676
635
|
assertNoDirectToolPlatformIdentity(toolInput);
|
|
677
636
|
if (!roleAllowsDirectToolOperation(role.agentPackageRef, operationId, toolOperation, toolInput))
|
|
678
|
-
throw Object.assign(new Error("ROLE_TOOL_OPERATION_DENIED"), {
|
|
679
|
-
httpStatus: 403,
|
|
680
|
-
});
|
|
637
|
+
throw Object.assign(new Error("ROLE_TOOL_OPERATION_DENIED"), { httpStatus: 403 });
|
|
681
638
|
const deadlineAt = context?.deadlineAt;
|
|
682
|
-
if (typeof deadlineAt !== "string" ||
|
|
683
|
-
|
|
684
|
-
throw Object.assign(new Error("DIRECT_TOOL_DEADLINE_REQUIRED"), {
|
|
685
|
-
httpStatus: 400,
|
|
686
|
-
});
|
|
639
|
+
if (typeof deadlineAt !== "string" || Number.isNaN(Date.parse(deadlineAt)))
|
|
640
|
+
throw Object.assign(new Error("DIRECT_TOOL_DEADLINE_REQUIRED"), { httpStatus: 400 });
|
|
687
641
|
const connection = await resolveLocalToolConnection?.();
|
|
688
642
|
if (!connection)
|
|
689
|
-
throw Object.assign(new Error("LOCAL_TOOL_BRIDGE_UNAVAILABLE"), {
|
|
690
|
-
httpStatus: 503,
|
|
691
|
-
});
|
|
643
|
+
throw Object.assign(new Error("LOCAL_TOOL_BRIDGE_UNAVAILABLE"), { httpStatus: 503 });
|
|
692
644
|
try {
|
|
693
645
|
return await createLocalToolBridgeHostClient({
|
|
694
646
|
endpoint: connection.endpoint,
|
|
@@ -712,8 +664,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
712
664
|
? 409
|
|
713
665
|
: code === "LOCAL_TOOL_SCOPE_DENIED"
|
|
714
666
|
? 403
|
|
715
|
-
: code === "LOCAL_TOOL_OFFLINE" ||
|
|
716
|
-
code === "LOCAL_TOOL_PROVIDER_UNAVAILABLE"
|
|
667
|
+
: code === "LOCAL_TOOL_OFFLINE" || code === "LOCAL_TOOL_PROVIDER_UNAVAILABLE"
|
|
717
668
|
? 503
|
|
718
669
|
: code === "LOCAL_TOOL_COMMAND_FAILED"
|
|
719
670
|
? 500
|
|
@@ -739,7 +690,9 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
739
690
|
let canonicalTaskInput = input;
|
|
740
691
|
if (context?.fileMaterializationInputs !== undefined) {
|
|
741
692
|
if (operationId !== "putTaskDocument")
|
|
742
|
-
throw Object.assign(new Error("FILE_MATERIALIZATION_UNSUPPORTED_OPERATION"), {
|
|
693
|
+
throw Object.assign(new Error("FILE_MATERIALIZATION_UNSUPPORTED_OPERATION"), {
|
|
694
|
+
httpStatus: 400,
|
|
695
|
+
});
|
|
743
696
|
const taskMutationIdempotencyKey = string(input.idempotencyKey, "idempotencyKey");
|
|
744
697
|
const result = object(await execution.invoke("materializeExternalFiles", {
|
|
745
698
|
contract: "execution.external-file-materialization",
|
|
@@ -747,19 +700,13 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
747
700
|
callerRef: authenticatedRoleRef,
|
|
748
701
|
idempotencyKey: `${taskMutationIdempotencyKey}:carrier-file-materialization`,
|
|
749
702
|
correlationId: taskMutationIdempotencyKey,
|
|
750
|
-
...(typeof input.taskId === "string"
|
|
751
|
-
? { taskId: input.taskId }
|
|
752
|
-
: {}),
|
|
703
|
+
...(typeof input.taskId === "string" ? { taskId: input.taskId } : {}),
|
|
753
704
|
roleRef: authenticatedRoleRef,
|
|
754
|
-
...(canonicalWorkerRef !== undefined
|
|
755
|
-
? { workerRef: canonicalWorkerRef }
|
|
756
|
-
: {}),
|
|
705
|
+
...(canonicalWorkerRef !== undefined ? { workerRef: canonicalWorkerRef } : {}),
|
|
757
706
|
files: context.fileMaterializationInputs,
|
|
758
707
|
}), "carrier file materialization result");
|
|
759
708
|
if (!Array.isArray(result.files) || result.files.length !== 1)
|
|
760
|
-
throw Object.assign(new Error("FILE_MATERIALIZATION_COUNT_INVALID"), {
|
|
761
|
-
httpStatus: 400,
|
|
762
|
-
});
|
|
709
|
+
throw Object.assign(new Error("FILE_MATERIALIZATION_COUNT_INVALID"), { httpStatus: 400 });
|
|
763
710
|
const file = object(result.files[0], "materialized carrier file");
|
|
764
711
|
const content = string(file.content, "materialized carrier file content");
|
|
765
712
|
canonicalTaskInput = { ...input, content };
|
|
@@ -767,8 +714,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
767
714
|
const taskResult = await taskOperation(queryOperations.has(operationId)
|
|
768
715
|
? canonicalTaskInput
|
|
769
716
|
: { ...canonicalTaskInput, actorRef });
|
|
770
|
-
if (taskMutationOperations.has(operationId) &&
|
|
771
|
-
typeof input.taskId === "string")
|
|
717
|
+
if (taskMutationOperations.has(operationId) && typeof input.taskId === "string")
|
|
772
718
|
reconciliationCoordinator?.kick(input.taskId);
|
|
773
719
|
if (operationId === "getTaskDocument")
|
|
774
720
|
return fileBridgeOutputForTaskResult(taskResult);
|
|
@@ -781,10 +727,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
781
727
|
async getWorkerBinding(taskId, roleRef) {
|
|
782
728
|
const binding = taskFacts(taskId).roleBindings.find((candidate) => candidate.roleRef === roleRef);
|
|
783
729
|
return binding?.workerRef
|
|
784
|
-
? {
|
|
785
|
-
workerRef: binding.workerRef,
|
|
786
|
-
conversationLocator: binding.conversationLocator,
|
|
787
|
-
}
|
|
730
|
+
? { workerRef: binding.workerRef, conversationLocator: binding.conversationLocator }
|
|
788
731
|
: null;
|
|
789
732
|
},
|
|
790
733
|
async bindWorker(binding) {
|
|
@@ -811,28 +754,21 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
811
754
|
}),
|
|
812
755
|
agent: Object.freeze({
|
|
813
756
|
async listPendingMessages(limit) {
|
|
814
|
-
const messages = await agent.listPendingCollaborationMessages({
|
|
815
|
-
limit,
|
|
816
|
-
});
|
|
757
|
+
const messages = await agent.listPendingCollaborationMessages({ limit });
|
|
817
758
|
return messages
|
|
818
759
|
.filter((message) => message.lastDeliveryErrorCode !== "UNKNOWN")
|
|
819
760
|
.map((message) => ({ ...message, status: "PENDING" }));
|
|
820
761
|
},
|
|
821
762
|
async getPendingMessage(messageRef) {
|
|
822
|
-
const message = agent.getCollaborationMessage({
|
|
823
|
-
messageId: messageRef,
|
|
824
|
-
});
|
|
763
|
+
const message = agent.getCollaborationMessage({ messageId: messageRef });
|
|
825
764
|
if (message.status !== "PENDING")
|
|
826
765
|
throw new Error("COLLABORATION_MESSAGE_NOT_PENDING");
|
|
827
|
-
// A terminal Task must never re-enter the physical Browser delivery path.
|
|
828
766
|
if (taskIsTerminal(taskFacts(message.taskId).status))
|
|
829
767
|
throw new Error("TASK_TERMINAL");
|
|
830
768
|
return { ...message, status: "PENDING" };
|
|
831
769
|
},
|
|
832
770
|
async reportDeliveryOutcome(input) {
|
|
833
|
-
const message = agent.getCollaborationMessage({
|
|
834
|
-
messageId: input.messageRef,
|
|
835
|
-
});
|
|
771
|
+
const message = agent.getCollaborationMessage({ messageId: input.messageRef });
|
|
836
772
|
if (message.status === "DELIVERED")
|
|
837
773
|
return;
|
|
838
774
|
await agent.reportCollaborationDelivery({
|
|
@@ -859,16 +795,12 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
859
795
|
request.callerRef === "extension:collaboration-carrier";
|
|
860
796
|
if (!internalBrowserCaller)
|
|
861
797
|
agent.getRegisteredRole(request.callerRef);
|
|
862
|
-
if (request.roleRef &&
|
|
863
|
-
!internalBrowserCaller &&
|
|
864
|
-
request.roleRef !== request.callerRef)
|
|
798
|
+
if (request.roleRef && !internalBrowserCaller && request.roleRef !== request.callerRef)
|
|
865
799
|
return false;
|
|
866
|
-
if (request.projectRoot &&
|
|
867
|
-
resolve(request.projectRoot) !== config.workspaceRoot)
|
|
800
|
+
if (request.projectRoot && resolve(request.projectRoot) !== config.workspaceRoot)
|
|
868
801
|
return false;
|
|
869
802
|
const nodeScoped = request.nodeId !== undefined || request.runNo !== undefined;
|
|
870
|
-
if (nodeScoped &&
|
|
871
|
-
(request.nodeId === undefined || request.runNo === undefined))
|
|
803
|
+
if (nodeScoped && (request.nodeId === undefined || request.runNo === undefined))
|
|
872
804
|
return false;
|
|
873
805
|
if (nodeScoped && !request.taskId)
|
|
874
806
|
return false;
|
|
@@ -880,8 +812,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
880
812
|
return false;
|
|
881
813
|
if (request.taskId) {
|
|
882
814
|
const taskFact = taskFacts(request.taskId);
|
|
883
|
-
if (request.capability === "collaboration.deliver" &&
|
|
884
|
-
taskIsTerminal(taskFact.status))
|
|
815
|
+
if (request.capability === "collaboration.deliver" && taskIsTerminal(taskFact.status))
|
|
885
816
|
return false;
|
|
886
817
|
if (request.workerRef && !internalBrowserCaller)
|
|
887
818
|
await agent.validateWorker({
|
|
@@ -890,10 +821,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
890
821
|
workerRef: request.workerRef,
|
|
891
822
|
});
|
|
892
823
|
if (request.nodeId) {
|
|
893
|
-
const nodeContext = unwrap(task.queries.getNodeContext({
|
|
894
|
-
taskId: request.taskId,
|
|
895
|
-
nodeId: request.nodeId,
|
|
896
|
-
}));
|
|
824
|
+
const nodeContext = unwrap(task.queries.getNodeContext({ taskId: request.taskId, nodeId: request.nodeId }));
|
|
897
825
|
if (taskFact.status !== "ACTIVE")
|
|
898
826
|
return false;
|
|
899
827
|
if (taskFact.currentNodeId !== nodeContext.node.nodeId)
|
|
@@ -902,12 +830,10 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
902
830
|
return false;
|
|
903
831
|
if (!browserCapability && nodeContext.node.status !== "IN_PROGRESS")
|
|
904
832
|
return false;
|
|
905
|
-
const nodeBinding = taskFact.roleBindings.find((candidate) => candidate.agentPackageRef ===
|
|
906
|
-
nodeContext.node.requiredAgentPackageRef);
|
|
833
|
+
const nodeBinding = taskFact.roleBindings.find((candidate) => candidate.agentPackageRef === nodeContext.node.requiredAgentPackageRef);
|
|
907
834
|
if (!nodeBinding?.workerRef)
|
|
908
835
|
return false;
|
|
909
|
-
const scopedRoleRef = request.roleRef ??
|
|
910
|
-
(internalBrowserCaller ? undefined : request.callerRef);
|
|
836
|
+
const scopedRoleRef = request.roleRef ?? (internalBrowserCaller ? undefined : request.callerRef);
|
|
911
837
|
if (scopedRoleRef !== nodeBinding.roleRef)
|
|
912
838
|
return false;
|
|
913
839
|
if (request.workerRef !== nodeBinding.workerRef)
|
|
@@ -999,19 +925,14 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
999
925
|
},
|
|
1000
926
|
});
|
|
1001
927
|
const roleForPackage = (agentPackageRef) => {
|
|
1002
|
-
const role = agent
|
|
1003
|
-
.listRegisteredRoles()
|
|
1004
|
-
.find((candidate) => candidate.agentPackageRef === agentPackageRef);
|
|
928
|
+
const role = agent.listRegisteredRoles().find((candidate) => candidate.agentPackageRef === agentPackageRef);
|
|
1005
929
|
if (!role)
|
|
1006
930
|
throw new Error("ROLE_NOT_FOUND");
|
|
1007
931
|
return role;
|
|
1008
932
|
};
|
|
1009
933
|
const browserPermissionRole = (roleRef) => {
|
|
1010
|
-
const role = agent
|
|
1011
|
-
|
|
1012
|
-
.find((candidate) => candidate.roleRef === roleRef);
|
|
1013
|
-
if (!role ||
|
|
1014
|
-
!rolePackageRefs.includes(role.agentPackageRef))
|
|
934
|
+
const role = agent.listRegisteredRoles().find((candidate) => candidate.roleRef === roleRef);
|
|
935
|
+
if (!role || !rolePackageRefs.includes(role.agentPackageRef))
|
|
1015
936
|
return null;
|
|
1016
937
|
return { ...role, agentPackageRef: role.agentPackageRef };
|
|
1017
938
|
};
|
|
@@ -1039,9 +960,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1039
960
|
value.moduleRef !== "agent-gateway")
|
|
1040
961
|
return null;
|
|
1041
962
|
const facts = object(value.facts, "agent gateway facts");
|
|
1042
|
-
return typeof facts.publicBaseUrl === "string"
|
|
1043
|
-
? facts.publicBaseUrl
|
|
1044
|
-
: null;
|
|
963
|
+
return typeof facts.publicBaseUrl === "string" ? facts.publicBaseUrl : null;
|
|
1045
964
|
}
|
|
1046
965
|
catch {
|
|
1047
966
|
return null;
|
|
@@ -1068,17 +987,12 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1068
987
|
issues.push(`ROLE_CARRIER_URL_MISMATCH:${role.roleRef}`);
|
|
1069
988
|
const expectedPackageVersion = Reflect.get(value, "expectedPackageVersion");
|
|
1070
989
|
if (expectedPackageVersion !== undefined &&
|
|
1071
|
-
(typeof expectedPackageVersion !== "string" ||
|
|
1072
|
-
expectedPackageVersion.length === 0))
|
|
990
|
+
(typeof expectedPackageVersion !== "string" || expectedPackageVersion.length === 0))
|
|
1073
991
|
throw new TypeError("expectedPackageVersion must be a non-empty string");
|
|
1074
992
|
if (typeof expectedPackageVersion === "string" &&
|
|
1075
993
|
role.registeredPackageVersion !== expectedPackageVersion)
|
|
1076
994
|
issues.push(`ROLE_PACKAGE_VERSION_DRIFT:${role.roleRef}:${role.registeredPackageVersion}:${expectedPackageVersion}`);
|
|
1077
|
-
return {
|
|
1078
|
-
status: issues.length === 0 ? "PASS" : "FAIL",
|
|
1079
|
-
role,
|
|
1080
|
-
issues,
|
|
1081
|
-
};
|
|
995
|
+
return { status: issues.length === 0 ? "PASS" : "FAIL", role, issues };
|
|
1082
996
|
}
|
|
1083
997
|
if (operation === "role.delete") {
|
|
1084
998
|
await agent.deleteRole(role.roleRef);
|
|
@@ -1122,27 +1036,16 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1122
1036
|
if (!persisted?.workerRef || !persisted.conversationLocator)
|
|
1123
1037
|
throw new Error("WORKER_CREATE_BINDING_NOT_PERSISTED");
|
|
1124
1038
|
};
|
|
1125
|
-
// Dispatch all three fixed Workers concurrently. When `waitFor` names a
|
|
1126
|
-
// subset of roleRefs (the J1 Product path), only those results gate the
|
|
1127
|
-
// return; the remaining Worker creation is a durable, idempotent Execution
|
|
1128
|
-
// effect whose completion backend reconciliation recovers from the
|
|
1129
|
-
// durable Task binding facts — never a bare in-memory promise.
|
|
1130
1039
|
const waitFor = new Set(options?.waitFor ?? []);
|
|
1131
|
-
const shouldWait = (agentPackageRef) => waitFor.size === 0 ||
|
|
1132
|
-
waitFor.has(roleForPackage(agentPackageRef).roleRef);
|
|
1040
|
+
const shouldWait = (agentPackageRef) => waitFor.size === 0 || waitFor.has(roleForPackage(agentPackageRef).roleRef);
|
|
1133
1041
|
const pending = rolePackageRefs.map((agentPackageRef) => ({
|
|
1134
1042
|
agentPackageRef,
|
|
1135
1043
|
promise: provision(agentPackageRef),
|
|
1136
1044
|
}));
|
|
1137
1045
|
const awaited = pending.filter((entry) => shouldWait(entry.agentPackageRef));
|
|
1138
1046
|
const deferred = pending.filter((entry) => !shouldWait(entry.agentPackageRef));
|
|
1139
|
-
for (const entry of deferred)
|
|
1140
|
-
entry.promise.catch(() => {
|
|
1141
|
-
// Deferred Worker creation failure is recoverable: the durable
|
|
1142
|
-
// binding stays unset, so a later backend reconciliation pass re-provisions
|
|
1143
|
-
// only the missing role.
|
|
1144
|
-
});
|
|
1145
|
-
}
|
|
1047
|
+
for (const entry of deferred)
|
|
1048
|
+
entry.promise.catch(() => { });
|
|
1146
1049
|
const results = await Promise.allSettled(awaited.map((entry) => entry.promise));
|
|
1147
1050
|
const failure = results.find((result) => result.status === "rejected");
|
|
1148
1051
|
if (failure)
|
|
@@ -1173,22 +1076,14 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1173
1076
|
fingerprint: `wake:${input.taskId}:${input.nodeId}:${input.runNo}:${input.trigger}:${underlyingRef}`,
|
|
1174
1077
|
},
|
|
1175
1078
|
}), "task reconciliation wake execution");
|
|
1176
|
-
if (wakeExecution.status !== "SUCCEEDED" ||
|
|
1177
|
-
wakeExecution.sideEffectState !== "APPLIED")
|
|
1079
|
+
if (wakeExecution.status !== "SUCCEEDED" || wakeExecution.sideEffectState !== "APPLIED")
|
|
1178
1080
|
throw new Error(`TASK_WAKE_NOT_CONFIRMED:${String(wakeExecution.status)}:${String(wakeExecution.sideEffectState)}`);
|
|
1179
1081
|
return wakeExecution;
|
|
1180
1082
|
};
|
|
1181
1083
|
reconciliationCoordinator = createReconciliationCoordinator({
|
|
1182
1084
|
async listTaskPage(input) {
|
|
1183
1085
|
return taskStore.listReconciliationTaskIds({
|
|
1184
|
-
statuses: [
|
|
1185
|
-
"PENDING",
|
|
1186
|
-
"READY",
|
|
1187
|
-
"ACTIVE",
|
|
1188
|
-
"WAITING",
|
|
1189
|
-
"FAILED",
|
|
1190
|
-
"PAUSED",
|
|
1191
|
-
],
|
|
1086
|
+
statuses: ["PENDING", "READY", "ACTIVE", "WAITING", "FAILED", "PAUSED"],
|
|
1192
1087
|
...input,
|
|
1193
1088
|
});
|
|
1194
1089
|
},
|
|
@@ -1200,9 +1095,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1200
1095
|
return Array.isArray(batch.signals) ? batch.signals : [];
|
|
1201
1096
|
},
|
|
1202
1097
|
async acknowledgeExecutionSignal(signalRef) {
|
|
1203
|
-
await execution.invoke("acknowledgeExecutionObserverSignal", {
|
|
1204
|
-
signalRef,
|
|
1205
|
-
});
|
|
1098
|
+
await execution.invoke("acknowledgeExecutionObserverSignal", { signalRef });
|
|
1206
1099
|
},
|
|
1207
1100
|
async ensureWorkers(taskId) {
|
|
1208
1101
|
await ensureTaskWorkers(taskId);
|
|
@@ -1216,9 +1109,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1216
1109
|
if (operation === "task.create") {
|
|
1217
1110
|
const idempotencyKey = string(value.idempotencyKey, "idempotencyKey");
|
|
1218
1111
|
const created = unwrap(task.commands.createTask({
|
|
1219
|
-
...(typeof value.taskId === "string"
|
|
1220
|
-
? { taskId: value.taskId }
|
|
1221
|
-
: {}),
|
|
1112
|
+
...(typeof value.taskId === "string" ? { taskId: value.taskId } : {}),
|
|
1222
1113
|
title: string(value.title, "title"),
|
|
1223
1114
|
objective: string(value.objective, "objective"),
|
|
1224
1115
|
plan: object(value.plan, "plan"),
|
|
@@ -1237,9 +1128,6 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1237
1128
|
actorRef: "extension:human",
|
|
1238
1129
|
idempotencyKey,
|
|
1239
1130
|
}));
|
|
1240
|
-
// J1: return once the Product Worker is durably bound; Dev/Test
|
|
1241
|
-
// continue as recoverable durable effects without blocking Product
|
|
1242
|
-
// requirement discussion.
|
|
1243
1131
|
const provisioned = await ensureTaskWorkers(created.taskId, {
|
|
1244
1132
|
waitFor: [roleForPackage("@tomflow/proflow-agent-product").roleRef],
|
|
1245
1133
|
});
|
|
@@ -1248,9 +1136,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1248
1136
|
}
|
|
1249
1137
|
if (operation === "task.list")
|
|
1250
1138
|
return unwrap(task.queries.listTasks({
|
|
1251
|
-
...(Array.isArray(value.statuses)
|
|
1252
|
-
? { statuses: value.statuses }
|
|
1253
|
-
: {}),
|
|
1139
|
+
...(Array.isArray(value.statuses) ? { statuses: value.statuses } : {}),
|
|
1254
1140
|
}));
|
|
1255
1141
|
if (operation === "task.get")
|
|
1256
1142
|
return unwrap(task.queries.getTask({ taskId: string(value.taskId, "taskId") }));
|
|
@@ -1268,9 +1154,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1268
1154
|
if (operation === "message.acknowledge")
|
|
1269
1155
|
return unwrap(task.commands.acknowledgeMessage({
|
|
1270
1156
|
messageId: string(value.messageId, "messageId"),
|
|
1271
|
-
...(typeof value.resolution === "string"
|
|
1272
|
-
? { resolution: value.resolution }
|
|
1273
|
-
: {}),
|
|
1157
|
+
...(typeof value.resolution === "string" ? { resolution: value.resolution } : {}),
|
|
1274
1158
|
actorRef: "extension:human",
|
|
1275
1159
|
idempotencyKey: string(value.idempotencyKey, "idempotencyKey"),
|
|
1276
1160
|
}));
|
|
@@ -1383,26 +1267,31 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1383
1267
|
}
|
|
1384
1268
|
if (operation === "browser.permission.classify") {
|
|
1385
1269
|
const roleRef = string(value.roleRef, "roleRef");
|
|
1386
|
-
|
|
1270
|
+
if (value.taskId !== undefined && typeof value.taskId !== "string")
|
|
1271
|
+
return { decision: "HUMAN_REQUIRED", reason: "CONTEXT_MISMATCH" };
|
|
1272
|
+
const taskId = typeof value.taskId === "string" ? value.taskId : undefined;
|
|
1387
1273
|
const operationId = string(value.operationId, "operationId");
|
|
1388
1274
|
const conversationLocator = string(value.conversationLocator, "conversationLocator");
|
|
1389
1275
|
const workerRef = typeof value.workerRef === "string" ? value.workerRef : null;
|
|
1390
1276
|
const role = browserPermissionRole(roleRef);
|
|
1391
1277
|
if (!role)
|
|
1392
|
-
return {
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1278
|
+
return { decision: "HUMAN_REQUIRED", reason: "ROLE_VALIDATION_MISMATCH" };
|
|
1279
|
+
const taskBinding = resolveBrowserPermissionTaskBinding({
|
|
1280
|
+
...(taskId === undefined ? {} : { taskId }),
|
|
1281
|
+
agentPackageRef: role.agentPackageRef,
|
|
1282
|
+
roleRef,
|
|
1283
|
+
workerRef,
|
|
1284
|
+
conversationLocator,
|
|
1285
|
+
listTaskIds: () => unwrap(task.queries.listTasks({})).tasks.map((summary) => summary.taskId),
|
|
1286
|
+
getRoleBindings: (candidateTaskId) => {
|
|
1287
|
+
try {
|
|
1288
|
+
return unwrap(task.queries.getTask({ taskId: candidateTaskId })).roleBindings;
|
|
1289
|
+
}
|
|
1290
|
+
catch {
|
|
1291
|
+
return null;
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
});
|
|
1406
1295
|
const [validation, currentGatewayUrl] = await Promise.all([
|
|
1407
1296
|
roleCarrierValidation(role.agentPackageRef),
|
|
1408
1297
|
currentAgentGatewayUrl(),
|
|
@@ -1445,22 +1334,14 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1445
1334
|
}
|
|
1446
1335
|
if (operation === "collaboration.reportDelivery") {
|
|
1447
1336
|
const outcome = string(value.outcome, "outcome");
|
|
1448
|
-
if (outcome !== "DELIVERED" &&
|
|
1449
|
-
outcome !== "FAILED" &&
|
|
1450
|
-
outcome !== "UNKNOWN")
|
|
1337
|
+
if (outcome !== "DELIVERED" && outcome !== "FAILED" && outcome !== "UNKNOWN")
|
|
1451
1338
|
throw new Error("COLLABORATION_DELIVERY_OUTCOME_INVALID");
|
|
1452
1339
|
await browserOwnerPorts.agent.reportDeliveryOutcome({
|
|
1453
1340
|
messageRef: string(value.messageRef, "messageRef"),
|
|
1454
1341
|
outcome,
|
|
1455
|
-
...(typeof value.evidenceRef === "string"
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
...(typeof value.executionRef === "string"
|
|
1459
|
-
? { executionRef: value.executionRef }
|
|
1460
|
-
: {}),
|
|
1461
|
-
...(typeof value.errorCode === "string"
|
|
1462
|
-
? { errorCode: value.errorCode }
|
|
1463
|
-
: {}),
|
|
1342
|
+
...(typeof value.evidenceRef === "string" ? { evidenceRef: value.evidenceRef } : {}),
|
|
1343
|
+
...(typeof value.executionRef === "string" ? { executionRef: value.executionRef } : {}),
|
|
1344
|
+
...(typeof value.errorCode === "string" ? { errorCode: value.errorCode } : {}),
|
|
1464
1345
|
});
|
|
1465
1346
|
return { reported: true };
|
|
1466
1347
|
}
|
|
@@ -1484,9 +1365,8 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1484
1365
|
const assessmentRef = string(value.assessmentRef, "assessmentRef");
|
|
1485
1366
|
const observerPayload = object(value.payload, "system observer reason payload");
|
|
1486
1367
|
const payloadAssessmentRef = string(observerPayload.assessmentRef, "payload.assessmentRef");
|
|
1487
|
-
if (payloadAssessmentRef !== assessmentRef)
|
|
1368
|
+
if (payloadAssessmentRef !== assessmentRef)
|
|
1488
1369
|
throw new Error("SYSTEM_OBSERVER_ASSESSMENT_REF_MISMATCH");
|
|
1489
|
-
}
|
|
1490
1370
|
const { assessmentRef: _assessmentRef, kind, ...modelPayload } = observerPayload;
|
|
1491
1371
|
const response = object(await model.invoke("infer", {
|
|
1492
1372
|
contractVersion: "1.0.0",
|
|
@@ -1505,8 +1385,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1505
1385
|
: {};
|
|
1506
1386
|
const code = error.code === "CONTEXT_TOO_LARGE"
|
|
1507
1387
|
? "CONTEXT_TOO_LARGE"
|
|
1508
|
-
: error.code === "MODEL_UNAVAILABLE" ||
|
|
1509
|
-
error.code === "CAPABILITY_UNSUPPORTED"
|
|
1388
|
+
: error.code === "MODEL_UNAVAILABLE" || error.code === "CAPABILITY_UNSUPPORTED"
|
|
1510
1389
|
? "REASON_UNAVAILABLE"
|
|
1511
1390
|
: "REASON_FAILED";
|
|
1512
1391
|
return { ok: false, errorCode: code };
|
|
@@ -1528,9 +1407,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1528
1407
|
approvalApplication,
|
|
1529
1408
|
observerApplication,
|
|
1530
1409
|
async lookup(_operationId, _authenticatedRoleRef, _input) {
|
|
1531
|
-
throw Object.assign(new Error("ACTION_RESULT_LOOKUP_UNSUPPORTED"), {
|
|
1532
|
-
httpStatus: 409,
|
|
1533
|
-
});
|
|
1410
|
+
throw Object.assign(new Error("ACTION_RESULT_LOOKUP_UNSUPPORTED"), { httpStatus: 409 });
|
|
1534
1411
|
},
|
|
1535
1412
|
async readiness() {
|
|
1536
1413
|
const diagnostics = taskStore.diagnostics();
|
|
@@ -1567,10 +1444,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1567
1444
|
}
|
|
1568
1445
|
async function ensureRoleManagementCredential(stateRoot) {
|
|
1569
1446
|
const path = join(stateRoot, "agent", "secrets", "role-management.token");
|
|
1570
|
-
await mkdir(join(stateRoot, "agent", "secrets"), {
|
|
1571
|
-
recursive: true,
|
|
1572
|
-
mode: 0o700,
|
|
1573
|
-
});
|
|
1447
|
+
await mkdir(join(stateRoot, "agent", "secrets"), { recursive: true, mode: 0o700 });
|
|
1574
1448
|
if (!existsSync(path)) {
|
|
1575
1449
|
const generated = randomBytes(32).toString("base64url");
|
|
1576
1450
|
try {
|
|
@@ -1700,17 +1574,11 @@ export function createPlatformHost(input) {
|
|
|
1700
1574
|
const stoppedDependencies = () => ({
|
|
1701
1575
|
task: { owner: "task", status: "NOT_READY", liveness: "DOWN" },
|
|
1702
1576
|
agent: { owner: "agent", status: "NOT_READY", liveness: "DOWN" },
|
|
1703
|
-
execution: {
|
|
1704
|
-
owner: "execution",
|
|
1705
|
-
status: "NOT_READY",
|
|
1706
|
-
liveness: "DOWN",
|
|
1707
|
-
},
|
|
1577
|
+
execution: { owner: "execution", status: "NOT_READY", liveness: "DOWN" },
|
|
1708
1578
|
model: { owner: "model", status: "NOT_READY", liveness: "DOWN" },
|
|
1709
1579
|
});
|
|
1710
1580
|
const status = async () => {
|
|
1711
|
-
const dependencies = graph
|
|
1712
|
-
? await graph.readiness()
|
|
1713
|
-
: stoppedDependencies();
|
|
1581
|
+
const dependencies = graph ? await graph.readiness() : stoppedDependencies();
|
|
1714
1582
|
return {
|
|
1715
1583
|
process: lifecycle,
|
|
1716
1584
|
liveness: lifecycle === "STOPPED" ? "DOWN" : "UP",
|
|
@@ -1808,8 +1676,7 @@ export function createPlatformHost(input) {
|
|
|
1808
1676
|
modelTransportCredential = input.config.modelTransportCredentialFile
|
|
1809
1677
|
? await readModelTransportCredential(input.config.modelTransportCredentialFile)
|
|
1810
1678
|
: undefined;
|
|
1811
|
-
executionTransportCredential = input.config
|
|
1812
|
-
.executionTransportCredentialFile
|
|
1679
|
+
executionTransportCredential = input.config.executionTransportCredentialFile
|
|
1813
1680
|
? await readExecutionTransportCredential(input.config.executionTransportCredentialFile)
|
|
1814
1681
|
: input.executionCredential;
|
|
1815
1682
|
log("DEPENDENCY_INITIALIZATION_STARTED", {
|
|
@@ -1834,18 +1701,14 @@ export function createPlatformHost(input) {
|
|
|
1834
1701
|
url.pathname === "/internal/execution/identity/ready") {
|
|
1835
1702
|
if (!executionIdentityCredential ||
|
|
1836
1703
|
!managementCredentialMatches(request.headers.authorization, executionIdentityCredential))
|
|
1837
|
-
return respond(response, 401, {
|
|
1838
|
-
error: "EXECUTION_IDENTITY_AUTH_FAILED",
|
|
1839
|
-
});
|
|
1704
|
+
return respond(response, 401, { error: "EXECUTION_IDENTITY_AUTH_FAILED" });
|
|
1840
1705
|
return respond(response, 200, { ready: true });
|
|
1841
1706
|
}
|
|
1842
1707
|
if (request.method === "POST" &&
|
|
1843
1708
|
url.pathname === "/internal/execution/authorize") {
|
|
1844
1709
|
if (!executionIdentityCredential ||
|
|
1845
1710
|
!managementCredentialMatches(request.headers.authorization, executionIdentityCredential))
|
|
1846
|
-
return respond(response, 401, {
|
|
1847
|
-
error: "EXECUTION_IDENTITY_AUTH_FAILED",
|
|
1848
|
-
});
|
|
1711
|
+
return respond(response, 401, { error: "EXECUTION_IDENTITY_AUTH_FAILED" });
|
|
1849
1712
|
const chunks = [];
|
|
1850
1713
|
for await (const chunk of request)
|
|
1851
1714
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
@@ -1858,15 +1721,11 @@ export function createPlatformHost(input) {
|
|
|
1858
1721
|
url.pathname === "/application/approval") {
|
|
1859
1722
|
if (!approvalApplicationCredential ||
|
|
1860
1723
|
!managementCredentialMatches(request.headers.authorization, approvalApplicationCredential))
|
|
1861
|
-
return respond(response, 401, {
|
|
1862
|
-
error: "APPROVAL_APPLICATION_AUTH_FAILED",
|
|
1863
|
-
});
|
|
1724
|
+
return respond(response, 401, { error: "APPROVAL_APPLICATION_AUTH_FAILED" });
|
|
1864
1725
|
const chunks = [];
|
|
1865
1726
|
let bytes = 0;
|
|
1866
1727
|
for await (const chunk of request) {
|
|
1867
|
-
const buffer = Buffer.isBuffer(chunk)
|
|
1868
|
-
? chunk
|
|
1869
|
-
: Buffer.from(chunk);
|
|
1728
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1870
1729
|
bytes += buffer.byteLength;
|
|
1871
1730
|
if (bytes > 262_144)
|
|
1872
1731
|
throw new TypeError("REQUEST_BODY_TOO_LARGE");
|
|
@@ -1883,19 +1742,14 @@ export function createPlatformHost(input) {
|
|
|
1883
1742
|
});
|
|
1884
1743
|
}
|
|
1885
1744
|
}
|
|
1886
|
-
if (request.method === "POST" &&
|
|
1887
|
-
url.pathname === "/application/log") {
|
|
1745
|
+
if (request.method === "POST" && url.pathname === "/application/log") {
|
|
1888
1746
|
if (!taskApplicationCredential ||
|
|
1889
1747
|
!managementCredentialMatches(request.headers.authorization, taskApplicationCredential))
|
|
1890
|
-
return respond(response, 401, {
|
|
1891
|
-
error: "BROWSER_LOG_AUTH_FAILED",
|
|
1892
|
-
});
|
|
1748
|
+
return respond(response, 401, { error: "BROWSER_LOG_AUTH_FAILED" });
|
|
1893
1749
|
const chunks = [];
|
|
1894
1750
|
let bytes = 0;
|
|
1895
1751
|
for await (const chunk of request) {
|
|
1896
|
-
const buffer = Buffer.isBuffer(chunk)
|
|
1897
|
-
? chunk
|
|
1898
|
-
: Buffer.from(chunk);
|
|
1752
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1899
1753
|
bytes += buffer.byteLength;
|
|
1900
1754
|
if (bytes > 32_768)
|
|
1901
1755
|
throw new TypeError("REQUEST_BODY_TOO_LARGE");
|
|
@@ -1910,25 +1764,18 @@ export function createPlatformHost(input) {
|
|
|
1910
1764
|
}
|
|
1911
1765
|
catch (error) {
|
|
1912
1766
|
return respond(response, 400, {
|
|
1913
|
-
error: error instanceof Error
|
|
1914
|
-
? error.message
|
|
1915
|
-
: "INVALID_LOG_ENTRY",
|
|
1767
|
+
error: error instanceof Error ? error.message : "INVALID_LOG_ENTRY",
|
|
1916
1768
|
});
|
|
1917
1769
|
}
|
|
1918
1770
|
}
|
|
1919
|
-
if (request.method === "POST" &&
|
|
1920
|
-
url.pathname === "/application/observer") {
|
|
1771
|
+
if (request.method === "POST" && url.pathname === "/application/observer") {
|
|
1921
1772
|
if (!taskApplicationCredential ||
|
|
1922
1773
|
!managementCredentialMatches(request.headers.authorization, taskApplicationCredential))
|
|
1923
|
-
return respond(response, 401, {
|
|
1924
|
-
error: "OBSERVER_APPLICATION_AUTH_FAILED",
|
|
1925
|
-
});
|
|
1774
|
+
return respond(response, 401, { error: "OBSERVER_APPLICATION_AUTH_FAILED" });
|
|
1926
1775
|
const chunks = [];
|
|
1927
1776
|
let bytes = 0;
|
|
1928
1777
|
for await (const chunk of request) {
|
|
1929
|
-
const buffer = Buffer.isBuffer(chunk)
|
|
1930
|
-
? chunk
|
|
1931
|
-
: Buffer.from(chunk);
|
|
1778
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1932
1779
|
bytes += buffer.byteLength;
|
|
1933
1780
|
if (bytes > 262_144)
|
|
1934
1781
|
throw new TypeError("REQUEST_BODY_TOO_LARGE");
|
|
@@ -1945,19 +1792,14 @@ export function createPlatformHost(input) {
|
|
|
1945
1792
|
});
|
|
1946
1793
|
}
|
|
1947
1794
|
}
|
|
1948
|
-
if (request.method === "POST" &&
|
|
1949
|
-
url.pathname === "/application/task") {
|
|
1795
|
+
if (request.method === "POST" && url.pathname === "/application/task") {
|
|
1950
1796
|
if (!taskApplicationCredential ||
|
|
1951
1797
|
!managementCredentialMatches(request.headers.authorization, taskApplicationCredential))
|
|
1952
|
-
return respond(response, 401, {
|
|
1953
|
-
error: "TASK_APPLICATION_AUTH_FAILED",
|
|
1954
|
-
});
|
|
1798
|
+
return respond(response, 401, { error: "TASK_APPLICATION_AUTH_FAILED" });
|
|
1955
1799
|
const chunks = [];
|
|
1956
1800
|
let bytes = 0;
|
|
1957
1801
|
for await (const chunk of request) {
|
|
1958
|
-
const buffer = Buffer.isBuffer(chunk)
|
|
1959
|
-
? chunk
|
|
1960
|
-
: Buffer.from(chunk);
|
|
1802
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1961
1803
|
bytes += buffer.byteLength;
|
|
1962
1804
|
if (bytes > 262_144)
|
|
1963
1805
|
throw new TypeError("REQUEST_BODY_TOO_LARGE");
|
|
@@ -1974,19 +1816,14 @@ export function createPlatformHost(input) {
|
|
|
1974
1816
|
});
|
|
1975
1817
|
}
|
|
1976
1818
|
}
|
|
1977
|
-
if (request.method === "POST" &&
|
|
1978
|
-
url.pathname === "/management/agent") {
|
|
1819
|
+
if (request.method === "POST" && url.pathname === "/management/agent") {
|
|
1979
1820
|
if (!roleManagementCredential ||
|
|
1980
1821
|
!managementCredentialMatches(request.headers.authorization, roleManagementCredential))
|
|
1981
|
-
return respond(response, 401, {
|
|
1982
|
-
error: "MANAGEMENT_AUTH_FAILED",
|
|
1983
|
-
});
|
|
1822
|
+
return respond(response, 401, { error: "MANAGEMENT_AUTH_FAILED" });
|
|
1984
1823
|
const chunks = [];
|
|
1985
1824
|
let bytes = 0;
|
|
1986
1825
|
for await (const chunk of request) {
|
|
1987
|
-
const buffer = Buffer.isBuffer(chunk)
|
|
1988
|
-
? chunk
|
|
1989
|
-
: Buffer.from(chunk);
|
|
1826
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1990
1827
|
bytes += buffer.byteLength;
|
|
1991
1828
|
if (bytes > 65_536)
|
|
1992
1829
|
throw new TypeError("REQUEST_BODY_TOO_LARGE");
|
|
@@ -2011,20 +1848,15 @@ export function createPlatformHost(input) {
|
|
|
2011
1848
|
});
|
|
2012
1849
|
}
|
|
2013
1850
|
}
|
|
2014
|
-
if (request.method !== "POST" ||
|
|
2015
|
-
!url.pathname.startsWith("/actions/"))
|
|
1851
|
+
if (request.method !== "POST" || !url.pathname.startsWith("/actions/"))
|
|
2016
1852
|
return respond(response, 404, { error: "NOT_FOUND" });
|
|
2017
1853
|
if (gatewayTransportCredential &&
|
|
2018
1854
|
!managementCredentialMatches(request.headers.authorization, gatewayTransportCredential))
|
|
2019
|
-
return respond(response, 401, {
|
|
2020
|
-
error: "GATEWAY_TRANSPORT_AUTH_FAILED",
|
|
2021
|
-
});
|
|
1855
|
+
return respond(response, 401, { error: "GATEWAY_TRANSPORT_AUTH_FAILED" });
|
|
2022
1856
|
const chunks = [];
|
|
2023
1857
|
let bytes = 0;
|
|
2024
1858
|
for await (const chunk of request) {
|
|
2025
|
-
const buffer = Buffer.isBuffer(chunk)
|
|
2026
|
-
? chunk
|
|
2027
|
-
: Buffer.from(chunk);
|
|
1859
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2028
1860
|
bytes += buffer.byteLength;
|
|
2029
1861
|
if (bytes > 1_048_576)
|
|
2030
1862
|
throw new TypeError("REQUEST_BODY_TOO_LARGE");
|
|
@@ -2040,12 +1872,8 @@ export function createPlatformHost(input) {
|
|
|
2040
1872
|
: await graph.route(operationId, authenticatedRoleRef, body.input, {
|
|
2041
1873
|
...(body.fileMaterializationInputs === undefined
|
|
2042
1874
|
? {}
|
|
2043
|
-
: {
|
|
2044
|
-
|
|
2045
|
-
}),
|
|
2046
|
-
...(typeof body.deadlineAt === "string"
|
|
2047
|
-
? { deadlineAt: body.deadlineAt }
|
|
2048
|
-
: {}),
|
|
1875
|
+
: { fileMaterializationInputs: body.fileMaterializationInputs }),
|
|
1876
|
+
...(typeof body.deadlineAt === "string" ? { deadlineAt: body.deadlineAt } : {}),
|
|
2049
1877
|
});
|
|
2050
1878
|
respond(response, 200, result);
|
|
2051
1879
|
}
|
|
@@ -90,7 +90,9 @@ export function createReconciliationCoordinator(options) {
|
|
|
90
90
|
};
|
|
91
91
|
const canAttempt = (taskId) => (failures.get(taskId)?.nextAt ?? 0) <= now();
|
|
92
92
|
const schedulePendingRetry = (taskId) => {
|
|
93
|
-
if (stopped ||
|
|
93
|
+
if (stopped ||
|
|
94
|
+
(!pendingSignals.has(taskId) && !failures.has(taskId)) ||
|
|
95
|
+
retryTimers.has(taskId))
|
|
94
96
|
return;
|
|
95
97
|
const delay = Math.max(1, (failures.get(taskId)?.nextAt ?? now()) - now());
|
|
96
98
|
const retry = setTimeout(() => {
|
|
@@ -195,7 +197,8 @@ export function createReconciliationCoordinator(options) {
|
|
|
195
197
|
}
|
|
196
198
|
})().finally(() => {
|
|
197
199
|
taskInFlight.delete(taskId);
|
|
198
|
-
if (!stopped &&
|
|
200
|
+
if (!stopped &&
|
|
201
|
+
(pendingSignals.has(taskId) || failures.has(taskId)))
|
|
199
202
|
schedulePendingRetry(taskId);
|
|
200
203
|
else
|
|
201
204
|
admittedTasks.delete(taskId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomflow/proflow-platform-host",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -24,19 +24,19 @@
|
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"zod": "4.1.12",
|
|
27
|
-
"@tomflow/proflow-execution-browser-extension": "^0.1.51",
|
|
28
|
-
"@tomflow/proflow-agent-runtime": "^0.1.14",
|
|
29
27
|
"@tomflow/proflow-module-contract": "^0.1.13",
|
|
30
|
-
"@tomflow/proflow-task-orchestration": "^0.1.11",
|
|
31
|
-
"@tomflow/proflow-task-migration-runner": "^0.1.11",
|
|
32
28
|
"@tomflow/proflow-execution-contracts": "^0.1.11",
|
|
33
|
-
"@tomflow/proflow-task-
|
|
29
|
+
"@tomflow/proflow-task-migration-runner": "^0.1.11",
|
|
30
|
+
"@tomflow/proflow-task-orchestration": "^0.1.11",
|
|
31
|
+
"@tomflow/proflow-task-store-sqlite": "^0.1.12",
|
|
32
|
+
"@tomflow/proflow-execution-browser-extension": "^0.1.57",
|
|
33
|
+
"@tomflow/proflow-agent-runtime": "^0.1.16"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@tomflow/proflow-agent-gateway": "^0.1.17",
|
|
37
|
-
"@tomflow/proflow-
|
|
38
|
-
"@tomflow/proflow-model-runtime": "^0.1.
|
|
39
|
-
"@tomflow/proflow-
|
|
37
|
+
"@tomflow/proflow-execution-runtime": "^0.1.19",
|
|
38
|
+
"@tomflow/proflow-model-runtime": "^0.1.25",
|
|
39
|
+
"@tomflow/proflow-deployment-conformance": "^0.1.13"
|
|
40
40
|
},
|
|
41
41
|
"description": "Provides the ProFlow local application composition root that binds Task, Agent, Execution and Model owner transports.",
|
|
42
42
|
"keywords": [
|
package/proflow.module.json
CHANGED