@intutic/gate 1.10.1

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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +261 -0
  3. package/dist/actions.d.ts +86 -0
  4. package/dist/actions.d.ts.map +1 -0
  5. package/dist/actions.js +239 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/client.d.ts +94 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +207 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/dsh.d.ts +162 -0
  12. package/dist/dsh.d.ts.map +1 -0
  13. package/dist/dsh.js +189 -0
  14. package/dist/dsh.js.map +1 -0
  15. package/dist/errors.d.ts +41 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +53 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/eve.d.ts +425 -0
  20. package/dist/eve.d.ts.map +1 -0
  21. package/dist/eve.js +378 -0
  22. package/dist/eve.js.map +1 -0
  23. package/dist/gate.d.ts +113 -0
  24. package/dist/gate.d.ts.map +1 -0
  25. package/dist/gate.js +274 -0
  26. package/dist/gate.js.map +1 -0
  27. package/dist/harness.d.ts +482 -0
  28. package/dist/harness.d.ts.map +1 -0
  29. package/dist/harness.js +504 -0
  30. package/dist/harness.js.map +1 -0
  31. package/dist/headers.d.ts +26 -0
  32. package/dist/headers.d.ts.map +1 -0
  33. package/dist/headers.js +30 -0
  34. package/dist/headers.js.map +1 -0
  35. package/dist/imagecheck.d.ts +93 -0
  36. package/dist/imagecheck.d.ts.map +1 -0
  37. package/dist/imagecheck.js +286 -0
  38. package/dist/imagecheck.js.map +1 -0
  39. package/dist/index.d.ts +26 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +19 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/managedAgents.d.ts +375 -0
  44. package/dist/managedAgents.d.ts.map +1 -0
  45. package/dist/managedAgents.js +446 -0
  46. package/dist/managedAgents.js.map +1 -0
  47. package/dist/mastra.d.ts +175 -0
  48. package/dist/mastra.d.ts.map +1 -0
  49. package/dist/mastra.js +161 -0
  50. package/dist/mastra.js.map +1 -0
  51. package/dist/openai.d.ts +392 -0
  52. package/dist/openai.d.ts.map +1 -0
  53. package/dist/openai.js +658 -0
  54. package/dist/openai.js.map +1 -0
  55. package/dist/shlex.d.ts +16 -0
  56. package/dist/shlex.d.ts.map +1 -0
  57. package/dist/shlex.js +78 -0
  58. package/dist/shlex.js.map +1 -0
  59. package/dist/snapshot.d.ts +111 -0
  60. package/dist/snapshot.d.ts.map +1 -0
  61. package/dist/snapshot.js +243 -0
  62. package/dist/snapshot.js.map +1 -0
  63. package/dist/soprules.d.ts +88 -0
  64. package/dist/soprules.d.ts.map +1 -0
  65. package/dist/soprules.js +176 -0
  66. package/dist/soprules.js.map +1 -0
  67. package/dist/trueforge.d.ts +191 -0
  68. package/dist/trueforge.d.ts.map +1 -0
  69. package/dist/trueforge.js +184 -0
  70. package/dist/trueforge.js.map +1 -0
  71. package/dist/vercel.d.ts +174 -0
  72. package/dist/vercel.d.ts.map +1 -0
  73. package/dist/vercel.js +157 -0
  74. package/dist/vercel.js.map +1 -0
  75. package/dist/workflow.d.ts +209 -0
  76. package/dist/workflow.d.ts.map +1 -0
  77. package/dist/workflow.js +254 -0
  78. package/dist/workflow.js.map +1 -0
  79. package/dist/wrapTools.d.ts +68 -0
  80. package/dist/wrapTools.d.ts.map +1 -0
  81. package/dist/wrapTools.js +95 -0
  82. package/dist/wrapTools.js.map +1 -0
  83. package/package.json +123 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * SOP rules authored in the product, enforced before execution.
