@tomflow/proflow-platform-host 0.1.27 → 0.1.29

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.29
4
+
5
+ ### Patch Changes
6
+
7
+ - Accept real ChatGPT Custom GPT conversation URLs whose GPT path segment includes the roleRef followed by the human-readable slug, while preserving exact Worker, host, query, and hash validation for Browser Permission policy.
8
+
9
+ ## 0.1.28
10
+
11
+ ### Patch Changes
12
+
13
+ - Align Browser Permission with the current role-carrier validation v2 evidence contract emitted by Agent Runtime.
14
+
3
15
  ## 0.1.27
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.27";
9
+ moduleVersion: "0.1.29";
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";
27
+ readonly moduleVersion: "0.1.29";
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.27";
37
+ moduleVersion: "0.1.29";
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.27";
51
+ moduleVersion: "0.1.29";
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.27";
69
+ moduleVersion: "0.1.29";
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.27";
82
+ moduleVersion: "0.1.29";
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.27";
93
+ moduleVersion: "0.1.29";
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.27";
110
+ readonly moduleVersion: "0.1.29";
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.27";
119
+ readonly moduleVersion: "0.1.29";
120
120
  } | {
121
121
  contract: "deployment.result.v1";
122
122
  moduleRef: "platform-host";
123
- moduleVersion: "0.1.27";
123
+ moduleVersion: "0.1.29";
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.27";
6
+ readonly moduleVersion: "0.1.29";
7
7
  readonly kind: "service";
8
8
  readonly templateVersion: "1.0.0";
9
9
  readonly platformCompatibility: ">=1.0.0 <2.0.0";
@@ -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.27",
6
+ moduleVersion: "0.1.29",
7
7
  kind: "service",
8
8
  templateVersion: "1.0.0",
9
9
  platformCompatibility: ">=1.0.0 <2.0.0",
@@ -1,5 +1,13 @@
1
1
  import { roleOperations } from "./role-operations.js";
2
2
  const loopbackHosts = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
3
+ function validRoleConversationSegment(roleRef, segment) {
4
+ if (segment === roleRef)
5
+ return true;
6
+ if (!segment)
7
+ return false;
8
+ const slugPrefix = `${roleRef}-`;
9
+ return segment.startsWith(slugPrefix) && segment.length > slugPrefix.length;
10
+ }
3
11
  function validWorkerConversation(role, context) {
4
12
  const workerRef = context.workerRef;
5
13
  if (!workerRef)
@@ -15,7 +23,7 @@ function validWorkerConversation(role, context) {
15
23
  locator.hash === "" &&
16
24
  segments.length === 4 &&
17
25
  segments[0] === "g" &&
18
- segments[1] === role.roleRef &&
26
+ validRoleConversationSegment(role.roleRef, segments[1]) &&
19
27
  segments[2] === "c" &&
20
28
  segments[3] === workerRef);
21
29
  }
package/dist/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
+ import { browserStructuredLogSchema, createOperationSink, createHostOperationObserver, operationContext, incomingOperationRef } from "./operation-observability.js";
1
2
  import { randomBytes, timingSafeEqual } from "node:crypto";
2
3
  import { existsSync } from "node:fs";
3
- import { appendFile, chmod, mkdir, readFile, stat, writeFile, } from "node:fs/promises";
4
+ import { chmod, mkdir, readFile, stat, writeFile, } from "node:fs/promises";
4
5
  import { createServer } from "node:http";
5
6
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
6
7
  import { createAgentRuntime } from "@tomflow/proflow-agent-runtime";
@@ -41,32 +42,6 @@ const systemObserverReasonResultSchema = z
41
42
  rationale: z.string().min(1),
42
43
  })
43
44
  .strict();
