@orangecheck/agent-langgraph 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -90,4 +90,4 @@ MIT.
90
90
  - [oc-agent-protocol](https://github.com/orangecheck/oc-agent-protocol) — normative wire format.
91
91
  - [@orangecheck/agent-core](https://www.npmjs.com/package/@orangecheck/agent-core) — verifier library.
92
92
  - [@orangecheck/agent-mcp](https://www.npmjs.com/package/@orangecheck/agent-mcp) / [-anthropic](https://www.npmjs.com/package/@orangecheck/agent-anthropic) / [-openai](https://www.npmjs.com/package/@orangecheck/agent-openai) / [-vercel](https://www.npmjs.com/package/@orangecheck/agent-vercel) — sibling adapters, all stateless.
93
- - [console.ochk.io/integrations/langgraph](https://console.ochk.io/integrations/langgraph) — managed-tier story.
93
+ - [fleet.ochk.io/integrations/langgraph](https://fleet.ochk.io/integrations/langgraph) — managed-tier story.
package/dist/index.d.mts CHANGED
@@ -30,7 +30,7 @@ interface OcToolNodeWrapped<TArgs extends Record<string, unknown>, TResult> {
30
30
  execute: (args: TArgs, ctx: AgentContext & {
31
31
  callId: string;
32
32
  graphState: Record<string, unknown>;
33
- console?: ConsoleClient;
33
+ fleet?: FleetClient;
34
34
  }) => Promise<{
35
35
  result: TResult;
36
36
  action: ActionEnvelope;
@@ -38,7 +38,7 @@ interface OcToolNodeWrapped<TArgs extends Record<string, unknown>, TResult> {
38
38
  }>;
39
39
  }
40
40
  declare function ocToolNode<TArgs extends Record<string, unknown>, TResult>(input: OcToolNodeInput<TArgs, TResult>): OcToolNodeWrapped<TArgs, TResult>;
41
- interface ConsoleClient {
41
+ interface FleetClient {
42
42
  baseUrl?: string;
43
43
  apiToken: string;
44
44
  projectId: string;
@@ -49,6 +49,6 @@ interface PostActionResult {
49
49
  project_id: string;
50
50
  delegation_id: string;
51
51
  }
52
- declare function postActionToConsole(action: ActionEnvelope, client: ConsoleClient): Promise<PostActionResult>;
52
+ declare function postActionToFleet(action: ActionEnvelope, client: FleetClient): Promise<PostActionResult>;
53
53
 
54
- export { type AgentContext, type ConsoleClient, type LangGraphToolCall, type OcToolNodeInput, type OcToolNodeWrapped, type PostActionResult, type StampToolCallInput, canonicalizeToolCall, graphStateHash, ocToolNode, postActionToConsole, stampToolCall, toolCallActionId, toolCallHash };
54
+ export { type AgentContext, type FleetClient, type LangGraphToolCall, type OcToolNodeInput, type OcToolNodeWrapped, type PostActionResult, type StampToolCallInput, canonicalizeToolCall, graphStateHash, ocToolNode, postActionToFleet, stampToolCall, toolCallActionId, toolCallHash };
package/dist/index.d.ts CHANGED
@@ -30,7 +30,7 @@ interface OcToolNodeWrapped<TArgs extends Record<string, unknown>, TResult> {
30
30
  execute: (args: TArgs, ctx: AgentContext & {
31
31
  callId: string;
32
32
  graphState: Record<string, unknown>;
33
- console?: ConsoleClient;
33
+ fleet?: FleetClient;
34
34
  }) => Promise<{
35
35
  result: TResult;
36
36
  action: ActionEnvelope;
@@ -38,7 +38,7 @@ interface OcToolNodeWrapped<TArgs extends Record<string, unknown>, TResult> {
38
38
  }>;
39
39
  }
40
40
  declare function ocToolNode<TArgs extends Record<string, unknown>, TResult>(input: OcToolNodeInput<TArgs, TResult>): OcToolNodeWrapped<TArgs, TResult>;
41
- interface ConsoleClient {
41
+ interface FleetClient {
42
42
  baseUrl?: string;
43
43
  apiToken: string;
44
44
  projectId: string;
@@ -49,6 +49,6 @@ interface PostActionResult {
49
49
  project_id: string;
50
50
  delegation_id: string;
51
51
  }
52
- declare function postActionToConsole(action: ActionEnvelope, client: ConsoleClient): Promise<PostActionResult>;
52
+ declare function postActionToFleet(action: ActionEnvelope, client: FleetClient): Promise<PostActionResult>;
53
53
 
54
- export { type AgentContext, type ConsoleClient, type LangGraphToolCall, type OcToolNodeInput, type OcToolNodeWrapped, type PostActionResult, type StampToolCallInput, canonicalizeToolCall, graphStateHash, ocToolNode, postActionToConsole, stampToolCall, toolCallActionId, toolCallHash };
54
+ export { type AgentContext, type FleetClient, type LangGraphToolCall, type OcToolNodeInput, type OcToolNodeWrapped, type PostActionResult, type StampToolCallInput, canonicalizeToolCall, graphStateHash, ocToolNode, postActionToFleet, stampToolCall, toolCallActionId, toolCallHash };
package/dist/index.js CHANGED
@@ -69,19 +69,19 @@ function ocToolNode(input) {
69
69
  });
70
70
  const result = await input.execute(args);
71
71
  let posted = null;
72
- if (ctx.console) {
72
+ if (ctx.fleet) {
73
73
  try {
74
- posted = await postActionToConsole(action, ctx.console);
74
+ posted = await postActionToFleet(action, ctx.fleet);
75
75
  } catch (err) {
76
- console.error("[oc-agent-langgraph] postActionToConsole failed:", err);
76
+ console.error("[oc-agent-langgraph] postActionToFleet failed:", err);
77
77
  }
78
78
  }
79
79
  return { result, action, posted };
80
80
  }
81
81
  };
82
82
  }
83
- async function postActionToConsole(action, client) {
84
- const baseUrl = client.baseUrl ?? "https://console.ochk.io";
83
+ async function postActionToFleet(action, client) {
84
+ const baseUrl = client.baseUrl ?? "https://fleet.ochk.io";
85
85
  const f = client.fetch ?? fetch;
86
86
  const body = {
87
87
  project_id: client.projectId,
@@ -110,7 +110,7 @@ async function postActionToConsole(action, client) {
110
110
  if (j2.reason) reason = j2.reason;
111
111
  } catch {
112
112
  }
113
- throw new Error(`postActionToConsole failed: ${reason}`);
113
+ throw new Error(`postActionToFleet failed: ${reason}`);
114
114
  }
115
115
  const j = await r.json();
116
116
  return j.action;
@@ -119,7 +119,7 @@ async function postActionToConsole(action, client) {
119
119
  exports.canonicalizeToolCall = canonicalizeToolCall;
120
120
  exports.graphStateHash = graphStateHash;
121
121
  exports.ocToolNode = ocToolNode;
122
- exports.postActionToConsole = postActionToConsole;
122
+ exports.postActionToFleet = postActionToFleet;
123
123
  exports.stampToolCall = stampToolCall;
124
124
  exports.toolCallActionId = toolCallActionId;
125
125
  exports.toolCallHash = toolCallHash;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["canonicalize","hexEncode","sha256","computeActionId","parseScope","isSubScope","signAsAgent","j"],"mappings":"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","file":"index.js","sourcesContent":["// @orangecheck/agent-langgraph — wrap LangGraph tool-node executions in\n// OC Agent action envelopes.\n//\n// LangGraph's distinguishing feature is graph state: stateful agents\n// composed of typed nodes that share a persisted state object. The\n// compliance-relevant question becomes \"at what graph state did this\n// agent take this action?\" — and the answer must be cryptographically\n// bound to the action receipt.\n//\n// This adapter therefore canonicalizes (verb, args, callId, graph_state_hash)\n// — one extra field over the agent-mcp / agent-anthropic / agent-openai /\n// agent-vercel canonicalization. Two verifiers re-deriving the action from\n// a persisted graph snapshot compute the same envelope id only when the\n// snapshot's state-hash matches; tampered or out-of-order replay fails.\n\nimport { sha256 } from '@noble/hashes/sha256';\nimport {\n canonicalize,\n computeActionId,\n hexEncode,\n isSubScope,\n parseScope,\n type DelegationEnvelope,\n} from '@orangecheck/agent-core';\nimport {\n signAsAgent,\n type ActionEnvelope,\n type SignerRef,\n} from '@orangecheck/agent-signer';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Graph state hashing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Compute a stable hash of a LangGraph state object. Use canonical JSON\n * (RFC 8785) so reordered keys / repeated serializations produce the same\n * hash. Returns `sha256:<64-hex>`.\n *\n * Most LangGraph deployments persist the state object via a checkpointer\n * (in-memory, postgres, redis, etc.). The state-hash here is over the\n * checkpointer's serialized state at the moment the tool node fires.\n */\nexport function graphStateHash(state: Record<string, unknown>): string {\n const str = canonicalize(state as unknown as Parameters<typeof canonicalize>[0]);\n return 'sha256:' + hexEncode(sha256(new TextEncoder().encode(str + '\\n')));\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Tool-call canonicalization\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface LangGraphToolCall {\n /** Stable id assigned by your graph runtime for this tool invocation. */\n callId: string;\n /** Tool verb — typically the node name. */\n verb: string;\n /** JSON-serializable arguments to the tool node. */\n args: Record<string, unknown>;\n /** sha256:<64-hex> of the graph state at the moment of execution. */\n graphStateHash: string;\n}\n\n/**\n * Produce the canonical bytes for a LangGraph tool call.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"args\": <canonical JSON of args>,\n * \"call_id\": <callId>,\n * \"graph_state_hash\": <state hash>,\n * \"verb\": <verb>\n * }\n */\nexport function canonicalizeToolCall(c: LangGraphToolCall): Uint8Array {\n const canon = {\n args: c.args,\n call_id: c.callId,\n graph_state_hash: c.graphStateHash,\n verb: c.verb,\n };\n const str = canonicalize(canon as unknown as Parameters<typeof canonicalize>[0]);\n return new TextEncoder().encode(str + '\\n');\n}\n\nexport function toolCallHash(c: LangGraphToolCall): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeToolCall(c)));\n}\n\nexport function toolCallActionId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n c: LangGraphToolCall\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: toolCallHash(c),\n content_length: canonicalizeToolCall(c).byteLength,\n content_mime: 'application/vnd.oc-agent.langgraph-tool-call+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamping primitive\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AgentContext {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n}\n\nexport interface StampToolCallInput extends AgentContext {\n call: LangGraphToolCall;\n /** Defaults to `langgraph:tool(verb=<verb>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\nexport async function stampToolCall(input: StampToolCallInput): Promise<ActionEnvelope> {\n const scopeExercised =\n input.scopeExercised ?? `langgraph:tool(verb=${input.call.verb})`;\n\n const granted = (input.delegation.scopes ?? []).map(parseScope);\n const exercisedParsed = parseScope(scopeExercised);\n if (!granted.some((g) => isSubScope(exercisedParsed, g))) {\n throw new Error(\n `stampToolCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`\n );\n }\n\n const bytes = canonicalizeToolCall(input.call);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: toolCallHash(input.call), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.langgraph-tool-call+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ocToolNode — convenience wrapper\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OcToolNodeInput<TArgs extends Record<string, unknown>, TResult> {\n /** OC Agent verb under which this tool is exercised. */\n verb: string;\n /** Your real handler. Receives the same args your LangGraph tool node receives. */\n execute: (args: TArgs) => Promise<TResult>;\n}\n\nexport interface OcToolNodeWrapped<TArgs extends Record<string, unknown>, TResult> {\n verb: string;\n /**\n * The execute fn your LangGraph node calls. Receives args + a context\n * object with the agent ref, delegation, callId, current graph state,\n * and an optional `console` field that triggers fire-and-forget POST\n * to console.ochk.io/api/actions after the underlying execute returns.\n */\n execute: (\n args: TArgs,\n ctx: AgentContext & {\n callId: string;\n graphState: Record<string, unknown>;\n console?: ConsoleClient;\n }\n ) => Promise<{\n result: TResult;\n action: ActionEnvelope;\n posted: PostActionResult | null;\n }>;\n}\n\n/**\n * Wrap a LangGraph tool-node execute path so it produces an OC Agent\n * action envelope per call. The wrapper computes the graph_state_hash\n * inline from the supplied state object, so the verifier replaying the\n * graph against a persisted snapshot gets the same hash deterministically.\n *\n * Typical shape:\n *\n * const createInvoice = ocToolNode({\n * verb: 'invoice.create',\n * execute: async (args) => myInvoiceCreateImpl(args),\n * });\n *\n * const graph = new StateGraph(MyState)\n * .addNode('createInvoice', async (state) => {\n * const args = pickToolArgs(state);\n * const callId = state.lastToolCallId;\n * const { result, action } = await createInvoice.execute(args, {\n * agent, delegation, callId, graphState: state,\n * });\n * await audit.append(action);\n * return mergeResultIntoState(state, result);\n * });\n */\nexport function ocToolNode<TArgs extends Record<string, unknown>, TResult>(\n input: OcToolNodeInput<TArgs, TResult>\n): OcToolNodeWrapped<TArgs, TResult> {\n return {\n verb: input.verb,\n execute: async (args, ctx) => {\n const call: LangGraphToolCall = {\n callId: ctx.callId,\n verb: input.verb,\n args,\n graphStateHash: graphStateHash(ctx.graphState),\n };\n const action = await stampToolCall({\n agent: ctx.agent,\n delegation: ctx.delegation,\n call,\n });\n const result = await input.execute(args);\n let posted: PostActionResult | null = null;\n if (ctx.console) {\n try {\n posted = await postActionToConsole(action, ctx.console);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[oc-agent-langgraph] postActionToConsole failed:', err);\n }\n }\n return { result, action, posted };\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Console integration: POST stamped actions to console.ochk.io/api/actions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ConsoleClient {\n /** Defaults to https://console.ochk.io. */\n baseUrl?: string;\n /** Bearer token from /settings § 03 (`ock_<hex>`). */\n apiToken: string;\n /** Project the action belongs to (proj_*). */\n projectId: string;\n /** Optional fetch override for runtimes that need it. */\n fetch?: typeof fetch;\n}\n\nexport interface PostActionResult {\n id: string;\n project_id: string;\n delegation_id: string;\n}\n\nexport async function postActionToConsole(\n action: ActionEnvelope,\n client: ConsoleClient\n): Promise<PostActionResult> {\n const baseUrl = client.baseUrl ?? 'https://console.ochk.io';\n const f = client.fetch ?? fetch;\n const body = {\n project_id: client.projectId,\n delegation_id: action.delegation_id,\n agent_address: action.signer.address,\n scope_exercised: action.scope_exercised,\n content_hash: action.content.hash,\n content_length: action.content.length,\n content_mime: action.content.mime,\n signed_at: action.signed_at,\n signature: action.sig.value,\n id: action.id,\n };\n const r = await f(`${baseUrl}/api/actions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${client.apiToken}`,\n },\n body: JSON.stringify(body),\n });\n if (!r.ok) {\n let reason = `http_${r.status}`;\n try {\n const j = (await r.json()) as { reason?: string };\n if (j.reason) reason = j.reason;\n } catch {\n // body wasn't json\n }\n throw new Error(`postActionToConsole failed: ${reason}`);\n }\n const j = (await r.json()) as { ok: true; action: PostActionResult };\n return j.action;\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["canonicalize","hexEncode","sha256","computeActionId","parseScope","isSubScope","signAsAgent","j"],"mappings":"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","file":"index.js","sourcesContent":["// @orangecheck/agent-langgraph — wrap LangGraph tool-node executions in\n// OC Agent action envelopes.\n//\n// LangGraph's distinguishing feature is graph state: stateful agents\n// composed of typed nodes that share a persisted state object. The\n// compliance-relevant question becomes \"at what graph state did this\n// agent take this action?\" — and the answer must be cryptographically\n// bound to the action receipt.\n//\n// This adapter therefore canonicalizes (verb, args, callId, graph_state_hash)\n// — one extra field over the agent-mcp / agent-anthropic / agent-openai /\n// agent-vercel canonicalization. Two verifiers re-deriving the action from\n// a persisted graph snapshot compute the same envelope id only when the\n// snapshot's state-hash matches; tampered or out-of-order replay fails.\n\nimport { sha256 } from '@noble/hashes/sha256';\nimport {\n canonicalize,\n computeActionId,\n hexEncode,\n isSubScope,\n parseScope,\n type DelegationEnvelope,\n} from '@orangecheck/agent-core';\nimport {\n signAsAgent,\n type ActionEnvelope,\n type SignerRef,\n} from '@orangecheck/agent-signer';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Graph state hashing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Compute a stable hash of a LangGraph state object. Use canonical JSON\n * (RFC 8785) so reordered keys / repeated serializations produce the same\n * hash. Returns `sha256:<64-hex>`.\n *\n * Most LangGraph deployments persist the state object via a checkpointer\n * (in-memory, postgres, redis, etc.). The state-hash here is over the\n * checkpointer's serialized state at the moment the tool node fires.\n */\nexport function graphStateHash(state: Record<string, unknown>): string {\n const str = canonicalize(state as unknown as Parameters<typeof canonicalize>[0]);\n return 'sha256:' + hexEncode(sha256(new TextEncoder().encode(str + '\\n')));\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Tool-call canonicalization\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface LangGraphToolCall {\n /** Stable id assigned by your graph runtime for this tool invocation. */\n callId: string;\n /** Tool verb — typically the node name. */\n verb: string;\n /** JSON-serializable arguments to the tool node. */\n args: Record<string, unknown>;\n /** sha256:<64-hex> of the graph state at the moment of execution. */\n graphStateHash: string;\n}\n\n/**\n * Produce the canonical bytes for a LangGraph tool call.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"args\": <canonical JSON of args>,\n * \"call_id\": <callId>,\n * \"graph_state_hash\": <state hash>,\n * \"verb\": <verb>\n * }\n */\nexport function canonicalizeToolCall(c: LangGraphToolCall): Uint8Array {\n const canon = {\n args: c.args,\n call_id: c.callId,\n graph_state_hash: c.graphStateHash,\n verb: c.verb,\n };\n const str = canonicalize(canon as unknown as Parameters<typeof canonicalize>[0]);\n return new TextEncoder().encode(str + '\\n');\n}\n\nexport function toolCallHash(c: LangGraphToolCall): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeToolCall(c)));\n}\n\nexport function toolCallActionId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n c: LangGraphToolCall\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: toolCallHash(c),\n content_length: canonicalizeToolCall(c).byteLength,\n content_mime: 'application/vnd.oc-agent.langgraph-tool-call+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamping primitive\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AgentContext {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n}\n\nexport interface StampToolCallInput extends AgentContext {\n call: LangGraphToolCall;\n /** Defaults to `langgraph:tool(verb=<verb>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\nexport async function stampToolCall(input: StampToolCallInput): Promise<ActionEnvelope> {\n const scopeExercised =\n input.scopeExercised ?? `langgraph:tool(verb=${input.call.verb})`;\n\n const granted = (input.delegation.scopes ?? []).map(parseScope);\n const exercisedParsed = parseScope(scopeExercised);\n if (!granted.some((g) => isSubScope(exercisedParsed, g))) {\n throw new Error(\n `stampToolCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`\n );\n }\n\n const bytes = canonicalizeToolCall(input.call);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: toolCallHash(input.call), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.langgraph-tool-call+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ocToolNode — convenience wrapper\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OcToolNodeInput<TArgs extends Record<string, unknown>, TResult> {\n /** OC Agent verb under which this tool is exercised. */\n verb: string;\n /** Your real handler. Receives the same args your LangGraph tool node receives. */\n execute: (args: TArgs) => Promise<TResult>;\n}\n\nexport interface OcToolNodeWrapped<TArgs extends Record<string, unknown>, TResult> {\n verb: string;\n /**\n * The execute fn your LangGraph node calls. Receives args + a context\n * object with the agent ref, delegation, callId, current graph state,\n * and an optional `fleet` field that triggers fire-and-forget POST\n * to fleet.ochk.io/api/actions after the underlying execute returns.\n */\n execute: (\n args: TArgs,\n ctx: AgentContext & {\n callId: string;\n graphState: Record<string, unknown>;\n fleet?: FleetClient;\n }\n ) => Promise<{\n result: TResult;\n action: ActionEnvelope;\n posted: PostActionResult | null;\n }>;\n}\n\n/**\n * Wrap a LangGraph tool-node execute path so it produces an OC Agent\n * action envelope per call. The wrapper computes the graph_state_hash\n * inline from the supplied state object, so the verifier replaying the\n * graph against a persisted snapshot gets the same hash deterministically.\n *\n * Typical shape:\n *\n * const createInvoice = ocToolNode({\n * verb: 'invoice.create',\n * execute: async (args) => myInvoiceCreateImpl(args),\n * });\n *\n * const graph = new StateGraph(MyState)\n * .addNode('createInvoice', async (state) => {\n * const args = pickToolArgs(state);\n * const callId = state.lastToolCallId;\n * const { result, action } = await createInvoice.execute(args, {\n * agent, delegation, callId, graphState: state,\n * });\n * await audit.append(action);\n * return mergeResultIntoState(state, result);\n * });\n */\nexport function ocToolNode<TArgs extends Record<string, unknown>, TResult>(\n input: OcToolNodeInput<TArgs, TResult>\n): OcToolNodeWrapped<TArgs, TResult> {\n return {\n verb: input.verb,\n execute: async (args, ctx) => {\n const call: LangGraphToolCall = {\n callId: ctx.callId,\n verb: input.verb,\n args,\n graphStateHash: graphStateHash(ctx.graphState),\n };\n const action = await stampToolCall({\n agent: ctx.agent,\n delegation: ctx.delegation,\n call,\n });\n const result = await input.execute(args);\n let posted: PostActionResult | null = null;\n if (ctx.fleet) {\n try {\n posted = await postActionToFleet(action, ctx.fleet);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[oc-agent-langgraph] postActionToFleet failed:', err);\n }\n }\n return { result, action, posted };\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Fleet integration: POST stamped actions to fleet.ochk.io/api/actions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface FleetClient {\n /** Defaults to https://fleet.ochk.io. */\n baseUrl?: string;\n /** Bearer token from /settings § 03 (`ock_<hex>`). */\n apiToken: string;\n /** Project the action belongs to (proj_*). */\n projectId: string;\n /** Optional fetch override for runtimes that need it. */\n fetch?: typeof fetch;\n}\n\nexport interface PostActionResult {\n id: string;\n project_id: string;\n delegation_id: string;\n}\n\nexport async function postActionToFleet(\n action: ActionEnvelope,\n client: FleetClient\n): Promise<PostActionResult> {\n const baseUrl = client.baseUrl ?? 'https://fleet.ochk.io';\n const f = client.fetch ?? fetch;\n const body = {\n project_id: client.projectId,\n delegation_id: action.delegation_id,\n agent_address: action.signer.address,\n scope_exercised: action.scope_exercised,\n content_hash: action.content.hash,\n content_length: action.content.length,\n content_mime: action.content.mime,\n signed_at: action.signed_at,\n signature: action.sig.value,\n id: action.id,\n };\n const r = await f(`${baseUrl}/api/actions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${client.apiToken}`,\n },\n body: JSON.stringify(body),\n });\n if (!r.ok) {\n let reason = `http_${r.status}`;\n try {\n const j = (await r.json()) as { reason?: string };\n if (j.reason) reason = j.reason;\n } catch {\n // body wasn't json\n }\n throw new Error(`postActionToFleet failed: ${reason}`);\n }\n const j = (await r.json()) as { ok: true; action: PostActionResult };\n return j.action;\n}\n"]}
package/dist/index.mjs CHANGED
@@ -67,19 +67,19 @@ function ocToolNode(input) {
67
67
  });
68
68
  const result = await input.execute(args);
69
69
  let posted = null;
70
- if (ctx.console) {
70
+ if (ctx.fleet) {
71
71
  try {
72
- posted = await postActionToConsole(action, ctx.console);
72
+ posted = await postActionToFleet(action, ctx.fleet);
73
73
  } catch (err) {
74
- console.error("[oc-agent-langgraph] postActionToConsole failed:", err);
74
+ console.error("[oc-agent-langgraph] postActionToFleet failed:", err);
75
75
  }
76
76
  }
77
77
  return { result, action, posted };
78
78
  }
79
79
  };
80
80
  }
81
- async function postActionToConsole(action, client) {
82
- const baseUrl = client.baseUrl ?? "https://console.ochk.io";
81
+ async function postActionToFleet(action, client) {
82
+ const baseUrl = client.baseUrl ?? "https://fleet.ochk.io";
83
83
  const f = client.fetch ?? fetch;
84
84
  const body = {
85
85
  project_id: client.projectId,
@@ -108,12 +108,12 @@ async function postActionToConsole(action, client) {
108
108
  if (j2.reason) reason = j2.reason;
109
109
  } catch {
110
110
  }
111
- throw new Error(`postActionToConsole failed: ${reason}`);
111
+ throw new Error(`postActionToFleet failed: ${reason}`);
112
112
  }
113
113
  const j = await r.json();
114
114
  return j.action;
115
115
  }
116
116
 
117
- export { canonicalizeToolCall, graphStateHash, ocToolNode, postActionToConsole, stampToolCall, toolCallActionId, toolCallHash };
117
+ export { canonicalizeToolCall, graphStateHash, ocToolNode, postActionToFleet, stampToolCall, toolCallActionId, toolCallHash };
118
118
  //# sourceMappingURL=index.mjs.map
119
119
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["j"],"mappings":"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","file":"index.mjs","sourcesContent":["// @orangecheck/agent-langgraph — wrap LangGraph tool-node executions in\n// OC Agent action envelopes.\n//\n// LangGraph's distinguishing feature is graph state: stateful agents\n// composed of typed nodes that share a persisted state object. The\n// compliance-relevant question becomes \"at what graph state did this\n// agent take this action?\" — and the answer must be cryptographically\n// bound to the action receipt.\n//\n// This adapter therefore canonicalizes (verb, args, callId, graph_state_hash)\n// — one extra field over the agent-mcp / agent-anthropic / agent-openai /\n// agent-vercel canonicalization. Two verifiers re-deriving the action from\n// a persisted graph snapshot compute the same envelope id only when the\n// snapshot's state-hash matches; tampered or out-of-order replay fails.\n\nimport { sha256 } from '@noble/hashes/sha256';\nimport {\n canonicalize,\n computeActionId,\n hexEncode,\n isSubScope,\n parseScope,\n type DelegationEnvelope,\n} from '@orangecheck/agent-core';\nimport {\n signAsAgent,\n type ActionEnvelope,\n type SignerRef,\n} from '@orangecheck/agent-signer';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Graph state hashing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Compute a stable hash of a LangGraph state object. Use canonical JSON\n * (RFC 8785) so reordered keys / repeated serializations produce the same\n * hash. Returns `sha256:<64-hex>`.\n *\n * Most LangGraph deployments persist the state object via a checkpointer\n * (in-memory, postgres, redis, etc.). The state-hash here is over the\n * checkpointer's serialized state at the moment the tool node fires.\n */\nexport function graphStateHash(state: Record<string, unknown>): string {\n const str = canonicalize(state as unknown as Parameters<typeof canonicalize>[0]);\n return 'sha256:' + hexEncode(sha256(new TextEncoder().encode(str + '\\n')));\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Tool-call canonicalization\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface LangGraphToolCall {\n /** Stable id assigned by your graph runtime for this tool invocation. */\n callId: string;\n /** Tool verb — typically the node name. */\n verb: string;\n /** JSON-serializable arguments to the tool node. */\n args: Record<string, unknown>;\n /** sha256:<64-hex> of the graph state at the moment of execution. */\n graphStateHash: string;\n}\n\n/**\n * Produce the canonical bytes for a LangGraph tool call.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"args\": <canonical JSON of args>,\n * \"call_id\": <callId>,\n * \"graph_state_hash\": <state hash>,\n * \"verb\": <verb>\n * }\n */\nexport function canonicalizeToolCall(c: LangGraphToolCall): Uint8Array {\n const canon = {\n args: c.args,\n call_id: c.callId,\n graph_state_hash: c.graphStateHash,\n verb: c.verb,\n };\n const str = canonicalize(canon as unknown as Parameters<typeof canonicalize>[0]);\n return new TextEncoder().encode(str + '\\n');\n}\n\nexport function toolCallHash(c: LangGraphToolCall): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeToolCall(c)));\n}\n\nexport function toolCallActionId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n c: LangGraphToolCall\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: toolCallHash(c),\n content_length: canonicalizeToolCall(c).byteLength,\n content_mime: 'application/vnd.oc-agent.langgraph-tool-call+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamping primitive\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AgentContext {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n}\n\nexport interface StampToolCallInput extends AgentContext {\n call: LangGraphToolCall;\n /** Defaults to `langgraph:tool(verb=<verb>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\nexport async function stampToolCall(input: StampToolCallInput): Promise<ActionEnvelope> {\n const scopeExercised =\n input.scopeExercised ?? `langgraph:tool(verb=${input.call.verb})`;\n\n const granted = (input.delegation.scopes ?? []).map(parseScope);\n const exercisedParsed = parseScope(scopeExercised);\n if (!granted.some((g) => isSubScope(exercisedParsed, g))) {\n throw new Error(\n `stampToolCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`\n );\n }\n\n const bytes = canonicalizeToolCall(input.call);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: toolCallHash(input.call), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.langgraph-tool-call+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ocToolNode — convenience wrapper\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OcToolNodeInput<TArgs extends Record<string, unknown>, TResult> {\n /** OC Agent verb under which this tool is exercised. */\n verb: string;\n /** Your real handler. Receives the same args your LangGraph tool node receives. */\n execute: (args: TArgs) => Promise<TResult>;\n}\n\nexport interface OcToolNodeWrapped<TArgs extends Record<string, unknown>, TResult> {\n verb: string;\n /**\n * The execute fn your LangGraph node calls. Receives args + a context\n * object with the agent ref, delegation, callId, current graph state,\n * and an optional `console` field that triggers fire-and-forget POST\n * to console.ochk.io/api/actions after the underlying execute returns.\n */\n execute: (\n args: TArgs,\n ctx: AgentContext & {\n callId: string;\n graphState: Record<string, unknown>;\n console?: ConsoleClient;\n }\n ) => Promise<{\n result: TResult;\n action: ActionEnvelope;\n posted: PostActionResult | null;\n }>;\n}\n\n/**\n * Wrap a LangGraph tool-node execute path so it produces an OC Agent\n * action envelope per call. The wrapper computes the graph_state_hash\n * inline from the supplied state object, so the verifier replaying the\n * graph against a persisted snapshot gets the same hash deterministically.\n *\n * Typical shape:\n *\n * const createInvoice = ocToolNode({\n * verb: 'invoice.create',\n * execute: async (args) => myInvoiceCreateImpl(args),\n * });\n *\n * const graph = new StateGraph(MyState)\n * .addNode('createInvoice', async (state) => {\n * const args = pickToolArgs(state);\n * const callId = state.lastToolCallId;\n * const { result, action } = await createInvoice.execute(args, {\n * agent, delegation, callId, graphState: state,\n * });\n * await audit.append(action);\n * return mergeResultIntoState(state, result);\n * });\n */\nexport function ocToolNode<TArgs extends Record<string, unknown>, TResult>(\n input: OcToolNodeInput<TArgs, TResult>\n): OcToolNodeWrapped<TArgs, TResult> {\n return {\n verb: input.verb,\n execute: async (args, ctx) => {\n const call: LangGraphToolCall = {\n callId: ctx.callId,\n verb: input.verb,\n args,\n graphStateHash: graphStateHash(ctx.graphState),\n };\n const action = await stampToolCall({\n agent: ctx.agent,\n delegation: ctx.delegation,\n call,\n });\n const result = await input.execute(args);\n let posted: PostActionResult | null = null;\n if (ctx.console) {\n try {\n posted = await postActionToConsole(action, ctx.console);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[oc-agent-langgraph] postActionToConsole failed:', err);\n }\n }\n return { result, action, posted };\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Console integration: POST stamped actions to console.ochk.io/api/actions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ConsoleClient {\n /** Defaults to https://console.ochk.io. */\n baseUrl?: string;\n /** Bearer token from /settings § 03 (`ock_<hex>`). */\n apiToken: string;\n /** Project the action belongs to (proj_*). */\n projectId: string;\n /** Optional fetch override for runtimes that need it. */\n fetch?: typeof fetch;\n}\n\nexport interface PostActionResult {\n id: string;\n project_id: string;\n delegation_id: string;\n}\n\nexport async function postActionToConsole(\n action: ActionEnvelope,\n client: ConsoleClient\n): Promise<PostActionResult> {\n const baseUrl = client.baseUrl ?? 'https://console.ochk.io';\n const f = client.fetch ?? fetch;\n const body = {\n project_id: client.projectId,\n delegation_id: action.delegation_id,\n agent_address: action.signer.address,\n scope_exercised: action.scope_exercised,\n content_hash: action.content.hash,\n content_length: action.content.length,\n content_mime: action.content.mime,\n signed_at: action.signed_at,\n signature: action.sig.value,\n id: action.id,\n };\n const r = await f(`${baseUrl}/api/actions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${client.apiToken}`,\n },\n body: JSON.stringify(body),\n });\n if (!r.ok) {\n let reason = `http_${r.status}`;\n try {\n const j = (await r.json()) as { reason?: string };\n if (j.reason) reason = j.reason;\n } catch {\n // body wasn't json\n }\n throw new Error(`postActionToConsole failed: ${reason}`);\n }\n const j = (await r.json()) as { ok: true; action: PostActionResult };\n return j.action;\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["j"],"mappings":"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","file":"index.mjs","sourcesContent":["// @orangecheck/agent-langgraph — wrap LangGraph tool-node executions in\n// OC Agent action envelopes.\n//\n// LangGraph's distinguishing feature is graph state: stateful agents\n// composed of typed nodes that share a persisted state object. The\n// compliance-relevant question becomes \"at what graph state did this\n// agent take this action?\" — and the answer must be cryptographically\n// bound to the action receipt.\n//\n// This adapter therefore canonicalizes (verb, args, callId, graph_state_hash)\n// — one extra field over the agent-mcp / agent-anthropic / agent-openai /\n// agent-vercel canonicalization. Two verifiers re-deriving the action from\n// a persisted graph snapshot compute the same envelope id only when the\n// snapshot's state-hash matches; tampered or out-of-order replay fails.\n\nimport { sha256 } from '@noble/hashes/sha256';\nimport {\n canonicalize,\n computeActionId,\n hexEncode,\n isSubScope,\n parseScope,\n type DelegationEnvelope,\n} from '@orangecheck/agent-core';\nimport {\n signAsAgent,\n type ActionEnvelope,\n type SignerRef,\n} from '@orangecheck/agent-signer';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Graph state hashing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Compute a stable hash of a LangGraph state object. Use canonical JSON\n * (RFC 8785) so reordered keys / repeated serializations produce the same\n * hash. Returns `sha256:<64-hex>`.\n *\n * Most LangGraph deployments persist the state object via a checkpointer\n * (in-memory, postgres, redis, etc.). The state-hash here is over the\n * checkpointer's serialized state at the moment the tool node fires.\n */\nexport function graphStateHash(state: Record<string, unknown>): string {\n const str = canonicalize(state as unknown as Parameters<typeof canonicalize>[0]);\n return 'sha256:' + hexEncode(sha256(new TextEncoder().encode(str + '\\n')));\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Tool-call canonicalization\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface LangGraphToolCall {\n /** Stable id assigned by your graph runtime for this tool invocation. */\n callId: string;\n /** Tool verb — typically the node name. */\n verb: string;\n /** JSON-serializable arguments to the tool node. */\n args: Record<string, unknown>;\n /** sha256:<64-hex> of the graph state at the moment of execution. */\n graphStateHash: string;\n}\n\n/**\n * Produce the canonical bytes for a LangGraph tool call.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"args\": <canonical JSON of args>,\n * \"call_id\": <callId>,\n * \"graph_state_hash\": <state hash>,\n * \"verb\": <verb>\n * }\n */\nexport function canonicalizeToolCall(c: LangGraphToolCall): Uint8Array {\n const canon = {\n args: c.args,\n call_id: c.callId,\n graph_state_hash: c.graphStateHash,\n verb: c.verb,\n };\n const str = canonicalize(canon as unknown as Parameters<typeof canonicalize>[0]);\n return new TextEncoder().encode(str + '\\n');\n}\n\nexport function toolCallHash(c: LangGraphToolCall): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeToolCall(c)));\n}\n\nexport function toolCallActionId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n c: LangGraphToolCall\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: toolCallHash(c),\n content_length: canonicalizeToolCall(c).byteLength,\n content_mime: 'application/vnd.oc-agent.langgraph-tool-call+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamping primitive\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AgentContext {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n}\n\nexport interface StampToolCallInput extends AgentContext {\n call: LangGraphToolCall;\n /** Defaults to `langgraph:tool(verb=<verb>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\nexport async function stampToolCall(input: StampToolCallInput): Promise<ActionEnvelope> {\n const scopeExercised =\n input.scopeExercised ?? `langgraph:tool(verb=${input.call.verb})`;\n\n const granted = (input.delegation.scopes ?? []).map(parseScope);\n const exercisedParsed = parseScope(scopeExercised);\n if (!granted.some((g) => isSubScope(exercisedParsed, g))) {\n throw new Error(\n `stampToolCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`\n );\n }\n\n const bytes = canonicalizeToolCall(input.call);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: toolCallHash(input.call), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.langgraph-tool-call+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ocToolNode — convenience wrapper\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OcToolNodeInput<TArgs extends Record<string, unknown>, TResult> {\n /** OC Agent verb under which this tool is exercised. */\n verb: string;\n /** Your real handler. Receives the same args your LangGraph tool node receives. */\n execute: (args: TArgs) => Promise<TResult>;\n}\n\nexport interface OcToolNodeWrapped<TArgs extends Record<string, unknown>, TResult> {\n verb: string;\n /**\n * The execute fn your LangGraph node calls. Receives args + a context\n * object with the agent ref, delegation, callId, current graph state,\n * and an optional `fleet` field that triggers fire-and-forget POST\n * to fleet.ochk.io/api/actions after the underlying execute returns.\n */\n execute: (\n args: TArgs,\n ctx: AgentContext & {\n callId: string;\n graphState: Record<string, unknown>;\n fleet?: FleetClient;\n }\n ) => Promise<{\n result: TResult;\n action: ActionEnvelope;\n posted: PostActionResult | null;\n }>;\n}\n\n/**\n * Wrap a LangGraph tool-node execute path so it produces an OC Agent\n * action envelope per call. The wrapper computes the graph_state_hash\n * inline from the supplied state object, so the verifier replaying the\n * graph against a persisted snapshot gets the same hash deterministically.\n *\n * Typical shape:\n *\n * const createInvoice = ocToolNode({\n * verb: 'invoice.create',\n * execute: async (args) => myInvoiceCreateImpl(args),\n * });\n *\n * const graph = new StateGraph(MyState)\n * .addNode('createInvoice', async (state) => {\n * const args = pickToolArgs(state);\n * const callId = state.lastToolCallId;\n * const { result, action } = await createInvoice.execute(args, {\n * agent, delegation, callId, graphState: state,\n * });\n * await audit.append(action);\n * return mergeResultIntoState(state, result);\n * });\n */\nexport function ocToolNode<TArgs extends Record<string, unknown>, TResult>(\n input: OcToolNodeInput<TArgs, TResult>\n): OcToolNodeWrapped<TArgs, TResult> {\n return {\n verb: input.verb,\n execute: async (args, ctx) => {\n const call: LangGraphToolCall = {\n callId: ctx.callId,\n verb: input.verb,\n args,\n graphStateHash: graphStateHash(ctx.graphState),\n };\n const action = await stampToolCall({\n agent: ctx.agent,\n delegation: ctx.delegation,\n call,\n });\n const result = await input.execute(args);\n let posted: PostActionResult | null = null;\n if (ctx.fleet) {\n try {\n posted = await postActionToFleet(action, ctx.fleet);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[oc-agent-langgraph] postActionToFleet failed:', err);\n }\n }\n return { result, action, posted };\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Fleet integration: POST stamped actions to fleet.ochk.io/api/actions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface FleetClient {\n /** Defaults to https://fleet.ochk.io. */\n baseUrl?: string;\n /** Bearer token from /settings § 03 (`ock_<hex>`). */\n apiToken: string;\n /** Project the action belongs to (proj_*). */\n projectId: string;\n /** Optional fetch override for runtimes that need it. */\n fetch?: typeof fetch;\n}\n\nexport interface PostActionResult {\n id: string;\n project_id: string;\n delegation_id: string;\n}\n\nexport async function postActionToFleet(\n action: ActionEnvelope,\n client: FleetClient\n): Promise<PostActionResult> {\n const baseUrl = client.baseUrl ?? 'https://fleet.ochk.io';\n const f = client.fetch ?? fetch;\n const body = {\n project_id: client.projectId,\n delegation_id: action.delegation_id,\n agent_address: action.signer.address,\n scope_exercised: action.scope_exercised,\n content_hash: action.content.hash,\n content_length: action.content.length,\n content_mime: action.content.mime,\n signed_at: action.signed_at,\n signature: action.sig.value,\n id: action.id,\n };\n const r = await f(`${baseUrl}/api/actions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${client.apiToken}`,\n },\n body: JSON.stringify(body),\n });\n if (!r.ok) {\n let reason = `http_${r.status}`;\n try {\n const j = (await r.json()) as { reason?: string };\n if (j.reason) reason = j.reason;\n } catch {\n // body wasn't json\n }\n throw new Error(`postActionToFleet failed: ${reason}`);\n }\n const j = (await r.json()) as { ok: true; action: PostActionResult };\n return j.action;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangecheck/agent-langgraph",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Wrap LangGraph tool nodes with OC Agent scope enforcement and emit a signed agent-action envelope per execution. Binds the action to the graph-state hash so verifiers replay the audit against the persisted graph snapshot.",
5
5
  "keywords": [
6
6
  "bitcoin",
package/src/index.ts CHANGED
@@ -160,15 +160,15 @@ export interface OcToolNodeWrapped<TArgs extends Record<string, unknown>, TResul
160
160
  /**
161
161
  * The execute fn your LangGraph node calls. Receives args + a context
162
162
  * object with the agent ref, delegation, callId, current graph state,
163
- * and an optional `console` field that triggers fire-and-forget POST
164
- * to console.ochk.io/api/actions after the underlying execute returns.
163
+ * and an optional `fleet` field that triggers fire-and-forget POST
164
+ * to fleet.ochk.io/api/actions after the underlying execute returns.
165
165
  */
166
166
  execute: (
167
167
  args: TArgs,
168
168
  ctx: AgentContext & {
169
169
  callId: string;
170
170
  graphState: Record<string, unknown>;
171
- console?: ConsoleClient;
171
+ fleet?: FleetClient;
172
172
  }
173
173
  ) => Promise<{
174
174
  result: TResult;
@@ -220,12 +220,12 @@ export function ocToolNode<TArgs extends Record<string, unknown>, TResult>(
220
220
  });
221
221
  const result = await input.execute(args);
222
222
  let posted: PostActionResult | null = null;
223
- if (ctx.console) {
223
+ if (ctx.fleet) {
224
224
  try {
225
- posted = await postActionToConsole(action, ctx.console);
225
+ posted = await postActionToFleet(action, ctx.fleet);
226
226
  } catch (err) {
227
227
  // eslint-disable-next-line no-console
228
- console.error('[oc-agent-langgraph] postActionToConsole failed:', err);
228
+ console.error('[oc-agent-langgraph] postActionToFleet failed:', err);
229
229
  }
230
230
  }
231
231
  return { result, action, posted };
@@ -234,11 +234,11 @@ export function ocToolNode<TArgs extends Record<string, unknown>, TResult>(
234
234
  }
235
235
 
236
236
  // ─────────────────────────────────────────────────────────────────────────────
237
- // Console integration: POST stamped actions to console.ochk.io/api/actions
237
+ // Fleet integration: POST stamped actions to fleet.ochk.io/api/actions
238
238
  // ─────────────────────────────────────────────────────────────────────────────
239
239
 
240
- export interface ConsoleClient {
241
- /** Defaults to https://console.ochk.io. */
240
+ export interface FleetClient {
241
+ /** Defaults to https://fleet.ochk.io. */
242
242
  baseUrl?: string;
243
243
  /** Bearer token from /settings § 03 (`ock_<hex>`). */
244
244
  apiToken: string;
@@ -254,11 +254,11 @@ export interface PostActionResult {
254
254
  delegation_id: string;
255
255
  }
256
256
 
257
- export async function postActionToConsole(
257
+ export async function postActionToFleet(
258
258
  action: ActionEnvelope,
259
- client: ConsoleClient
259
+ client: FleetClient
260
260
  ): Promise<PostActionResult> {
261
- const baseUrl = client.baseUrl ?? 'https://console.ochk.io';
261
+ const baseUrl = client.baseUrl ?? 'https://fleet.ochk.io';
262
262
  const f = client.fetch ?? fetch;
263
263
  const body = {
264
264
  project_id: client.projectId,
@@ -288,7 +288,7 @@ export async function postActionToConsole(
288
288
  } catch {
289
289
  // body wasn't json
290
290
  }
291
- throw new Error(`postActionToConsole failed: ${reason}`);
291
+ throw new Error(`postActionToFleet failed: ${reason}`);
292
292
  }
293
293
  const j = (await r.json()) as { ok: true; action: PostActionResult };
294
294
  return j.action;