@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 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.23";
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.23";
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.23";
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.23";
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.23";
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.23";
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.23";
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.23";
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.23";
119
+ readonly moduleVersion: "0.1.25";
120
120
  } | {
121
121
  contract: "deployment.result.v1";
122
122
  moduleRef: "platform-host";
123
- moduleVersion: "0.1.23";
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.23";
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.23",
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
- ? "HEALTHY"
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
- httpStatus: 403,
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
- Number.isNaN(Date.parse(deadlineAt)))
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"), { httpStatus: 400 });
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
- .listRegisteredRoles()
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
- const taskId = string(value.taskId, "taskId");
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
- decision: "HUMAN_REQUIRED",
1394
- reason: "ROLE_VALIDATION_MISMATCH",
1395
- };
1396
- let taskBinding = null;
1397
- try {
1398
- const current = unwrap(task.queries.getTask({ taskId }));
1399
- taskBinding =
1400
- current.roleBindings.find((candidate) => candidate.roleRef === roleRef &&
1401
- candidate.agentPackageRef === role.agentPackageRef) ?? null;
1402
- }
1403
- catch {
1404
- taskBinding = null;
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
- ? { evidenceRef: value.evidenceRef }
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
- fileMaterializationInputs: body.fileMaterializationInputs,
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 || !pendingSignals.has(taskId) || retryTimers.has(taskId))
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 && pendingSignals.has(taskId))
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.23",
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-store-sqlite": "^0.1.12"
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-deployment-conformance": "^0.1.13",
38
- "@tomflow/proflow-model-runtime": "^0.1.24",
39
- "@tomflow/proflow-execution-runtime": "^0.1.19"
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": [
@@ -3,7 +3,7 @@
3
3
  "contractVersion": "1.0.0",
4
4
  "moduleRef": "platform-host",
5
5
  "packageName": "@tomflow/proflow-platform-host",
6
- "moduleVersion": "0.1.23",
6
+ "moduleVersion": "0.1.25",
7
7
  "kind": "service",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",