44
- const browserStructuredLogSchema = z
45
- .object({
46
- timestamp: z.string().datetime(),
47
- level: z.enum(["DEBUG", "INFO", "WARN", "ERROR"]),
48
- component: z.string().min(1).max(100),
49
- capability: z.string().min(1).max(160).optional(),
50
- operation: z.string().min(1).max(160).optional(),
51
- status: z.string().min(1).max(80).optional(),
52
- errorCode: z.string().min(1).max(160).optional(),
53
- correlationId: z.string().min(1).max(240).optional(),
54
- taskId: z.string().min(1).max(240).optional(),
55
- nodeId: z.string().min(1).max(240).optional(),
56
- runNo: z.number().int().nonnegative().optional(),
57
- agentPackageRef: z.string().min(1).max(240).optional(),
58
- roleRef: z.string().min(1).max(240).optional(),
59
- workerRef: z.string().min(1).max(240).optional(),
60
- executionRef: z.string().min(1).max(240).optional(),
61
- messageRef: z.string().min(1).max(240).optional(),
62
- artifactRef: z.string().min(1).max(240).optional(),
63
- evidenceRef: z.string().min(1).max(240).optional(),
64
- conversationLocator: z.string().min(1).max(1_000).optional(),
65
- operationRef: z.string().min(1).max(240).optional(),
66
- attemptNo: z.number().int().nonnegative().optional(),
67
- tabId: z.number().int().nonnegative().optional(),
68
- })
69
- .strict();
70
45
  const taskDocumentFileSchema = z.object({
71
46
  taskId: z.string().min(1),
72
47
  documentType: z.string().min(1),
@@ -408,6 +383,8 @@ async function readDeploymentOwnerSummary(stateRoot) {
408
383
  }
409
384
  }
410
385
  async function constructGraph(config, executionCredential, modelCredential, resolveConnection, resolveLocalToolConnection) {
386
+ const sink = createOperationSink(config.stateRoot);
387
+ const observer = createHostOperationObserver(entry => sink.write("platform-host", entry));
411
388
  const databasePath = join(config.stateRoot, "state", "task.sqlite");
412
389
  const migration = applyMigrations({ databasePath, migrations: taskMigrations });
413
390
  if (!migration.ok)
@@ -468,8 +445,8 @@ async function constructGraph(config, executionCredential, modelCredential, reso
468
445
  taskStore.close();
469
446
  throw error;
470
447
  }
471
- const execution = createOwnerHttpClient("execution", config.executionBaseUrl, executionCredential, resolveConnection);
472
- const model = createOwnerHttpClient("model", config.modelBaseUrl, modelCredential, resolveConnection);
448
+ const execution = observer.port("execution", createOwnerHttpClient("execution", config.executionBaseUrl, executionCredential, resolveConnection));
449
+ const model = observer.port("model", createOwnerHttpClient("model", config.modelBaseUrl, modelCredential, resolveConnection));
473
450
  let reconciliationCoordinator;
474
451
  const boundedSystemView = async (view) => {
475
452
  if (view === "task") {
@@ -616,7 +593,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
616
593
  throw Object.assign(new Error("TASK_WORKER_BINDING_MISMATCH"), { httpStatus: 403 });
617
594
  return binding.workerRef;
618
595
  };
619
- const route = async (operationId, authenticatedRoleRef, rawInput, context) => {
596
+ const routeImpl = async (operationId, authenticatedRoleRef, rawInput, context) => {
620
597
  const role = agent.getRegisteredRole(authenticatedRoleRef);
621
598
  if (!roleOperations[role.agentPackageRef]?.has(operationId))
622
599
  throw Object.assign(new Error("ROLE_OPERATION_DENIED"), { httpStatus: 403 });
@@ -642,7 +619,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
642
619
  if (!connection)
643
620
  throw Object.assign(new Error("LOCAL_TOOL_BRIDGE_UNAVAILABLE"), { httpStatus: 503 });
644
621
  try {
645
- return await createLocalToolBridgeHostClient({
622
+ return await observer.run("tool-invocation", toolOperation, () => createLocalToolBridgeHostClient({
646
623
  endpoint: connection.endpoint,
647
624
  token: connection.credential,
648
625
  }).request({
@@ -652,7 +629,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
652
629
  operation: toolOperation,
653
630
  input: toolInput,
654
631
  deadlineAt,
655
- });
632
+ }));
656
633
  }
657
634
  catch (error) {
658
635
  const code = error instanceof LocalToolBridgeError
@@ -722,6 +699,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
722
699
  }
723
700
  throw new Error("OPERATION_NOT_ROUTED");
724
701
  };
702
+ const route = (...args) => observer.run("action", args[0], () => routeImpl(...args), args[2]);
725
703
  const browserOwnerPorts = Object.freeze({
726
704
  task: Object.freeze({
727
705
  async getWorkerBinding(taskId, roleRef) {
@@ -939,7 +917,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
939
917
  const roleCarrierValidation = async (agentPackageRef) => {
940
918
  try {
941
919
  const value = object(JSON.parse(await readFile(join(config.workspaceRoot, ".proflow", "state", "agent", "role-carrier-validation", `${encodeURIComponent(agentPackageRef)}.json`), "utf8")), "role carrier validation evidence");
942
- if (value.contract !== "proflow.role-carrier-validation.v1")
920
+ if (value.contract !== "proflow.role-carrier-validation.v2")
943
921
  return null;
944
922
  return {
945
923
  agentPackageRef: string(value.agentPackageRef, "agentPackageRef"),
@@ -966,7 +944,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
966
944
  return null;
967
945
  }
968
946
  };
969
- const roleManagement = Object.freeze({
947
+ const roleManagement = observer.port("role", {
970
948
  async invoke(operation, rawInput) {
971
949
  const value = object(rawInput, "role management input");
972
950
  if (operation === "role.register") {
@@ -1103,7 +1081,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
1103
1081
  getProjection: (taskId) => taskDriverPorts.getTaskDriveProjection(taskId),
1104
1082
  requestWake: requestTaskWake,
1105
1083
  });
1106
- const taskApplication = Object.freeze({
1084
+ const taskApplication = observer.port("task", {
1107
1085
  async invoke(operation, rawInput) {
1108
1086
  const value = object(rawInput, "task application input");
1109
1087
  if (operation === "task.create") {
@@ -1214,7 +1192,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
1214
1192
  });
1215
1193
  return context;
1216
1194
  };
1217
- const approvalApplication = Object.freeze({
1195
+ const approvalApplication = observer.port("approval", {
1218
1196
  async invoke(operation, rawInput) {
1219
1197
  const value = object(rawInput, "approval application input");
1220
1198
  if (operation === "approval.list")
@@ -1258,7 +1236,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
1258
1236
  throw new Error("UNSUPPORTED_APPROVAL_APPLICATION_OPERATION");
1259
1237
  },
1260
1238
  });
1261
- const observerApplication = Object.freeze({
1239
+ const observerApplication = observer.port("observer", {
1262
1240
  async invoke(operation, rawInput) {
1263
1241
  const value = object(rawInput, "observer application input");
1264
1242
  if (operation === "task.reconcileAll") {
@@ -1398,8 +1376,9 @@ async function constructGraph(config, executionCredential, modelCredential, reso
1398
1376
  reconciliationCoordinator.start();
1399
1377
  return Object.freeze({
1400
1378
  route,
1379
+ operationSink: sink,
1401
1380
  browserOwnerPorts,
1402
- authorizeExecution,
1381
+ authorizeExecution: (...args) => observer.run("execution-identity", "authorize", () => authorizeExecution(...args)),
1403
1382
  taskDriverPorts,
1404
1383
  agentIdentityPorts,
1405
1384
  roleManagement,
@@ -1684,7 +1663,7 @@ export function createPlatformHost(input) {
1684
1663
  });
1685
1664
  graph = await constructGraph(input.config, executionTransportCredential, modelTransportCredential, input.resolveOwnerConnection, input.resolveLocalToolConnection);
1686
1665
  server = createServer((request, response) => {
1687
- const work = (async () => {
1666
+ const work = operationContext.run({ operationRef: incomingOperationRef(request.headers["x-proflow-operation-ref"]) }, async () => {
1688
1667
  const url = new URL(request.url ?? "/", "http://platform-host.local");
1689
1668
  if (request.method === "GET" && url.pathname === "/health")
1690
1669
  return respond(response, 200, {
@@ -1757,10 +1736,8 @@ export function createPlatformHost(input) {
1757
1736
  }
1758
1737
  try {
1759
1738
  const entry = browserStructuredLogSchema.parse(JSON.parse(Buffer.concat(chunks).toString("utf8")));
1760
- const logPath = join(input.config.stateRoot, "logs", "browser-extension", "events.jsonl");
1761
- await mkdir(dirname(logPath), { recursive: true, mode: 0o700 });
1762
- await appendFile(logPath, `${JSON.stringify({ ...entry, receivedAt: new Date().toISOString() })}\n`, { mode: 0o600 });
1763
- return respond(response, 200, { accepted: true });
1739
+ await graph.operationSink.write("browser-extension", entry);
1740
+ return respond(response, 200, { accepted: true, ...(entry.eventId ? { eventId: entry.eventId } : {}) });
1764
1741
  }
1765
1742
  catch (error) {
1766
1743
  return respond(response, 400, {
@@ -1885,7 +1862,7 @@ export function createPlatformHost(input) {
1885
1862
  error: error instanceof Error ? error.message : "INVALID_REQUEST",
1886
1863
  });
1887
1864
  }
1888
- })();
1865
+ });
1889
1866
  active.add(work);
1890
1867
  void work.finally(() => active.delete(work));
1891
1868
  });
@@ -0,0 +1,73 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { z } from "zod";
3
+ export declare const operationContext: AsyncLocalStorage<{
4
+ operationRef: string;
5
+ parentOperationRef?: string;
6
+ }>;
7
+ export declare function incomingOperationRef(value: unknown): string;
8
+ export declare const browserStructuredLogSchema: z.ZodObject<{
9
+ timestamp: z.ZodString;
10
+ contract: z.ZodOptional<z.ZodLiteral<"proflow.operation-event.v1">>;
11
+ eventId: z.ZodOptional<z.ZodString>;
12
+ sequenceNo: z.ZodOptional<z.ZodNumber>;
13
+ source: z.ZodOptional<z.ZodLiteral<"browser-extension">>;
14
+ extensionInstanceId: z.ZodOptional<z.ZodString>;
15
+ moduleVersion: z.ZodOptional<z.ZodString>;
16
+ event: z.ZodOptional<z.ZodString>;
17
+ phase: z.ZodOptional<z.ZodString>;
18
+ operationId: z.ZodOptional<z.ZodString>;
19
+ correlationKind: z.ZodOptional<z.ZodEnum<{
20
+ ADJACENT: "ADJACENT";
21
+ EXACT: "EXACT";
22
+ IDENTITY_MATCH: "IDENTITY_MATCH";
23
+ }>>;
24
+ decision: z.ZodOptional<z.ZodString>;
25
+ reason: z.ZodOptional<z.ZodString>;
26
+ sideEffectState: z.ZodOptional<z.ZodEnum<{
27
+ APPLIED: "APPLIED";
28
+ NOT_APPLIED: "NOT_APPLIED";
29
+ NOT_STARTED: "NOT_STARTED";
30
+ STARTED: "STARTED";
31
+ UNKNOWN: "UNKNOWN";
32
+ }>>;
33
+ durationMs: z.ZodOptional<z.ZodNumber>;
34
+ contentInstanceId: z.ZodOptional<z.ZodString>;
35
+ browserSessionEpoch: z.ZodOptional<z.ZodNumber>;
36
+ level: z.ZodEnum<{
37
+ DEBUG: "DEBUG";
38
+ ERROR: "ERROR";
39
+ INFO: "INFO";
40
+ WARN: "WARN";
41
+ }>;
42
+ component: z.ZodString;
43
+ capability: z.ZodOptional<z.ZodString>;
44
+ operation: z.ZodOptional<z.ZodString>;
45
+ status: z.ZodOptional<z.ZodString>;
46
+ errorCode: z.ZodOptional<z.ZodString>;
47
+ correlationId: z.ZodOptional<z.ZodString>;
48
+ taskId: z.ZodOptional<z.ZodString>;
49
+ nodeId: z.ZodOptional<z.ZodString>;
50
+ runNo: z.ZodOptional<z.ZodNumber>;
51
+ agentPackageRef: z.ZodOptional<z.ZodString>;
52
+ roleRef: z.ZodOptional<z.ZodString>;
53
+ workerRef: z.ZodOptional<z.ZodString>;
54
+ executionRef: z.ZodOptional<z.ZodString>;
55
+ messageRef: z.ZodOptional<z.ZodString>;
56
+ artifactRef: z.ZodOptional<z.ZodString>;
57
+ evidenceRef: z.ZodOptional<z.ZodString>;
58
+ conversationLocator: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
59
+ operationRef: z.ZodOptional<z.ZodString>;
60
+ attemptNo: z.ZodOptional<z.ZodNumber>;
61
+ tabId: z.ZodOptional<z.ZodNumber>;
62
+ }, z.core.$strict>;
63
+ /** One Host append owner, bounded queue and two 5 MiB segments per source. */
64
+ export declare function createOperationSink(stateRoot: string): {
65
+ write: (source: "browser-extension" | "platform-host", entry: Record<string, unknown>) => Promise<void>;
66
+ drain: () => Promise<void>;
67
+ };
68
+ export declare function createHostOperationObserver(emit: (entry: Record<string, unknown>) => unknown): {
69
+ run: <T>(surface: string, operationId: string, invoke: () => Promise<T>, axesInput?: unknown) => Promise<T>;
70
+ port: <T extends {
71
+ invoke(operation: string, input: unknown): Promise<unknown>;
72
+ }>(surface: string, target: T) => T;
73
+ };
@@ -0,0 +1,232 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { randomUUID } from "node:crypto";
3
+ import { appendFile, mkdir, open, stat, rename, readFile } from "node:fs/promises";
4
+ import { dirname, join } from "node:path";
5
+ import { z } from "zod";
6
+ const safeRef = z.string().regex(/^[A-Za-z0-9_.:@/><-]{1,240}$/);
7
+ function sanitizeUrl(value) {
8
+ try {
9
+ const url = new URL(value);
10
+ if (!["http:", "https:"].includes(url.protocol))
11
+ return "[REDACTED_INVALID_LOCATOR]";
12
+ url.username = "";
13
+ url.password = "";
14
+ url.search = "";
15
+ url.hash = "";
16
+ return url.toString();
17
+ }
18
+ catch {
19
+ return "[REDACTED_INVALID_LOCATOR]";
20
+ }
21
+ }
22
+ async function retainedTail(path) {
23
+ const file = await open(path, "r");
24
+ try {
25
+ const size = (await file.stat()).size;
26
+ const start = Math.max(0, size - 5 * 1024 * 1024);
27
+ const buffer = Buffer.alloc(size - start);
28
+ await file.read(buffer, 0, buffer.length, start);
29
+ const text = buffer.toString("utf8");
30
+ return start ? text.slice(text.indexOf("\n") + 1) : text;
31
+ }
32
+ finally {
33
+ await file.close();
34
+ }
35
+ }
36
+ export const operationContext = new AsyncLocalStorage();
37
+ export function incomingOperationRef(value) {
38
+ return typeof value === "string" && /^op:[0-9a-f-]{36}$/.test(value)
39
+ ? value
40
+ : `op:${randomUUID()}`;
41
+ }
42
+ export const browserStructuredLogSchema = z
43
+ .object({
44
+ timestamp: z.string().datetime(),
45
+ contract: z.literal("proflow.operation-event.v1").optional(),
46
+ eventId: safeRef.optional(),
47
+ sequenceNo: z.number().int().nonnegative().optional(),
48
+ source: z.literal("browser-extension").optional(),
49
+ extensionInstanceId: safeRef.optional(),
50
+ moduleVersion: safeRef.optional(),
51
+ event: safeRef.optional(),
52
+ phase: safeRef.optional(),
53
+ operationId: safeRef.optional(),
54
+ correlationKind: z.enum(["EXACT", "IDENTITY_MATCH", "ADJACENT"]).optional(),
55
+ decision: safeRef.optional(),
56
+ reason: safeRef.optional(),
57
+ sideEffectState: z
58
+ .enum(["NOT_STARTED", "STARTED", "APPLIED", "NOT_APPLIED", "UNKNOWN"])
59
+ .optional(),
60
+ durationMs: z.number().nonnegative().optional(),
61
+ contentInstanceId: safeRef.optional(),
62
+ browserSessionEpoch: z.number().int().nonnegative().optional(),
63
+ level: z.enum(["DEBUG", "INFO", "WARN", "ERROR"]),
64
+ component: safeRef,
65
+ capability: safeRef.optional(),
66
+ operation: safeRef.optional(),
67
+ status: safeRef.optional(),
68
+ errorCode: safeRef.optional(),
69
+ correlationId: safeRef.optional(),
70
+ taskId: safeRef.optional(),
71
+ nodeId: safeRef.optional(),
72
+ runNo: z.number().int().nonnegative().optional(),
73
+ agentPackageRef: safeRef.optional(),
74
+ roleRef: safeRef.optional(),
75
+ workerRef: safeRef.optional(),
76
+ executionRef: safeRef.optional(),
77
+ messageRef: safeRef.optional(),
78
+ artifactRef: safeRef.optional(),
79
+ evidenceRef: safeRef.optional(),
80
+ conversationLocator: z
81
+ .string()
82
+ .max(1_000)
83
+ .transform(sanitizeUrl)
84
+ .optional(),
85
+ operationRef: safeRef.optional(),
86
+ attemptNo: z.number().int().nonnegative().optional(),
87
+ tabId: z.number().int().nonnegative().optional(),
88
+ })
89
+ .strict();
90
+ /** One Host append owner, bounded queue and two 5 MiB segments per source. */
91
+ export function createOperationSink(stateRoot) {
92
+ let tail = Promise.resolve();
93
+ let pending = 0;
94
+ const seen = new Set();
95
+ let restored = false;
96
+ const write = (source, entry) => {
97
+ if (pending >= 1000)
98
+ return Promise.reject(new Error("LOG_QUEUE_FULL"));
99
+ pending++;
100
+ const run = tail.then(async () => {
101
+ const path = join(stateRoot, "logs", source, "events.jsonl");
102
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
103
+ if (source === "browser-extension" && !restored) {
104
+ for (const file of [path + ".1", path]) {
105
+ try {
106
+ for (const line of (await retainedTail(file)).split("\n")) {
107
+ try {
108
+ const v = JSON.parse(line);
109
+ if (v &&
110
+ typeof v === "object" &&
111
+ typeof Reflect.get(v, "eventId") === "string")
112
+ seen.add(String(Reflect.get(v, "eventId")));
113
+ if (seen.size > 20000)
114
+ seen.delete(seen.values().next().value ?? "");
115
+ }
116
+ catch { }
117
+ }
118
+ }
119
+ catch { }
120
+ }
121
+ restored = true;
122
+ }
123
+ const eventId = typeof entry.eventId === "string" ? entry.eventId : undefined;
124
+ if (eventId && seen.has(eventId))
125
+ return;
126
+ const info = await stat(path).catch(() => null);
127
+ if (info &&
128
+ (info.size >= 5 * 1024 * 1024 ||
129
+ Date.now() - info.mtimeMs > 7 * 86400_000))
130
+ await rename(path, path + ".1");
131
+ await appendFile(path, JSON.stringify({ ...entry, receivedAt: new Date().toISOString() }) +
132
+ "\n", { mode: 0o600 });
133
+ if (eventId) {
134
+ seen.add(eventId);
135
+ if (seen.size > 20000)
136
+ seen.delete(seen.values().next().value ?? "");
137
+ }
138
+ });
139
+ tail = run
140
+ .catch(() => undefined)
141
+ .finally(() => {
142
+ pending--;
143
+ });
144
+ return run;
145
+ };
146
+ return { write, drain: () => tail };
147
+ }
148
+ function code(value) {
149
+ return typeof value === "string" && /^[A-Z][A-Z0-9_.:-]{0,159}$/.test(value)
150
+ ? value
151
+ : undefined;
152
+ }
153
+ export function createHostOperationObserver(emit) {
154
+ const run = async (surface, operationId, invoke, axesInput) => {
155
+ const parent = operationContext.getStore();
156
+ const operationRef = `op:${randomUUID()}`;
157
+ const started = performance.now();
158
+ const record = (value, error) => {
159
+ const axes = {};
160
+ for (const candidate of [axesInput, value]) {
161
+ if (!candidate || typeof candidate !== "object")
162
+ continue;
163
+ for (const key of ["taskId", "nodeId", "executionRef", "workerRef", "roleRef", "evidenceRef", "artifactRef"]) {
164
+ const v = Reflect.get(candidate, key);
165
+ if (safeRef.safeParse(v).success)
166
+ axes[key] = String(v);
167
+ }
168
+ }
169
+ const result = value && typeof value === "object" ? value : {};
170
+ const failed = error !== undefined || Reflect.get(result, "ok") === false;
171
+ const errorValue = error ?? Reflect.get(result, "error");
172
+ const errorCode = errorValue && typeof errorValue === "object"
173
+ ? code(Reflect.get(errorValue, "code")) ?? code(Reflect.get(errorValue, "message"))
174
+ : undefined;
175
+ const entry = {
176
+ contract: "proflow.operation-boundary.v1",
177
+ eventId: `host-event:${randomUUID()}`,
178
+ timestamp: new Date().toISOString(),
179
+ source: "platform-host",
180
+ component: "platform-host-application-boundary",
181
+ event: "HOST_APPLICATION",
182
+ phase: surface,
183
+ operationId: safeRef.safeParse(operationId).success
184
+ ? operationId
185
+ : "INVALID_OPERATION",
186
+ operationRef,
187
+ ...(parent
188
+ ? {
189
+ parentOperationRef: parent.operationRef,
190
+ correlationId: parent.parentOperationRef ?? parent.operationRef,
191
+ }
192
+ : {}),
193
+ correlationKind: "EXACT",
194
+ ...axes,
195
+ decision: code(Reflect.get(result, "decision")), reason: code(Reflect.get(result, "reason")),
196
+ status: failed ? "FAILED" : Reflect.get(result, "decision") === "HUMAN_REQUIRED" ? "BLOCKED" : Reflect.get(result, "decision") === "DEFER" ? "DEFERRED" : "SUCCEEDED",
197
+ sideEffectState: ["APPLIED", "NOT_APPLIED", "NOT_STARTED", "UNKNOWN"].includes(String(Reflect.get(result, "sideEffectState"))) ? String(Reflect.get(result, "sideEffectState")) : "UNKNOWN",
198
+ ...(failed
199
+ ? { errorCode: errorCode ?? "HOST_APPLICATION_FAILED" }
200
+ : {}),
201
+ durationMs: performance.now() - started,
202
+ };
203
+ try {
204
+ void Promise.resolve(emit(entry)).catch(() => undefined);
205
+ }
206
+ catch { }
207
+ };
208
+ return operationContext.run({
209
+ operationRef,
210
+ ...(parent
211
+ ? {
212
+ parentOperationRef: parent.parentOperationRef ?? parent.operationRef,
213
+ }
214
+ : {}),
215
+ }, async () => {
216
+ try {
217
+ const value = await invoke();
218
+ record(value);
219
+ return value;
220
+ }
221
+ catch (error) {
222
+ record(undefined, error);
223
+ throw error;
224
+ }
225
+ });
226
+ };
227
+ const port = (surface, target) => Object.freeze({
228
+ ...target,
229
+ invoke: (operation, input) => run(surface, operation, () => target.invoke(operation, input), input),
230
+ });
231
+ return { run, port };
232
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomflow/proflow-platform-host",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -25,15 +25,15 @@
25
25
  "dependencies": {
26
26
  "zod": "4.1.12",
27
27
  "@tomflow/proflow-agent-runtime": "^0.1.17",
28
+ "@tomflow/proflow-execution-browser-extension": "^0.1.61",
28
29
  "@tomflow/proflow-execution-contracts": "^0.1.11",
29
- "@tomflow/proflow-execution-browser-extension": "^0.1.57",
30
- "@tomflow/proflow-task-migration-runner": "^0.1.11",
31
30
  "@tomflow/proflow-module-contract": "^0.1.13",
31
+ "@tomflow/proflow-task-migration-runner": "^0.1.11",
32
32
  "@tomflow/proflow-task-store-sqlite": "^0.1.12",
33
33
  "@tomflow/proflow-task-orchestration": "^0.1.11"
34
34
  },
35
35
  "devDependencies": {
36
- "@tomflow/proflow-agent-gateway": "^0.1.17",
36
+ "@tomflow/proflow-agent-gateway": "^0.1.18",
37
37
  "@tomflow/proflow-execution-runtime": "^0.1.19",
38
38
  "@tomflow/proflow-deployment-conformance": "^0.1.13",
39
39
  "@tomflow/proflow-model-runtime": "^0.1.25"
@@ -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.27",
6
+ "moduleVersion": "0.1.29",
7
7
  "kind": "service",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",