3
+ *
4
+ * Port of `packages/intutic-clawde/intutic_clawde/gate/soprules.py`.
5
+ *
6
+ * `SopRule` has carried an optional `argPattern` since mcp-proxy shipped, and
7
+ * `packages/mcp-proxy/src/policy.ts` matches it against the serialised tool
8
+ * input. `intutic/intutic-enterprise#14` added a ` WHERE ` clause to the SOP
9
+ * title grammar, so a rule can finally say "kubectl apply without a digest"
10
+ * instead of only "any shell call":
11
+ *
12
+ * BLOCK:^shell$ WHERE kubectl\s+apply(?!.*@sha256:):deploy must be pinned
13
+ *
14
+ * Now the *policy* lives in the SOP register — versioned, lifecycle-gated,
15
+ * visible on the dashboard, editable without touching agent code — and this
16
+ * module is only the part that applies it.
17
+ *
18
+ * Matching mirrors `matchSopRule` in
19
+ * `services/control-plane/src/lib/sopRuleTitle.ts` (itself a deliberate
20
+ * line-for-line mirror of `PolicyClient.matchRule` in
21
+ * `packages/mcp-proxy/src/policy.ts`), deliberately:
22
+ *
23
+ * * rules are tried in the order the control plane returned them
24
+ * * first match wins
25
+ * * `toolPattern` is tested against the tool name, `argPattern` against the
26
+ * serialised tool input
27
+ * * the serialisation is the `JSON.stringify(tool_input ?? {})` shape every
28
+ * mirror matches against — compact separators, insertion order, non-ASCII
29
+ * left intact
30
+ * * a rule whose regex does not compile is SKIPPED, not fatal
31
+ *
32
+ * Fetch failure is non-fatal for the same reason it is in `policy.ts`: the
33
+ * gate's image-integrity tier independently covers the deploy case and fails
34
+ * CLOSED. This tier changes where the policy is *authored*, not whether the
35
+ * run is safe.
36
+ */
37
+ export const FETCH_TIMEOUT_MS = 3_000;
38
+ // Actions the control plane can return. Anything else is ignored rather than
39
+ // guessed at — an unrecognised action must not silently become "allow".
40
+ export const ACTION_BLOCK = 'block';
41
+ export const ACTION_WARN = 'warn';
42
+ export const ACTION_APPROVAL = 'require_approval';
43
+ const KNOWN_ACTIONS = new Set([ACTION_BLOCK, ACTION_WARN, ACTION_APPROVAL]);
44
+ /** Mirror of `matchSopRule`, including its silent-skip on bad regex. */
45
+ export function ruleMatches(rule, toolName, toolInputJson) {
46
+ try {
47
+ if (!new RegExp(rule.toolPattern).test(toolName))
48
+ return false;
49
+ if (rule.argPattern !== null) {
50
+ return new RegExp(rule.argPattern).test(toolInputJson);
51
+ }
52
+ return true;
53
+ }
54
+ catch {
55
+ // Malformed regex in rule — skip silently, exactly as the control plane
56
+ // and mcp-proxy mirrors do.
57
+ return false;
58
+ }
59
+ }
60
+ /**
61
+ * The `JSON.stringify(tool_input ?? {})` shape every mirror matches against.
62
+ *
63
+ * `JSON.stringify` already produces compact separators and preserves
64
+ * insertion order and non-ASCII by default — this function exists mainly so
65
+ * every call site names the contract explicitly rather than calling
66
+ * `JSON.stringify` inline and drifting on a future edit.
67
+ */
68
+ export function serialiseToolInput(toolInput) {
69
+ return JSON.stringify(toolInput ?? {});
70
+ }
71
+ /**
72
+ * Accept the rule list in whichever envelope the control plane used.
73
+ *
74
+ * `/api/v1/sop/rules` returns `{rules:[...]}`, while sibling list endpoints
75
+ * variously return `{items:[...]}` and `{data:[...]}`. Reading the wrong key
76
+ * yields zero rules and a tier that enforces nothing while looking healthy —
77
+ * the worst failure a gate can have — so all three are accepted rather than
78
+ * assumed.
79
+ */
80
+ export function parseRules(payload) {
81
+ let rows;
82
+ if (Array.isArray(payload)) {
83
+ rows = payload;
84
+ }
85
+ else if (payload !== null && typeof payload === 'object') {
86
+ const obj = payload;
87
+ const candidate = obj.rules ?? obj.items ?? obj.data ?? [];
88
+ rows = Array.isArray(candidate) ? candidate : [];
89
+ }
90
+ else {
91
+ return [];
92
+ }
93
+ const out = [];
94
+ for (const row of rows) {
95
+ if (row === null || typeof row !== 'object')
96
+ continue;
97
+ const r = row;
98
+ const toolPattern = r.toolPattern ?? r.tool_pattern;
99
+ const action = String(r.action ?? '').toLowerCase();
100
+ if (typeof toolPattern !== 'string' || !toolPattern)
101
+ continue;
102
+ if (!KNOWN_ACTIONS.has(action))
103
+ continue;
104
+ const arg = r.argPattern ?? r.arg_pattern;
105
+ out.push({
106
+ id: String(r.id ?? r.sopId ?? 'sop'),
107
+ toolPattern,
108
+ action,
109
+ reason: String(r.reason ?? `SOP rule ${toolPattern}`),
110
+ argPattern: typeof arg === 'string' && arg ? arg : null,
111
+ });
112
+ }
113
+ return out;
114
+ }
115
+ /**
116
+ * Fetch active VALIDATED rules, or `null` if they could not be fetched.
117
+ *
118
+ * `null` and `[]` mean different things and the caller must not conflate
119
+ * them: `null` is "the register did not answer", `[]` is "the register
120
+ * answered and this workspace has no rules". Only the second is a statement
121
+ * about policy.
122
+ */
123
+ export async function fetchRules(baseUrl, apiKey, workspaceId, timeoutMs = FETCH_TIMEOUT_MS) {
124
+ const qs = new URLSearchParams({ workspaceId, active: 'true' });
125
+ const controller = new AbortController();
126
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
127
+ try {
128
+ const res = await fetch(`${baseUrl.replace(/\/$/, '')}/api/v1/sop/rules?${qs.toString()}`, {
129
+ method: 'GET',
130
+ headers: {
131
+ Authorization: `Bearer ${apiKey}`,
132
+ 'X-Workspace-Id': workspaceId,
133
+ },
134
+ signal: controller.signal,
135
+ });
136
+ if (!res.ok)
137
+ return null;
138
+ const raw = await res.text();
139
+ return parseRules(raw ? JSON.parse(raw) : {});
140
+ }
141
+ catch {
142
+ return null;
143
+ }
144
+ finally {
145
+ clearTimeout(timer);
146
+ }
147
+ }
148
+ /**
149
+ * True when the control plane emitted at least one usable `argPattern`.
150
+ *
151
+ * A control plane predating `intutic-enterprise#14` cannot produce one, so
152
+ * this doubles as a deployment probe — surface it rather than letting the
153
+ * tier be silently inert.
154
+ */
155
+ export function supportsArgPatterns(rules) {
156
+ return rules.some((r) => r.argPattern !== null && compiles(r.argPattern));
157
+ }
158
+ function compiles(pattern) {
159
+ try {
160
+ new RegExp(pattern);
161
+ return true;
162
+ }
163
+ catch {
164
+ return false;
165
+ }
166
+ }
167
+ /** First rule matching this call, in control-plane order. */
168
+ export function firstMatch(rules, toolName, toolInput) {
169
+ const payload = serialiseToolInput(toolInput);
170
+ for (const rule of rules) {
171
+ if (ruleMatches(rule, toolName, payload))
172
+ return rule;
173
+ }
174
+ return null;
175
+ }
176
+ //# sourceMappingURL=soprules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soprules.js","sourceRoot":"","sources":["../src/soprules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,191 @@
1
+ /**
2
+ * TrueForge (embedded) adapter: `intuticApprovalResponder()`.
3
+ *
4
+ * `@intutic/gate/trueforge` — Phase 1 of TrueForge support: another team's
5
+ * Node process that imports `@truefoundry/trueforge-core` directly
6
+ * (github.com/truefoundry/trueforge, MIT) and drives its own
7
+ * `SessionHandle`/`TurnHandle` API in-process. Same family idea as
8
+ * `mastra.ts`/`vercel.ts` (wrap the framework's own veto point around this
9
+ * package's `Gate.guard()`), but TrueForge's veto point is shaped
10
+ * differently from either of those, so this module follows `harness.ts`'s
11
+ * `intuticApprovalResponder()` precedent instead — a batch function
12
+ * answering pending approvals — not a single-call `beforeToolCall`/
13
+ * `toolApproval` option.
14
+ *
15
+ * Phase 2 (`HarnessType.TRUEFORGE_SERVER` — TrueForge run as its own
16
+ * standalone/hosted server, e.g. `npx @truefoundry/trueforge`, Docker
17
+ * Compose, or the Helm chart) is explicitly OUT OF SCOPE here. Nobody embeds
18
+ * an Intutic gate into a third-party OSS server process; that deployment
19
+ * mode needs governance running in an external Intutic-operated bridge
20
+ * service — separate, larger work, not attempted in this module.
21
+ *
22
+ * ## Confirmed against the real published package, not assumed
23
+ *
24
+ * `@truefoundry/trueforge-core@0.1.4` (`latest` on npm at the time this was
25
+ * written) was downloaded from the npm registry and its shipped `.d.ts`
26
+ * files read directly — not inferred from docs alone:
27
+ *
28
+ * - There is NO synchronous, in-process "approval resolver" callback
29
+ * anywhere in the package. Every shipped `.d.ts` file was grepped for
30
+ * `approvalResolver`/`onApproval`/`approvalHandler`/`resolveApproval`:
31
+ * zero matches. `ITurnResourceResolver`
32
+ * (`dist/agent-session/ITurnResourceResolver.d.ts`) resolves sandboxes,
33
+ * agent definitions, and tracing per run — it carries no tool-call veto
34
+ * hook of any kind.
35
+ * - The ONLY approval mechanism, embedded or not, is the same turn/event
36
+ * contract TrueForge's standalone server exposes over HTTP: a turn
37
+ * pauses with a `required_actions` entry of type `tool.approval_required`
38
+ * (`EventType.TOOL_APPROVAL_REQUIRED`, `dist/agent-session/schemas/
39
+ * events.d.ts`), naming each pending call only as `{id, source_event_id}`
40
+ * — no tool name or arguments travel on the event itself. Answering it
41
+ * means constructing a NEW turn (`SessionHandle.createTurn()`) whose
42
+ * `input` carries a `user.tool_approval` item:
43
+ * `{type:'user.tool_approval', thread_id, tool_call_id, approval}`,
44
+ * where `approval` is `{status:'allow'} | {status:'deny', reason?:
45
+ * string}` (`ApprovalDecisionSchema`, `dist/core/events/schema.d.ts`) —
46
+ * confirmed field-for-field, and pinned by this module's own test file
47
+ * via a structural assignability check against the real exported
48
+ * `TurnInputItem` (`@truefoundry/trueforge-core/agent-session`) and
49
+ * `ApprovalDecision` (`@truefoundry/trueforge-core/core`) types.
50
+ *
51
+ * So unlike `mastra.ts`/`vercel.ts`, this adapter cannot BE "the function you
52
+ * pass as a framework option" — embedded TrueForge has no such option. What
53
+ * it can be, and is, is the `harness.ts`-shaped piece: a function that
54
+ * evaluates a batch of pending approval requests through `Gate.guard()` and
55
+ * returns the `user.tool_approval` `TurnInputItem[]` ready to hand to your
56
+ * next `session.createTurn({ input, previous_turn_id })` call.
57
+ *
58
+ * ## What the embedding host still has to do itself
59
+ *
60
+ * Resolving a `{id, source_event_id}` tool-call reference back to the real
61
+ * tool NAME and ARGUMENTS is the host's job, not this adapter's: the host
62
+ * already owns the `TurnHandle`/event stream (`turn.stream()`/
63
+ * `listEvents()`) this package has no reason to depend on, and
64
+ * `source_event_id` points at a `model.message` event whose tool-call wire
65
+ * shape this package deliberately does not import — matching
66
+ * `mastra.ts`/`vercel.ts`'s existing convention of zero runtime dependency
67
+ * on the framework being gated. Build the `TrueforgeApprovalRequest[]` this
68
+ * responder consumes from whatever your own event-resolution loop already
69
+ * extracts, then feed the returned items straight into your next
70
+ * `createTurn()` call.
71
+ *
72
+ * ## GUESSED, clearly marked as such: the exact orchestration wiring
73
+ *
74
+ * The plan that authored this phase asked to pin the exact registration API
75
+ * against `TurnHandle`/`ITurnResourceResolver`; having read both real files,
76
+ * NEITHER carries a registration point for this at all — the orchestration
77
+ * loop (drive `turn.stream()`, detect a `tool.approval_required` required
78
+ * action, resolve each `source_event_id`, call this responder, call
79
+ * `createTurn()` again with the answers) is necessarily HAND-WRITTEN
80
+ * application code in the embedding host, not something a config option
81
+ * turns on. That loop's exact shape — how you persist the paused turn id,
82
+ * how you resolve `source_event_id`, whether you drain `stream()` to
83
+ * completion before or after answering — is a genuine judgment call for the
84
+ * embedding host's own architecture and is NOT prescribed here. See
85
+ * `apps/docs/integrations/trueforge.md` for a worked (illustrative, not
86
+ * load-bearing) example of one way to wire this loop.
87
+ *
88
+ * @module
89
+ */
90
+ import { type Gate } from './gate.js';
91
+ /**
92
+ * One pending `tool.approval_required` request, already resolved to a real
93
+ * tool name and arguments by the embedding host — see the module doc's
94
+ * "What the embedding host still has to do itself".
95
+ */
96
+ export interface TrueforgeApprovalRequest {
97
+ /** TrueForge's own thread id for this call — carried through unchanged
98
+ * onto the `user.tool_approval` item this responder produces. */
99
+ readonly threadId: string;
100
+ /** The pending tool call's id (`ToolCallRef.id`) — becomes `tool_call_id`
101
+ * on the produced item. */
102
+ readonly toolCallId: string;
103
+ /** The tool name `Gate.guard()` evaluates. */
104
+ readonly toolName: string;
105
+ /** The tool's arguments, resolved from the `model.message` event the
106
+ * pending call's `source_event_id` points at. */
107
+ readonly input: unknown;
108
+ }
109
+ /**
110
+ * Structural copy of `@truefoundry/trueforge-core`'s `ApprovalDecision`
111
+ * (`ApprovalDecisionSchema`, `core/events/schema.ts`) — this package does
112
+ * not depend on `@truefoundry/trueforge-core` at runtime, so the shape is
113
+ * declared here rather than imported. Confirmed field-for-field against
114
+ * `@truefoundry/trueforge-core@0.1.4`'s shipped `dist/core/events/schema.d.ts`
115
+ * (see module doc).
116
+ */
117
+ export type TrueforgeApprovalDecision = {
118
+ status: 'allow';
119
+ } | {
120
+ status: 'deny';
121
+ reason?: string;
122
+ };
123
+ /**
124
+ * Structural copy of the `user.tool_approval` member of
125
+ * `@truefoundry/trueforge-core`'s `TurnInputItem` union
126
+ * (`UserToolApprovalMessageSchema`, `agent-session/schemas/events.ts`) — the
127
+ * shape `SessionHandle.createTurn({ input })` expects for answering a
128
+ * pending approval. Confirmed field-for-field against the same install.
129
+ */
130
+ export interface TrueforgeUserToolApprovalItem {
131
+ type: 'user.tool_approval';
132
+ thread_id: string;
133
+ tool_call_id: string;
134
+ approval: TrueforgeApprovalDecision;
135
+ }
136
+ export interface IntuticApprovalResponderOptions {
137
+ /** Overrides the process-wide installed gate (`install(new Gate(...))`). */
138
+ gate?: Gate;
139
+ }
140
+ /**
141
+ * Render a pending request's `input` as the `tool_input` object
142
+ * {@link Gate.guard} evaluates. Mirrors `harness.ts`'s/`workflow.ts`'s own
143
+ * coercion: a non-object input (a bare string/number/array — TrueForge's own
144
+ * `model.message` tool-call arguments are typically already an object, but
145
+ * this adapter does not import that wire format to assume so) falls back to
146
+ * `{ args: [...] }` so the gate always has something to evaluate.
147
+ */
148
+ export declare function renderTrueforgeToolInput(input: unknown): Record<string, unknown>;
149
+ /**
150
+ * Build an approval responder: a function that evaluates each pending
151
+ * TrueForge tool-approval request via `Gate.guard()` and produces the
152
+ * `user.tool_approval` input items for your next `createTurn()` call:
153
+ *
154
+ * ```ts
155
+ * import { Gate, install } from '@intutic/gate'
156
+ * import { intuticApprovalResponder } from '@intutic/gate/trueforge'
157
+ *
158
+ * install(new Gate({ workspaceId: process.env.INTUTIC_WORKSPACE_ID }))
159
+ *
160
+ * const respond = intuticApprovalResponder()
161
+ *
162
+ * // ...drive turn.stream() until it pauses on a tool.approval_required
163
+ * // required_action; resolve each {id, source_event_id} back to a real
164
+ * // tool name + arguments (see the module doc), then:
165
+ * const input = await respond(pendingRequests)
166
+ * const nextTurn = await session.createTurn({
167
+ * turn_id: newTurnId(),
168
+ * input,
169
+ * previous_turn_id: pausedTurn.id,
170
+ * signal,
171
+ * resolver,
172
+ * })
173
+ * ```
174
+ *
175
+ * Decision mapping, per request:
176
+ *
177
+ * - gate allows -> `{ status: 'allow' }`
178
+ * - `IntuticGateRefusal` -> `{ status: 'deny', reason }` — the
179
+ * `[Intutic Governance] BLOCKED: ...` message
180
+ * - any OTHER throw -> `{ status: 'deny', reason: '... gate
181
+ * crashed ...' }` — fail CLOSED, same posture as `harness.ts`'s
182
+ * responder: this function's output is a decision list fed into a
183
+ * resumed turn, and a deliverable, auditable deny is better than an
184
+ * escaped throw that leaves the caller with nothing to resume with.
185
+ *
186
+ * Requires a `Gate` — either passed via `{ gate }`, or installed process-wide
187
+ * with `install(new Gate(...))`. A call with neither throws before
188
+ * evaluating anything, refusing to answer approvals unguarded.
189
+ */
190
+ export declare function intuticApprovalResponder(opts?: IntuticApprovalResponderOptions): (requests: readonly TrueforgeApprovalRequest[]) => Promise<TrueforgeUserToolApprovalItem[]>;
191
+ //# sourceMappingURL=trueforge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trueforge.d.ts","sourceRoot":"","sources":["../src/trueforge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AAEH,OAAO,EAAwB,KAAK,IAAI,EAAE,MAAM,WAAW,CAAA;AAG3D;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC;sEACkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB;gCAC4B;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB;sDACkD;IAClD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CACxB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,yBAAyB,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEjG;;;;;;GAMG;AACH,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,oBAAoB,CAAA;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,yBAAyB,CAAA;CACpC;AAED,MAAM,WAAW,+BAA+B;IAC9C,4EAA4E;IAC5E,IAAI,CAAC,EAAE,IAAI,CAAA;CACZ;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKhF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,GAAE,+BAAoC,GACzC,CAAC,QAAQ,EAAE,SAAS,wBAAwB,EAAE,KAAK,OAAO,CAAC,6BAA6B,EAAE,CAAC,CAsC7F"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * TrueForge (embedded) adapter: `intuticApprovalResponder()`.
3
+ *
4
+ * `@intutic/gate/trueforge` — Phase 1 of TrueForge support: another team's
5
+ * Node process that imports `@truefoundry/trueforge-core` directly
6
+ * (github.com/truefoundry/trueforge, MIT) and drives its own
7
+ * `SessionHandle`/`TurnHandle` API in-process. Same family idea as
8
+ * `mastra.ts`/`vercel.ts` (wrap the framework's own veto point around this
9
+ * package's `Gate.guard()`), but TrueForge's veto point is shaped
10
+ * differently from either of those, so this module follows `harness.ts`'s
11
+ * `intuticApprovalResponder()` precedent instead — a batch function
12
+ * answering pending approvals — not a single-call `beforeToolCall`/
13
+ * `toolApproval` option.
14
+ *
15
+ * Phase 2 (`HarnessType.TRUEFORGE_SERVER` — TrueForge run as its own
16
+ * standalone/hosted server, e.g. `npx @truefoundry/trueforge`, Docker
17
+ * Compose, or the Helm chart) is explicitly OUT OF SCOPE here. Nobody embeds
18
+ * an Intutic gate into a third-party OSS server process; that deployment
19
+ * mode needs governance running in an external Intutic-operated bridge
20
+ * service — separate, larger work, not attempted in this module.
21
+ *
22
+ * ## Confirmed against the real published package, not assumed
23
+ *
24
+ * `@truefoundry/trueforge-core@0.1.4` (`latest` on npm at the time this was
25
+ * written) was downloaded from the npm registry and its shipped `.d.ts`
26
+ * files read directly — not inferred from docs alone:
27
+ *
28
+ * - There is NO synchronous, in-process "approval resolver" callback
29
+ * anywhere in the package. Every shipped `.d.ts` file was grepped for
30
+ * `approvalResolver`/`onApproval`/`approvalHandler`/`resolveApproval`:
31
+ * zero matches. `ITurnResourceResolver`
32
+ * (`dist/agent-session/ITurnResourceResolver.d.ts`) resolves sandboxes,
33
+ * agent definitions, and tracing per run — it carries no tool-call veto
34
+ * hook of any kind.
35
+ * - The ONLY approval mechanism, embedded or not, is the same turn/event
36
+ * contract TrueForge's standalone server exposes over HTTP: a turn
37
+ * pauses with a `required_actions` entry of type `tool.approval_required`
38
+ * (`EventType.TOOL_APPROVAL_REQUIRED`, `dist/agent-session/schemas/
39
+ * events.d.ts`), naming each pending call only as `{id, source_event_id}`
40
+ * — no tool name or arguments travel on the event itself. Answering it
41
+ * means constructing a NEW turn (`SessionHandle.createTurn()`) whose
42
+ * `input` carries a `user.tool_approval` item:
43
+ * `{type:'user.tool_approval', thread_id, tool_call_id, approval}`,
44
+ * where `approval` is `{status:'allow'} | {status:'deny', reason?:
45
+ * string}` (`ApprovalDecisionSchema`, `dist/core/events/schema.d.ts`) —
46
+ * confirmed field-for-field, and pinned by this module's own test file
47
+ * via a structural assignability check against the real exported
48
+ * `TurnInputItem` (`@truefoundry/trueforge-core/agent-session`) and
49
+ * `ApprovalDecision` (`@truefoundry/trueforge-core/core`) types.
50
+ *
51
+ * So unlike `mastra.ts`/`vercel.ts`, this adapter cannot BE "the function you
52
+ * pass as a framework option" — embedded TrueForge has no such option. What
53
+ * it can be, and is, is the `harness.ts`-shaped piece: a function that
54
+ * evaluates a batch of pending approval requests through `Gate.guard()` and
55
+ * returns the `user.tool_approval` `TurnInputItem[]` ready to hand to your
56
+ * next `session.createTurn({ input, previous_turn_id })` call.
57
+ *
58
+ * ## What the embedding host still has to do itself
59
+ *
60
+ * Resolving a `{id, source_event_id}` tool-call reference back to the real
61
+ * tool NAME and ARGUMENTS is the host's job, not this adapter's: the host
62
+ * already owns the `TurnHandle`/event stream (`turn.stream()`/
63
+ * `listEvents()`) this package has no reason to depend on, and
64
+ * `source_event_id` points at a `model.message` event whose tool-call wire
65
+ * shape this package deliberately does not import — matching
66
+ * `mastra.ts`/`vercel.ts`'s existing convention of zero runtime dependency
67
+ * on the framework being gated. Build the `TrueforgeApprovalRequest[]` this
68
+ * responder consumes from whatever your own event-resolution loop already
69
+ * extracts, then feed the returned items straight into your next
70
+ * `createTurn()` call.
71
+ *
72
+ * ## GUESSED, clearly marked as such: the exact orchestration wiring
73
+ *
74
+ * The plan that authored this phase asked to pin the exact registration API
75
+ * against `TurnHandle`/`ITurnResourceResolver`; having read both real files,
76
+ * NEITHER carries a registration point for this at all — the orchestration
77
+ * loop (drive `turn.stream()`, detect a `tool.approval_required` required
78
+ * action, resolve each `source_event_id`, call this responder, call
79
+ * `createTurn()` again with the answers) is necessarily HAND-WRITTEN
80
+ * application code in the embedding host, not something a config option
81
+ * turns on. That loop's exact shape — how you persist the paused turn id,
82
+ * how you resolve `source_event_id`, whether you drain `stream()` to
83
+ * completion before or after answering — is a genuine judgment call for the
84
+ * embedding host's own architecture and is NOT prescribed here. See
85
+ * `apps/docs/integrations/trueforge.md` for a worked (illustrative, not
86
+ * load-bearing) example of one way to wire this loop.
87
+ *
88
+ * @module
89
+ */
90
+ import { active as activeGate } from './gate.js';
91
+ import { IntuticGateRefusal } from './errors.js';
92
+ /**
93
+ * Render a pending request's `input` as the `tool_input` object
94
+ * {@link Gate.guard} evaluates. Mirrors `harness.ts`'s/`workflow.ts`'s own
95
+ * coercion: a non-object input (a bare string/number/array — TrueForge's own
96
+ * `model.message` tool-call arguments are typically already an object, but
97
+ * this adapter does not import that wire format to assume so) falls back to
98
+ * `{ args: [...] }` so the gate always has something to evaluate.
99
+ */
100
+ export function renderTrueforgeToolInput(input) {
101
+ if (input !== null && typeof input === 'object' && !Array.isArray(input)) {
102
+ return input;
103
+ }
104
+ return { args: [input] };
105
+ }
106
+ /**
107
+ * Build an approval responder: a function that evaluates each pending
108
+ * TrueForge tool-approval request via `Gate.guard()` and produces the
109
+ * `user.tool_approval` input items for your next `createTurn()` call:
110
+ *
111
+ * ```ts
112
+ * import { Gate, install } from '@intutic/gate'
113
+ * import { intuticApprovalResponder } from '@intutic/gate/trueforge'
114
+ *
115
+ * install(new Gate({ workspaceId: process.env.INTUTIC_WORKSPACE_ID }))
116
+ *
117
+ * const respond = intuticApprovalResponder()
118
+ *
119
+ * // ...drive turn.stream() until it pauses on a tool.approval_required
120
+ * // required_action; resolve each {id, source_event_id} back to a real
121
+ * // tool name + arguments (see the module doc), then:
122
+ * const input = await respond(pendingRequests)
123
+ * const nextTurn = await session.createTurn({
124
+ * turn_id: newTurnId(),
125
+ * input,
126
+ * previous_turn_id: pausedTurn.id,
127
+ * signal,
128
+ * resolver,
129
+ * })
130
+ * ```
131
+ *
132
+ * Decision mapping, per request:
133
+ *
134
+ * - gate allows -> `{ status: 'allow' }`
135
+ * - `IntuticGateRefusal` -> `{ status: 'deny', reason }` — the
136
+ * `[Intutic Governance] BLOCKED: ...` message
137
+ * - any OTHER throw -> `{ status: 'deny', reason: '... gate
138
+ * crashed ...' }` — fail CLOSED, same posture as `harness.ts`'s
139
+ * responder: this function's output is a decision list fed into a
140
+ * resumed turn, and a deliverable, auditable deny is better than an
141
+ * escaped throw that leaves the caller with nothing to resume with.
142
+ *
143
+ * Requires a `Gate` — either passed via `{ gate }`, or installed process-wide
144
+ * with `install(new Gate(...))`. A call with neither throws before
145
+ * evaluating anything, refusing to answer approvals unguarded.
146
+ */
147
+ export function intuticApprovalResponder(opts = {}) {
148
+ return async (requests) => {
149
+ const g = opts.gate ?? activeGate();
150
+ if (g === null) {
151
+ throw new Error('No gate configured: call install(new Gate(...)) from @intutic/gate, or pass ' +
152
+ '{ gate } to intuticApprovalResponder(). Refusing to answer tool approvals unguarded.');
153
+ }
154
+ const items = [];
155
+ for (const req of requests) {
156
+ let approval;
157
+ try {
158
+ await g.guard(req.toolName, renderTrueforgeToolInput(req.input));
159
+ approval = { status: 'allow' };
160
+ }
161
+ catch (exc) {
162
+ if (exc instanceof IntuticGateRefusal) {
163
+ approval = { status: 'deny', reason: exc.message };
164
+ }
165
+ else {
166
+ const detail = exc instanceof Error ? exc.message : String(exc);
167
+ approval = {
168
+ status: 'deny',
169
+ reason: `[Intutic Governance] BLOCKED: gate crashed (${detail}) — failing closed rather ` +
170
+ 'than approving an unevaluated call.',
171
+ };
172
+ }
173
+ }
174
+ items.push({
175
+ type: 'user.tool_approval',
176
+ thread_id: req.threadId,
177
+ tool_call_id: req.toolCallId,
178
+ approval,
179
+ });
180
+ }
181
+ return items;
182
+ };
183
+ }
184
+ //# sourceMappingURL=trueforge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trueforge.js","sourceRoot":"","sources":["../src/trueforge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AAEH,OAAO,EAAE,MAAM,IAAI,UAAU,EAAa,MAAM,WAAW,CAAA;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAkDhD;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzE,OAAO,KAAgC,CAAA;IACzC,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,CAAA;AAC1B,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAwC,EAAE;IAE1C,OAAO,KAAK,EAAE,QAAQ,EAAE,EAAE;QACxB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,UAAU,EAAE,CAAA;QACnC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,8EAA8E;gBAC5E,sFAAsF,CACzF,CAAA;QACH,CAAC;QAED,MAAM,KAAK,GAAoC,EAAE,CAAA;QACjD,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;YAC3B,IAAI,QAAmC,CAAA;YACvC,IAAI,CAAC;gBACH,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,wBAAwB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;gBAChE,QAAQ,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAA;YAChC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,GAAG,YAAY,kBAAkB,EAAE,CAAC;oBACtC,QAAQ,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,CAAA;gBACpD,CAAC;qBAAM,CAAC;oBACN,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;oBAC/D,QAAQ,GAAG;wBACT,MAAM,EAAE,MAAM;wBACd,MAAM,EACJ,+CAA+C,MAAM,4BAA4B;4BACjF,qCAAqC;qBACxC,CAAA;gBACH,CAAC;YACH,CAAC;YACD,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,oBAAoB;gBAC1B,SAAS,EAAE,GAAG,CAAC,QAAQ;gBACvB,YAAY,EAAE,GAAG,CAAC,UAAU;gBAC5B,QAAQ;aACT,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC,CAAA;AACH,CAAC"}