@tomflow/proflow-agent-gateway 0.1.18 → 0.1.19

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,13 @@
1
1
  # @tomflow/proflow-agent-gateway
2
2
 
3
+ ## 0.1.19
4
+
5
+ ### Patch Changes
6
+
7
+ - Preserve allowlisted downstream tool error codes across Gateway HTTP translation while keeping unknown failures opaque, and stop generic Gateway logs from fabricating side-effect state.
8
+
9
+ - Fail closed when the public Gateway role credential store becomes group/world-readable on POSIX systems.
10
+
3
11
  ## 0.1.18
4
12
 
5
13
  ### Patch Changes
@@ -6,7 +6,7 @@ export declare const behaviorAdapter: {
6
6
  ok: true;
7
7
  status: "SUCCEEDED";
8
8
  moduleRef: "agent-gateway";
9
- moduleVersion: "0.1.18";
9
+ moduleVersion: "0.1.19";
10
10
  data: {
11
11
  localBaseUrl: string;
12
12
  publicBaseUrl?: string;
@@ -20,7 +20,7 @@ export declare const behaviorAdapter: {
20
20
  readonly ok: true;
21
21
  readonly status: "SUCCEEDED";
22
22
  readonly moduleRef: "agent-gateway";
23
- readonly moduleVersion: "0.1.18";
23
+ readonly moduleVersion: "0.1.19";
24
24
  };
25
25
  observedEffects: string[];
26
26
  }>;
@@ -30,7 +30,7 @@ export declare const behaviorAdapter: {
30
30
  ok: true;
31
31
  status: "SUCCEEDED";
32
32
  moduleRef: "agent-gateway";
33
- moduleVersion: "0.1.18";
33
+ moduleVersion: "0.1.19";
34
34
  data: {
35
35
  setupStatus: "BLOCKED" | "READY";
36
36
  runtimeStatus: "RUNNING" | "STOPPED";
@@ -51,7 +51,7 @@ export declare const behaviorAdapter: {
51
51
  ok: true;
52
52
  status: "SUCCEEDED";
53
53
  moduleRef: "agent-gateway";
54
- moduleVersion: "0.1.18";
54
+ moduleVersion: "0.1.19";
55
55
  data: {
56
56
  localBaseUrl: string;
57
57
  publicBaseUrl?: string;
@@ -66,7 +66,7 @@ export declare const behaviorAdapter: {
66
66
  ok: true;
67
67
  status: "SUCCEEDED";
68
68
  moduleRef: "agent-gateway";
69
- moduleVersion: "0.1.18";
69
+ moduleVersion: "0.1.19";
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: "agent-gateway";
82
- moduleVersion: "0.1.18";
82
+ moduleVersion: "0.1.19";
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: "agent-gateway";
93
- moduleVersion: "0.1.18";
93
+ moduleVersion: "0.1.19";
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: "agent-gateway";
110
- readonly moduleVersion: "0.1.18";
110
+ readonly moduleVersion: "0.1.19";
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: "agent-gateway";
119
- readonly moduleVersion: "0.1.18";
119
+ readonly moduleVersion: "0.1.19";
120
120
  } | {
121
121
  contract: "deployment.result.v1";
122
122
  moduleRef: "agent-gateway";
123
- moduleVersion: "0.1.18";
123
+ moduleVersion: "0.1.19";
124
124
  ok: false;
125
125
  status: "FAILED";
126
126
  error: {
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
- import { appendFile, mkdir } from "node:fs/promises";
2
+ import { appendFile, mkdir, stat, rename } from "node:fs/promises";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { deterministicLoopbackPort, readModuleSharedFacts, writeModuleSharedFacts, } from "@tomflow/proflow-module-contract";
5
5
  import { descriptor } from "./descriptor.js";
@@ -75,15 +75,31 @@ async function compose(context) {
75
75
  throw new Error("Agent role credential store is not materialized by Platform Host");
76
76
  const logPath = join(deps.stateRoot, "logs", "agent-gateway", "events.jsonl");
77
77
  let logTail = Promise.resolve();
78
+ let pendingLogs = 0;
78
79
  const log = (entry) => {
80
+ if (pendingLogs >= 1000)
81
+ return;
82
+ pendingLogs++;
79
83
  logTail = logTail
80
84
  .catch(() => undefined)
81
85
  .then(async () => {
82
86
  await mkdir(dirname(logPath), { recursive: true, mode: 0o700 });
83
- await appendFile(logPath, `${JSON.stringify(entry)}\n`, {
87
+ const target = entry.component === "agent-gateway-process"
88
+ ? logPath.replace("events.jsonl", "lifecycle.jsonl")
89
+ : logPath;
90
+ const info = await stat(target).catch(() => null);
91
+ if (info &&
92
+ (info.size >= 5 * 1024 * 1024 ||
93
+ Date.now() - info.mtimeMs > 7 * 86400_000))
94
+ await rename(target, `${target}.1`);
95
+ await appendFile(target, `${JSON.stringify(entry)}\n`, {
84
96
  encoding: "utf8",
85
97
  mode: 0o600,
86
98
  });
99
+ })
100
+ .catch(() => undefined)
101
+ .finally(() => {
102
+ pendingLogs--;
87
103
  });
88
104
  void logTail;
89
105
  };
@@ -121,9 +137,7 @@ export const behaviorAdapter = {
121
137
  }
122
138
  return {
123
139
  result: base,
124
- observedEffects: service
125
- ? ["Manage the declared service process"]
126
- : [],
140
+ observedEffects: service ? ["Manage the declared service process"] : [],
127
141
  };
128
142
  },
129
143
  status: async (context) => {
@@ -176,9 +190,7 @@ export const behaviorAdapter = {
176
190
  result: {
177
191
  ...base,
178
192
  data: {
179
- docs: readFileSync(new URL(import.meta.url.includes("/dist/")
180
- ? "../../DOCS.md"
181
- : "../DOCS.md", import.meta.url), "utf8"),
193
+ docs: readFileSync(new URL(import.meta.url.includes("/dist/") ? "../../DOCS.md" : "../DOCS.md", import.meta.url), "utf8"),
182
194
  },
183
195
  },
184
196
  observedEffects: [],
@@ -196,9 +208,7 @@ export const behaviorAdapter = {
196
208
  }
197
209
  catch (error) {
198
210
  return {
199
- result: failed("START_FAILED", error instanceof Error
200
- ? error.message
201
- : "agent-gateway start failed"),
211
+ result: failed("START_FAILED", error instanceof Error ? error.message : "agent-gateway start failed"),
202
212
  observedEffects: [],
203
213
  };
204
214
  }
@@ -3,7 +3,7 @@ export declare const descriptor: {
3
3
  readonly contractVersion: "1.0.0";
4
4
  readonly moduleRef: "agent-gateway";
5
5
  readonly packageName: "@tomflow/proflow-agent-gateway";
6
- readonly moduleVersion: "0.1.18";
6
+ readonly moduleVersion: "0.1.19";
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: "agent-gateway",
5
5
  packageName: "@tomflow/proflow-agent-gateway",
6
- moduleVersion: "0.1.18",
6
+ moduleVersion: "0.1.19",
7
7
  kind: "service",
8
8
  templateVersion: "1.0.0",
9
9
  platformCompatibility: ">=1.0.0 <2.0.0",
@@ -22,6 +22,11 @@ export type GatewayOptions = {
22
22
  port?: number;
23
23
  now?: () => number;
24
24
  actionTimeoutMs?: number;
25
+ onIngressFailure?: (entry: {
26
+ status: "FAILED";
27
+ httpStatus: number;
28
+ sideEffectState: "NOT_APPLIED";
29
+ }) => void;
25
30
  };
26
31
  export declare class AgentGatewayError extends Error {
27
32
  readonly code: string;
package/dist/src/index.js CHANGED
@@ -225,6 +225,7 @@ export async function createAgentGateway(options) {
225
225
  };
226
226
  const handler = async (request, response) => {
227
227
  inFlight += 1;
228
+ let ownerEntered = false;
228
229
  try {
229
230
  const url = new URL(request.url ?? "/", `http://${host}`);
230
231
  if (request.method === "GET" && url.pathname === "/health") {
@@ -335,6 +336,7 @@ export async function createAgentGateway(options) {
335
336
  const propagationReserveMs = Math.min(1_500, Math.max(100, Math.floor(actionTimeoutMs / 10)));
336
337
  const deadlineAt = new Date(now() + Math.max(1, actionTimeoutMs - propagationReserveMs)).toISOString();
337
338
  const actionSignal = AbortSignal.timeout(actionTimeoutMs);
339
+ ownerEntered = true;
338
340
  const operation = action.uncertain && options.owners.lookupResult
339
341
  ? options.owners.lookupResult(action.operationId, authenticatedRoleRef, canonicalBody)
340
342
  : options.owners.route(action.operationId, authenticatedRoleRef, canonicalBody, {
@@ -383,6 +385,16 @@ export async function createAgentGateway(options) {
383
385
  }));
384
386
  }
385
387
  finally {
388
+ if (!ownerEntered && response.statusCode >= 400) {
389
+ try {
390
+ options.onIngressFailure?.({
391
+ status: "FAILED",
392
+ httpStatus: response.statusCode,
393
+ sideEffectState: "NOT_APPLIED",
394
+ });
395
+ }
396
+ catch { }
397
+ }
386
398
  inFlight -= 1;
387
399
  }
388
400
  };
@@ -1,4 +1,4 @@
1
- import { createHash, timingSafeEqual } from "node:crypto";
1
+ import { createHash, timingSafeEqual, randomUUID } from "node:crypto";
2
2
  import { readFile, stat } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
4
  import { AgentGatewayError, createAgentGateway } from "./index.js";
@@ -91,9 +91,7 @@ function logErrorCode(error) {
91
91
  typeof Reflect.get(error, "message") === "string"
92
92
  ? String(Reflect.get(error, "message"))
93
93
  : "GATEWAY_FAILURE";
94
- return /^[A-Z][A-Z0-9_.:-]{0,159}$/.test(value)
95
- ? value
96
- : "GATEWAY_FAILURE";
94
+ return /^[A-Z][A-Z0-9_.:-]{0,159}$/.test(value) ? value : "GATEWAY_FAILURE";
97
95
  }
98
96
  function optionalToolOperation(value) {
99
97
  if (typeof value !== "object" ||
@@ -150,6 +148,9 @@ function parseCredentialStore(value) {
150
148
  return credentials;
151
149
  }
152
150
  async function readCurrentCredentialStore(file) {
151
+ const info = await stat(file);
152
+ if (process.platform !== "win32" && (info.mode & 0o077) !== 0)
153
+ throw new Error("ROLE_CREDENTIAL_STORE_PERMISSIONS_INVALID");
153
154
  return parseCredentialStore(JSON.parse(await readFile(file, "utf8")));
154
155
  }
155
156
  async function readDownstreamCredential(file) {
@@ -175,7 +176,7 @@ export async function createAgentGatewayProcess(input) {
175
176
  return false;
176
177
  }
177
178
  };
178
- const downstream = async (path, body, signal) => {
179
+ const downstream = async (path, body, signal, operationRef) => {
179
180
  const downstreamCredential = input.config.downstreamCredentialFile
180
181
  ? await readDownstreamCredential(input.config.downstreamCredentialFile)
181
182
  : undefined;
@@ -184,6 +185,9 @@ export async function createAgentGatewayProcess(input) {
184
185
  response = await fetchImplementation(`${input.config.downstreamBaseUrl}${path}`, {
185
186
  method: body === undefined ? "GET" : "POST",
186
187
  headers: {
188
+ ...(operationRef
189
+ ? { "x-proflow-operation-ref": operationRef }
190
+ : {}),
187
191
  ...(body === undefined
188
192
  ? {}
189
193
  : { "content-type": "application/json" }),
@@ -202,14 +206,16 @@ export async function createAgentGatewayProcess(input) {
202
206
  }
203
207
  if (!response.ok) {
204
208
  const downstreamCode = await boundedDownstreamErrorCode(response);
205
- const code = response.status >= 500 || response.status === 401
209
+ const code = response.status === 401
206
210
  ? "OWNER_SERVICE_UNAVAILABLE"
207
211
  : (downstreamCode ??
208
- (response.status === 400
209
- ? "INVALID_REQUEST"
210
- : response.status === 403
211
- ? "ROLE_OPERATION_DENIED"
212
- : "DOWNSTREAM_UNAVAILABLE"));
212
+ (response.status >= 500
213
+ ? "OWNER_SERVICE_UNAVAILABLE"
214
+ : response.status === 400
215
+ ? "INVALID_REQUEST"
216
+ : response.status === 403
217
+ ? "ROLE_OPERATION_DENIED"
218
+ : "DOWNSTREAM_UNAVAILABLE"));
213
219
  throw Object.assign(new AgentGatewayError(code), {
214
220
  httpStatus: response.status,
215
221
  });
@@ -220,26 +226,49 @@ export async function createAgentGatewayProcess(input) {
220
226
  const toolOperation = directToolOperationIds.has(inputEvent.operationId)
221
227
  ? optionalToolOperation(inputEvent.value)
222
228
  : undefined;
223
- input.operationLog?.({
224
- contract: "proflow.operation-boundary.v1",
225
- timestamp: new Date().toISOString(),
226
- source: "agent-gateway",
227
- component: "agent-gateway-ingress",
228
- event: inputEvent.mode === "LOOKUP"
229
- ? "GATEWAY_ACTION_LOOKUP"
230
- : "GATEWAY_ACTION",
231
- boundary: directToolOperationIds.has(inputEvent.operationId)
232
- ? "TOOL_INVOCATION"
233
- : "ACTION",
234
- status: inputEvent.status,
235
- operationId: inputEvent.operationId,
236
- roleRef: inputEvent.roleRef,
237
- ...(toolOperation ? { toolOperation } : {}),
238
- ...(inputEvent.errorCode ? { errorCode: inputEvent.errorCode } : {}),
239
- durationMs: inputEvent.durationMs,
240
- });
229
+ try {
230
+ void Promise.resolve(input.operationLog?.({
231
+ contract: "proflow.operation-boundary.v1",
232
+ eventId: `gateway-event:${randomUUID()}`,
233
+ operationRef: inputEvent.operationRef,
234
+ correlationKind: "EXACT",
235
+ timestamp: new Date().toISOString(),
236
+ source: "agent-gateway",
237
+ component: "agent-gateway-ingress",
238
+ event: inputEvent.mode === "LOOKUP"
239
+ ? "GATEWAY_ACTION_LOOKUP"
240
+ : "GATEWAY_ACTION",
241
+ boundary: directToolOperationIds.has(inputEvent.operationId)
242
+ ? "TOOL_INVOCATION"
243
+ : "ACTION",
244
+ status: inputEvent.status,
245
+ operationId: inputEvent.operationId,
246
+ roleRef: inputEvent.roleRef,
247
+ ...(toolOperation ? { toolOperation } : {}),
248
+ ...(inputEvent.errorCode ? { errorCode: inputEvent.errorCode } : {}),
249
+ durationMs: inputEvent.durationMs,
250
+ })).catch(() => undefined);
251
+ }
252
+ catch { }
241
253
  };
242
254
  const gateway = await createAgentGateway({
255
+ onIngressFailure(entry) {
256
+ try {
257
+ void Promise.resolve(input.operationLog?.({
258
+ contract: "proflow.operation-boundary.v1",
259
+ timestamp: new Date().toISOString(),
260
+ eventId: `gateway-event:${randomUUID()}`,
261
+ operationRef: `op:${randomUUID()}`,
262
+ correlationKind: "EXACT",
263
+ source: "agent-gateway",
264
+ component: "agent-gateway-ingress",
265
+ event: "GATEWAY_INGRESS_REJECTED",
266
+ ...entry,
267
+ errorCode: "GATEWAY_INGRESS_REJECTED",
268
+ })).catch(() => undefined);
269
+ }
270
+ catch { }
271
+ },
243
272
  host: input.config.host,
244
273
  port: input.config.port,
245
274
  relayBaseUrl: `${input.config.publicBaseUrl}/relay/`,
@@ -253,6 +282,7 @@ export async function createAgentGatewayProcess(input) {
253
282
  },
254
283
  async route(operationId, authenticatedRoleRef, value, context) {
255
284
  const started = performance.now();
285
+ const operationRef = `op:${randomUUID()}`;
256
286
  try {
257
287
  const result = await downstream(`/actions/${encodeURIComponent(operationId)}`, {
258
288
  authenticatedRoleRef,
@@ -263,9 +293,10 @@ export async function createAgentGatewayProcess(input) {
263
293
  : {
264
294
  fileMaterializationInputs: context.fileMaterializationInputs,
265
295
  }),
266
- }, context?.signal);
296
+ }, context?.signal, operationRef);
267
297
  emitOperation({
268
298
  mode: "ROUTE",
299
+ operationRef,
269
300
  operationId,
270
301
  roleRef: authenticatedRoleRef,
271
302
  value,
@@ -277,6 +308,7 @@ export async function createAgentGatewayProcess(input) {
277
308
  catch (error) {
278
309
  emitOperation({
279
310
  mode: "ROUTE",
311
+ operationRef,
280
312
  operationId,
281
313
  roleRef: authenticatedRoleRef,
282
314
  value,
@@ -289,10 +321,12 @@ export async function createAgentGatewayProcess(input) {
289
321
  },
290
322
  async lookupResult(operationId, authenticatedRoleRef, value) {
291
323
  const started = performance.now();
324
+ const operationRef = `op:${randomUUID()}`;
292
325
  try {
293
- const result = await downstream(`/actions/${encodeURIComponent(operationId)}/result`, { authenticatedRoleRef, input: value });
326
+ const result = await downstream(`/actions/${encodeURIComponent(operationId)}/result`, { authenticatedRoleRef, input: value }, undefined, operationRef);
294
327
  emitOperation({
295
328
  mode: "LOOKUP",
329
+ operationRef,
296
330
  operationId,
297
331
  roleRef: authenticatedRoleRef,
298
332
  value,
@@ -304,6 +338,7 @@ export async function createAgentGatewayProcess(input) {
304
338
  catch (error) {
305
339
  emitOperation({
306
340
  mode: "LOOKUP",
341
+ operationRef,
307
342
  operationId,
308
343
  roleRef: authenticatedRoleRef,
309
344
  value,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomflow/proflow-agent-gateway",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -28,14 +28,14 @@
28
28
  "@tomflow/proflow-module-contract": "^0.1.13"
29
29
  },
30
30
  "devDependencies": {
31
+ "@tomflow/proflow-task-migration-runner": "^0.1.11",
32
+ "@tomflow/proflow-execution-browser-extension": "^0.1.62",
33
+ "@tomflow/proflow-deployment-conformance": "^0.1.13",
31
34
  "@tomflow/proflow-agent-runtime": "^0.1.17",
32
- "@tomflow/proflow-execution-browser-extension": "^0.1.58",
35
+ "@tomflow/proflow-task-store-sqlite": "^0.1.12",
33
36
  "@tomflow/proflow-execution-runtime": "^0.1.19",
34
- "@tomflow/proflow-platform-host": "^0.1.28",
35
- "@tomflow/proflow-deployment-conformance": "^0.1.13",
36
- "@tomflow/proflow-task-migration-runner": "^0.1.11",
37
- "@tomflow/proflow-task-orchestration": "^0.1.11",
38
- "@tomflow/proflow-task-store-sqlite": "^0.1.12"
37
+ "@tomflow/proflow-platform-host": "^0.1.29",
38
+ "@tomflow/proflow-task-orchestration": "^0.1.11"
39
39
  },
40
40
  "description": "The sole Custom GPT Actions HTTP ingress and OpenAI transport anti-corruption layer.",
41
41
  "keywords": [
@@ -3,7 +3,7 @@
3
3
  "contractVersion": "1.0.0",
4
4
  "moduleRef": "agent-gateway",
5
5
  "packageName": "@tomflow/proflow-agent-gateway",
6
- "moduleVersion": "0.1.18",
6
+ "moduleVersion": "0.1.19",
7
7
  "kind": "service",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",