@khoavu882/doflow 2.0.0-beta.4 → 2.0.0-beta.5
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/core/.antigravity-plugin/plugin.json +0 -1
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
- package/core/harnesses/gemini/hooks/session-start.sh +5 -3
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
- package/core/harnesses/kiro/hooks/session-start.sh +11 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
- package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
- package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
- package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
- package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
- package/core/registry/assets.json +19 -0
- package/core/registry/contracts.json +2 -2
- package/core/registry/harnesses.json +196 -10
- package/core/registry/lifecycle.json +2626 -7
- package/core/registry/storage.json +24 -1
- package/core/shared/scripts/doflow/bin/doflow-run +10 -6
- package/core/shared/skills/do-design/SKILL.md +6 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
- package/core/shared/skills/do-implement/SKILL.md +3 -2
- package/core/shared/skills/do-plan/SKILL.md +7 -8
- package/package.json +1 -1
- package/src/adapters/antigravity/index.js +83 -47
- package/src/adapters/claude/index.js +15 -1
- package/src/adapters/codex/hooks.js +40 -5
- package/src/adapters/codex/index.js +52 -9
- package/src/adapters/copy-tree.js +4 -4
- package/src/adapters/gemini/hooks.js +57 -16
- package/src/adapters/gemini/index.js +57 -18
- package/src/adapters/kiro/index.js +15 -1
- package/src/adapters/pi/hooks.js +119 -0
- package/src/adapters/pi/index.js +34 -9
- package/src/cli/commands/install.js +1 -1
- package/src/cli/commands/update.js +1 -1
- package/src/cli/index.js +17 -5
- package/src/cli/runtime-commands.js +51 -12
- package/src/lifecycle/coverage.js +57 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/index.js +28 -14
- package/src/registry/index.js +136 -7
- package/src/runtime/candidate.js +505 -0
- package/src/runtime/claims.js +14 -5
- package/src/runtime/cli-result.js +11 -4
- package/src/runtime/cli.js +35 -268
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +102 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context-pack.js +1029 -244
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/evidence-ledger.js +7 -0
- package/src/runtime/freshness.js +44 -7
- package/src/runtime/outcome.js +1 -1
- package/src/runtime/readiness.js +83 -13
- package/src/runtime/retrieval-plan.js +422 -73
- package/src/runtime/retrieval-record.js +223 -0
- package/src/runtime/scaffold/artifacts.js +3 -0
- package/src/runtime/source-version.js +289 -0
- package/src/runtime/task-attempts.js +1 -1
- package/src/runtime/task-scope.js +28 -3
- package/src/runtime/task-state.js +72 -7
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
// C.3's renderer replaces this relative source-tree path for each install scope.
|
|
6
|
+
const BRIDGE = new URL('../../shared/hooks/bridge/bridge.sh', import.meta.url);
|
|
7
|
+
const ROUTES = [
|
|
8
|
+
{ tools: ['edit', 'write'], policy: 'pre-implementation-gate' },
|
|
9
|
+
{ tools: ['bash'], policy: 'pre-bash-guard' },
|
|
10
|
+
];
|
|
11
|
+
const LIMIT = 1024 * 1024;
|
|
12
|
+
const TIMEOUT_MS = 5000;
|
|
13
|
+
const ERROR_REASON = (policy) => `[${policy}] DoFlow policy error; details: doflow-run failure list`;
|
|
14
|
+
|
|
15
|
+
function route(toolName) {
|
|
16
|
+
for (const row of ROUTES) {
|
|
17
|
+
if (row.tools.includes(toolName)) return row.policy;
|
|
18
|
+
}
|
|
19
|
+
return typeof toolName === 'string' && toolName.startsWith('mcp__') ? 'mcp-tool-guard' : undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function captureFailure(policy, point) {
|
|
23
|
+
const script = new URL('../policies/capture-failure.sh', BRIDGE);
|
|
24
|
+
try {
|
|
25
|
+
const child = spawn('bash', ['-c', '. "$1"; doflow_capture_failure "$2" "$3"', 'doflow-capture', fileURLToPath(script), policy, point], {
|
|
26
|
+
detached: true,
|
|
27
|
+
stdio: 'ignore',
|
|
28
|
+
});
|
|
29
|
+
child.unref();
|
|
30
|
+
} catch {
|
|
31
|
+
// Failure capture must never change Pi's tool-call decision.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function bridgeCall(policy, payload, ctx) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
let child;
|
|
38
|
+
try {
|
|
39
|
+
const env = { ...process.env };
|
|
40
|
+
delete env.DOFLOW_PROJECT_DIR;
|
|
41
|
+
child = spawn('bash', [fileURLToPath(BRIDGE), 'pi', 'tool_call', policy], {
|
|
42
|
+
cwd: ctx.cwd,
|
|
43
|
+
env,
|
|
44
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
45
|
+
detached: true,
|
|
46
|
+
});
|
|
47
|
+
} catch (error) {
|
|
48
|
+
reject(Object.assign(error, { failurePoint: 'bridge-spawn' }));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const stdout = [];
|
|
53
|
+
let stdoutBytes = 0;
|
|
54
|
+
let stderrBytes = 0;
|
|
55
|
+
let settled = false;
|
|
56
|
+
let timedOut = false;
|
|
57
|
+
let overflow = false;
|
|
58
|
+
let aborted = false;
|
|
59
|
+
const finish = (error, result) => {
|
|
60
|
+
if (settled) return;
|
|
61
|
+
settled = true;
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
ctx.signal?.removeEventListener('abort', onAbort);
|
|
64
|
+
if (error) reject(error);
|
|
65
|
+
else resolve(result);
|
|
66
|
+
};
|
|
67
|
+
const kill = () => {
|
|
68
|
+
try {
|
|
69
|
+
if (child.pid && process.platform !== 'win32') process.kill(-child.pid, 'SIGKILL');
|
|
70
|
+
else child.kill('SIGKILL');
|
|
71
|
+
} catch {
|
|
72
|
+
try { child.kill('SIGKILL'); } catch {}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const fail = (point) => {
|
|
76
|
+
overflow = point === 'bridge-output-limit';
|
|
77
|
+
kill();
|
|
78
|
+
finish(Object.assign(new Error(point), { failurePoint: point }));
|
|
79
|
+
};
|
|
80
|
+
const onAbort = () => {
|
|
81
|
+
aborted = true;
|
|
82
|
+
kill();
|
|
83
|
+
finish(null, undefined);
|
|
84
|
+
};
|
|
85
|
+
const timer = setTimeout(() => {
|
|
86
|
+
timedOut = true;
|
|
87
|
+
kill();
|
|
88
|
+
}, TIMEOUT_MS);
|
|
89
|
+
if (ctx.signal?.aborted) onAbort();
|
|
90
|
+
else ctx.signal?.addEventListener('abort', onAbort, { once: true });
|
|
91
|
+
|
|
92
|
+
child.on('error', (error) => finish(Object.assign(error, { failurePoint: 'bridge-spawn' })));
|
|
93
|
+
child.stdout.on('data', (chunk) => {
|
|
94
|
+
stdoutBytes += chunk.length;
|
|
95
|
+
if (stdoutBytes > LIMIT) return fail('bridge-output-limit');
|
|
96
|
+
stdout.push(chunk);
|
|
97
|
+
});
|
|
98
|
+
child.stderr.on('data', (chunk) => {
|
|
99
|
+
stderrBytes += chunk.length;
|
|
100
|
+
if (stderrBytes > LIMIT) return fail('bridge-output-limit');
|
|
101
|
+
});
|
|
102
|
+
child.on('close', (code) => {
|
|
103
|
+
if (settled) return;
|
|
104
|
+
if (aborted) return finish(null, undefined);
|
|
105
|
+
if (timedOut) return finish(Object.assign(new Error('bridge timed out'), { failurePoint: 'bridge-timeout' }));
|
|
106
|
+
if (overflow) return finish(Object.assign(new Error('bridge output exceeded limit'), { failurePoint: 'bridge-output-limit' }));
|
|
107
|
+
if (code !== 0) return finish(Object.assign(new Error(`bridge exited ${code}`), { failurePoint: 'bridge-spawn' }));
|
|
108
|
+
const output = Buffer.concat(stdout).toString('utf8');
|
|
109
|
+
if (!output.trim()) return finish(null, undefined);
|
|
110
|
+
if (output.trim().split(/\r?\n/).length !== 1) {
|
|
111
|
+
return finish(Object.assign(new Error('bridge answer must be one JSON line'), { failurePoint: 'bridge-spawn' }));
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const answer = JSON.parse(output);
|
|
115
|
+
if (!answer || typeof answer !== 'object' || Array.isArray(answer) || answer.block !== true || typeof answer.reason !== 'string') {
|
|
116
|
+
throw new Error('invalid bridge answer');
|
|
117
|
+
}
|
|
118
|
+
finish(null, { block: true, reason: answer.reason });
|
|
119
|
+
} catch {
|
|
120
|
+
finish(Object.assign(new Error('invalid bridge answer'), { failurePoint: 'bridge-spawn' }));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
child.stdin.on('error', () => {});
|
|
124
|
+
child.stdin.end(payload);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export default function (pi) {
|
|
129
|
+
pi.on('tool_call', async (event, ctx) => {
|
|
130
|
+
let policy = 'unknown';
|
|
131
|
+
let profile = process.env.DOFLOW_FAULT_PROFILE || 'compat-1';
|
|
132
|
+
try {
|
|
133
|
+
policy = route(event.toolName);
|
|
134
|
+
if (!policy) return undefined;
|
|
135
|
+
profile = process.env.DOFLOW_FAULT_PROFILE || 'compat-1';
|
|
136
|
+
if (profile !== 'compat-1' && profile !== 'strict-1') captureFailure(policy, 'fault-profile-unknown');
|
|
137
|
+
if (!existsSync(BRIDGE)) {
|
|
138
|
+
captureFailure(policy, 'bridge-missing');
|
|
139
|
+
return { block: true, reason: '[doflow] shared hooks lack the bridge; run: doflow update -t pi' };
|
|
140
|
+
}
|
|
141
|
+
const payload = JSON.stringify({
|
|
142
|
+
type: event.type,
|
|
143
|
+
toolName: event.toolName,
|
|
144
|
+
toolCallId: event.toolCallId,
|
|
145
|
+
parentToolCallId: event.parentToolCallId,
|
|
146
|
+
input: event.input,
|
|
147
|
+
cwd: ctx.cwd,
|
|
148
|
+
sessionId: ctx.sessionManager?.getSessionId?.() ?? null,
|
|
149
|
+
});
|
|
150
|
+
if (Buffer.byteLength(payload) > LIMIT) throw Object.assign(new Error('bridge input exceeded limit'), { failurePoint: 'bridge-input-limit' });
|
|
151
|
+
return await bridgeCall(policy, payload, ctx);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const point = error?.failurePoint || 'bridge-spawn';
|
|
154
|
+
captureFailure(policy, point === 'bridge-timeout' || point === 'bridge-missing' ? point : 'bridge-spawn');
|
|
155
|
+
if (profile === 'strict-1') return { block: true, reason: ERROR_REASON(policy) };
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
# The normalized hook envelope, and each front door's share of the migration
|
|
2
2
|
|
|
3
|
+
Every shipped bridge places envelope version 1 under `doflow_event`, beside the native fields. Policies read v1 fields only when `version` is the JSON number `1`; any other version is unsupported and is evaluated as such (IC-034, DEC-030).
|
|
4
|
+
|
|
3
5
|
`policies/pre-implementation-gate.sh` prefers one normalized event over every native payload
|
|
4
6
|
shape (review R7):
|
|
5
7
|
|
|
6
8
|
```json
|
|
7
|
-
{"doflow_event": {"operation": "edit", "paths": ["src/a.js"], "projectRoot": "/repo", "taskId": "001-auth"}}
|
|
9
|
+
{"doflow_event": {"version": 1, "harness": "claude", "event": "PreToolUse", "operation": "edit", "paths": ["src/a.js"], "projectRoot": "/repo", "taskId": "001-auth"}}
|
|
8
10
|
```
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
retires its share of that union; when the last one does, the union decoder is deleted.
|
|
12
|
+
The envelope is the only policy input contract. A caller outside a shipped bridge must either
|
|
13
|
+
send a v1 envelope itself or pipe its native payload through
|
|
14
|
+
`bash .doflow/shared/hooks/bridge/bridge.sh <harness> <event> <policy>`.
|
|
14
15
|
|
|
15
16
|
## Which native event each front door decodes
|
|
16
17
|
|
|
@@ -24,16 +25,31 @@ tool's official docs (cross-checked against tool source where docs were incomple
|
|
|
24
25
|
| Gemini CLI | `BeforeTool` | also offers `BeforeToolSelection` (pre-selection restriction), which the gate does not use |
|
|
25
26
|
| GitHub Copilot | `preToolUse` | camelCase event names; `permissionRequest` is CLI-only, not fired in the cloud agent |
|
|
26
27
|
| OpenCode | `tool.execute.before` | a pluggable hook method, not a sub-event of the generic `event` hook |
|
|
27
|
-
| Pi | `
|
|
28
|
+
| Pi | `tool_call` | The live-observed `cli-extension` pre-implementation gate row blocks. Project-interactive and user-interactive rows remain unknown; interactive loading and project-trust behavior were not observed. `tool_execution_start` is observation-only |
|
|
28
29
|
| Antigravity | `PreToolUse` | 5-event surface, `hooks.json` + stdin/stdout JSON contract |
|
|
29
30
|
|
|
31
|
+
Pi 1.1.0 declares 41 `on()` overloads in its shipped extension types. `tool_call` is the enforcement event; `tool_execution_start` is not a gate.
|
|
32
|
+
|
|
30
33
|
`operation` is `"edit"` for anything that writes source; a decoder maps its native create/replace/
|
|
31
34
|
patch tool vocabulary down to that one word. Non-edit operations may be sent (the policy allows
|
|
32
35
|
them through), but sending nothing for a non-edit tool is equally correct and cheaper.
|
|
33
36
|
|
|
34
|
-
##
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
+
## Version and mixed-install rules
|
|
38
|
+
|
|
39
|
+
- A missing envelope or version, empty or non-object input, or a missing `contract.sh` is out of
|
|
40
|
+
step. The gate, bash guard and MCP guard deny with `DoFlow hooks out of step; run: doflow update
|
|
41
|
+
-t <harness>`; Stop and context policies exit silently, except the prompt hook emits its `{}`
|
|
42
|
+
no-op. Other envelope versions are unsupported and policies evaluate nothing.
|
|
43
|
+
- If a door cannot load `bridge/bridge.sh`, it renders its fixed native out-of-step response. The
|
|
44
|
+
Pi extension blocks with the fixed native out-of-step reason; a present bridge that fails to spawn or answer follows
|
|
45
|
+
the configured bridge-fault behavior.
|
|
46
|
+
|
|
47
|
+
Other current bridge behavior: Codex Stop crash stderr is suppressed (DEC-037). Stop-check
|
|
48
|
+
enforcement is answered from Stop coverage rows (DEC-038). Strict-1 never fault-blocks Stop, though
|
|
49
|
+
a real Stop deny still blocks (DEC-039). Bridge capture uses no temporary file, so an unwritable
|
|
50
|
+
`TMPDIR` does not turn a gate deny into an allow; background jobs do not hold Stop or SessionStart
|
|
51
|
+
open; strict-1 renders native denies without `jq`. `doflow update -t codex -f` re-merges edited
|
|
52
|
+
Codex `hooks.json` while keeping user entries (DEC-040).
|
|
37
53
|
- Root resolution: `DOFLOW_PROJECT_DIR` beats the envelope's `projectRoot` beats git discovery.
|
|
38
|
-
- The
|
|
39
|
-
|
|
54
|
+
- The pre-implementation gate checks required artifacts and reads run and readiness records; it
|
|
55
|
+
does not consult evidence state.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Antigravity
|
|
5
|
-
*
|
|
4
|
+
* Antigravity native decision renderer for the Cross-Harness Hook Runner's
|
|
5
|
+
* Decision Adapter seam, D2). The shared bridge owns native payload decoding.
|
|
6
6
|
*
|
|
7
7
|
* Antigravity's own native contracts — evidenced by core/harnesses/antigravity/hooks/*.sh (the
|
|
8
8
|
* shims src/adapters/antigravity/index.js actually projects today) and
|
|
@@ -19,67 +19,24 @@
|
|
|
19
19
|
* vocabulary than PreToolUse's allow/deny/ask/force_ask, not an oversight.
|
|
20
20
|
*
|
|
21
21
|
* SessionStart and PostToolUse have no dedicated Antigravity-native shim in
|
|
22
|
-
* src/adapters/antigravity/index.js's live projection
|
|
23
|
-
* separate, read-only-to-this-task artifact) documents both routed directly to the *Gemini*
|
|
24
|
-
* session-start.sh / post-edit-lint.sh scripts, which read the Claude/Gemini-style
|
|
25
|
-
* session_id/cwd/tool_name/tool_input field names, not the toolCall envelope above — this
|
|
26
|
-
* adapter honors that evidence for those two events rather than guessing at a toolCall shape
|
|
27
|
-
* with no corroborating source. No Phase A canonical policy owns PostToolUse today (only
|
|
22
|
+
* src/adapters/antigravity/index.js's live projection. No Phase A canonical policy owns PostToolUse today (only
|
|
28
23
|
* session-context, pre-implementation-gate, mcp-tool-guard, stop-check, pre-bash-guard exist),
|
|
29
24
|
* so its native shape is an ack passthrough either way.
|
|
30
25
|
*/
|
|
31
26
|
|
|
32
|
-
const { FILE_PATH_ARG_FIELDS, COMMAND_ARG_FIELDS, firstArgField } = require('./arg-fields');
|
|
33
|
-
|
|
34
|
-
function toCanonical(event, payload) {
|
|
35
|
-
const safePayload = payload && typeof payload === 'object' ? payload : {};
|
|
36
|
-
|
|
37
|
-
switch (event) {
|
|
38
|
-
case 'SessionStart':
|
|
39
|
-
return { session_id: safePayload.session_id || '', cwd: safePayload.cwd || '' };
|
|
40
|
-
|
|
41
|
-
case 'PreToolUse': {
|
|
42
|
-
const toolCall = safePayload.toolCall || {};
|
|
43
|
-
const args = toolCall.args || {};
|
|
44
|
-
return {
|
|
45
|
-
tool_name: toolCall.name || '',
|
|
46
|
-
tool_input: {
|
|
47
|
-
file_path: firstArgField(args, FILE_PATH_ARG_FIELDS),
|
|
48
|
-
command: firstArgField(args, COMMAND_ARG_FIELDS),
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
case 'PostToolUse':
|
|
54
|
-
// No Phase A policy consumes this event — canonical shape kept for symmetry only.
|
|
55
|
-
return {
|
|
56
|
-
tool_name: safePayload.tool_name || '',
|
|
57
|
-
session_id: safePayload.session_id || '',
|
|
58
|
-
tool_input: { file_path: (safePayload.tool_input && safePayload.tool_input.file_path) || '' },
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
case 'Stop':
|
|
62
|
-
return {
|
|
63
|
-
session_id: safePayload.session_id || '',
|
|
64
|
-
transcript_path: safePayload.transcriptPath || safePayload.transcript_path || '',
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
case 'PreInvocation':
|
|
68
|
-
// No Phase A policy — ack only (injectSteps handled entirely in toNative).
|
|
69
|
-
return {};
|
|
70
|
-
|
|
71
|
-
default:
|
|
72
|
-
return safePayload;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
27
|
function toNative(event, decision) {
|
|
77
28
|
const safeDecision = decision || { decision: 'allow' };
|
|
29
|
+
const blocked = safeDecision.effect === 'block' || safeDecision.decision === 'deny';
|
|
30
|
+
const reason = safeDecision.outcome === 'deny'
|
|
31
|
+
? safeDecision.reason
|
|
32
|
+
: safeDecision.effect === 'block'
|
|
33
|
+
? `[${safeDecision.policy}] DoFlow policy error; details: doflow-run failure list`
|
|
34
|
+
: safeDecision.reason;
|
|
78
35
|
|
|
79
36
|
switch (event) {
|
|
80
37
|
case 'PreToolUse':
|
|
81
|
-
if (
|
|
82
|
-
return { decision: 'deny', reason
|
|
38
|
+
if (blocked) {
|
|
39
|
+
return { decision: 'deny', reason };
|
|
83
40
|
}
|
|
84
41
|
return { decision: 'allow' };
|
|
85
42
|
|
|
@@ -88,8 +45,8 @@ function toNative(event, decision) {
|
|
|
88
45
|
return {};
|
|
89
46
|
|
|
90
47
|
case 'Stop':
|
|
91
|
-
if (
|
|
92
|
-
return { decision: 'continue', reason
|
|
48
|
+
if (blocked) {
|
|
49
|
+
return { decision: 'continue', reason };
|
|
93
50
|
}
|
|
94
51
|
// Silence (no positive field) lets the session stop, per Antigravity's own documented
|
|
95
52
|
// Stop contract — not the allow/deny vocabulary PreToolUse uses.
|
|
@@ -108,4 +65,4 @@ function toNative(event, decision) {
|
|
|
108
65
|
}
|
|
109
66
|
}
|
|
110
67
|
|
|
111
|
-
module.exports = {
|
|
68
|
+
module.exports = { toNative };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared lifecycle bridge. Sourcing defines functions only; it sets no shell options or traps.
|
|
3
|
+
case ${BASH_SOURCE[0]} in */*) _DOFLOW_BRIDGE_DIR=${BASH_SOURCE[0]%/*} ;; *) _DOFLOW_BRIDGE_DIR=. ;; esac
|
|
4
|
+
|
|
5
|
+
doflow_bridge_failure() {
|
|
6
|
+
local policy="$1" point="$2"
|
|
7
|
+
( . "$_DOFLOW_BRIDGE_DIR/../policies/capture-failure.sh"; doflow_capture_failure "$policy" "$point" ) >/dev/null 2>&1 || true
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
doflow_bridge_classify() {
|
|
11
|
+
local code="$1" record="$2" outcome="" decision_code="" version=0 reason="$3"
|
|
12
|
+
if [ -n "$record" ]; then
|
|
13
|
+
outcome="${record#*\"outcome\":\"}"; outcome="${outcome%%\"*}"
|
|
14
|
+
decision_code="${record#*\"code\":\"}"; decision_code="${decision_code%%\"*}"
|
|
15
|
+
version="${record#*\"policyVersion\":}"; version="${version%%,*}"
|
|
16
|
+
case "$version" in ''|*[^0-9]*) version=0;; esac
|
|
17
|
+
fi
|
|
18
|
+
DF_BRIDGE_OUTCOME=error; DF_BRIDGE_CODE=exit-"$code"; DF_BRIDGE_VERSION="$version"; DF_BRIDGE_REASON="$DF_BRIDGE_CODE"; DF_BRIDGE_POINT="eval-error:exit-$code"
|
|
19
|
+
if [ "$code" -eq 2 ] 2>/dev/null; then
|
|
20
|
+
if [ "$outcome" = deny ]; then DF_BRIDGE_OUTCOME=deny; DF_BRIDGE_CODE="$decision_code"; DF_BRIDGE_REASON="$reason"; DF_BRIDGE_POINT=""
|
|
21
|
+
else DF_BRIDGE_CODE=exit-2-unmarked; DF_BRIDGE_REASON=exit-2-unmarked; DF_BRIDGE_POINT=eval-error:exit-2-unmarked; fi
|
|
22
|
+
elif [ "$code" -eq 0 ] 2>/dev/null; then
|
|
23
|
+
case "$outcome" in
|
|
24
|
+
allow|advisory|error|unsupported) DF_BRIDGE_OUTCOME="$outcome"; DF_BRIDGE_CODE="$decision_code"; DF_BRIDGE_REASON="$decision_code"; DF_BRIDGE_POINT="" ;;
|
|
25
|
+
deny) DF_BRIDGE_OUTCOME=allow; DF_BRIDGE_CODE=decision-mismatch; DF_BRIDGE_REASON=decision-mismatch; DF_BRIDGE_POINT=decision-mismatch ;;
|
|
26
|
+
*) DF_BRIDGE_OUTCOME=allow; DF_BRIDGE_CODE=allow; DF_BRIDGE_REASON=allow; DF_BRIDGE_POINT=""; DF_BRIDGE_VERSION=0 ;;
|
|
27
|
+
esac
|
|
28
|
+
elif [ "$code" -ge 129 ] && [ "$code" -le 255 ] && [ "$code" -ne 255 ] 2>/dev/null; then
|
|
29
|
+
local sig=""
|
|
30
|
+
sig="$(kill -l "$((code - 128))" 2>/dev/null)" || sig=""
|
|
31
|
+
sig="${sig#SIG}"
|
|
32
|
+
case "$sig" in ''|*[!A-Za-z0-9_-]*) return;; esac
|
|
33
|
+
DF_BRIDGE_OUTCOME=error; DF_BRIDGE_CODE=signal-"$sig"; DF_BRIDGE_REASON="$DF_BRIDGE_CODE"; DF_BRIDGE_POINT="eval-error:signal-$sig"
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
doflow_bridge_effect() {
|
|
38
|
+
local harness="$1" event="$2" policy="$3" outcome="$4" profile="${DOFLOW_FAULT_PROFILE:-compat-1}" block=0
|
|
39
|
+
doflow_bridge_native_event_var "$harness" "$event"; event=$DF_NE
|
|
40
|
+
if [ "$profile" != compat-1 ] && [ "$profile" != strict-1 ]; then
|
|
41
|
+
profile=compat-1; doflow_bridge_failure "$policy" fault-profile-unknown
|
|
42
|
+
fi
|
|
43
|
+
DF_BRIDGE_PROFILE="$profile"
|
|
44
|
+
case "$policy" in pre-implementation-gate|pre-bash-guard|mcp-tool-guard|stop-check) block=1;; esac
|
|
45
|
+
DF_BRIDGE_EFFECT=proceed
|
|
46
|
+
if [ "$outcome" = deny ]; then DF_BRIDGE_EFFECT=block
|
|
47
|
+
elif [ "$profile" = strict-1 ] && [ "$block" -eq 1 ] && [ "$policy" != stop-check ] && { [ "$outcome" = error ] || [ "$outcome" = unsupported ] || [ "$DF_BRIDGE_CODE" = bridge-spawn ] || [ "$DF_BRIDGE_CODE" = envelope-encode ]; }; then DF_BRIDGE_EFFECT=block
|
|
48
|
+
elif [ "$outcome" = error ] && [ "$DF_BRIDGE_CODE" != bridge-spawn ] && [ -n "$DF_BRIDGE_POINT" ] && [ "$profile" = compat-1 ]; then
|
|
49
|
+
case "$harness:$event" in
|
|
50
|
+
claude:PreToolUse|codex:PreToolUse|gemini:BeforeTool|pi:tool_call) DF_BRIDGE_EFFECT=block ;;
|
|
51
|
+
antigravity:PreToolUse|antigravity:Stop) case "$DF_BRIDGE_CODE" in signal-*) DF_BRIDGE_EFFECT=proceed;; *) DF_BRIDGE_EFFECT=block;; esac ;;
|
|
52
|
+
kiro:PreToolUse) [ "$DF_BRIDGE_CODE" = exit-2-unmarked ] && DF_BRIDGE_EFFECT=block ;;
|
|
53
|
+
claude:Stop) [ "$DF_BRIDGE_CODE" = exit-2-unmarked ] && DF_BRIDGE_EFFECT=block ;;
|
|
54
|
+
codex:Stop|codex:stop|kiro:Stop|kiro:stop) [ "$DF_BRIDGE_CODE" = exit-2-unmarked ] && DF_BRIDGE_EFFECT=block || DF_BRIDGE_EFFECT=proceed ;;
|
|
55
|
+
esac
|
|
56
|
+
fi
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
doflow_bridge_drops_tool_streams() {
|
|
60
|
+
case "$1" in
|
|
61
|
+
pre-implementation-gate|pre-bash-guard|mcp-tool-guard) ;;
|
|
62
|
+
*) return 1 ;;
|
|
63
|
+
esac
|
|
64
|
+
case "$2:$3" in
|
|
65
|
+
claude:PreToolUse|codex:PreToolUse|gemini:BeforeTool|kiro:PreToolUse|antigravity:PreToolUse|antigravity:Stop|pi:tool_call) return 0 ;;
|
|
66
|
+
*) return 1 ;;
|
|
67
|
+
esac
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
doflow_bridge_drops_tool_stderr() {
|
|
71
|
+
case "$1:$2:$3" in
|
|
72
|
+
pre-implementation-gate:kiro:PreToolUse|pre-bash-guard:kiro:PreToolUse|mcp-tool-guard:kiro:PreToolUse) return 1 ;;
|
|
73
|
+
esac
|
|
74
|
+
doflow_bridge_drops_tool_streams "$1" "$2" "$3"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
doflow_bridge() {
|
|
78
|
+
local harness="${1:-}" event="${2:-}" policy="${3:-}" response_event="" emit="native" payload="" encoded="" policy_path="" captured="" err="" code=0 policy_code=0 record="" reason="" decision_reason="" stdout="" status=0 decision="" line="" spawn_failed=0 encode_failed=0
|
|
79
|
+
. "$_DOFLOW_BRIDGE_DIR/respond.sh"
|
|
80
|
+
doflow_bridge_native_event_var "$harness" "$event"; response_event=$DF_NE
|
|
81
|
+
if [ "${4:-}" = --emit ]; then emit="${5:-native}"; fi
|
|
82
|
+
payload="$(cat)"
|
|
83
|
+
if command -v jq >/dev/null 2>&1; then
|
|
84
|
+
encoded="$(printf '%s\n' "$payload" | DOFLOW_TASK_ID="${DOFLOW_TASK_ID:-}" DOFLOW_RUN_ID="${DOFLOW_RUN_ID:-}" DOFLOW_ATTEMPT="${DOFLOW_ATTEMPT:-}" DOFLOW_SESSION_ROLE="${DOFLOW_SESSION_ROLE:-}" jq -c --arg harness "$harness" --arg event "$event" --arg policy "$policy" --arg mode encode -f "$_DOFLOW_BRIDGE_DIR/envelope.jq" 2>/dev/null)" && {
|
|
85
|
+
case "$encoded" in \{*) payload="$encoded" ;; *) encode_failed=1 ;; esac
|
|
86
|
+
} || { encode_failed=1; }
|
|
87
|
+
if [ "$encode_failed" -eq 1 ]; then
|
|
88
|
+
encoded="$(jq -cn --arg harness "$harness" --arg event "$event" --arg policy "$policy" --arg mode minimal -f "$_DOFLOW_BRIDGE_DIR/envelope.jq" 2>/dev/null)" || encoded=""
|
|
89
|
+
case "$encoded" in \{*) payload="$encoded" ;; *) payload="{\"doflow_event\":{\"version\":1,\"harness\":\"$harness\",\"event\":\"$event\",\"operation\":\"tool\"}}" ;; esac
|
|
90
|
+
doflow_bridge_failure "$policy" envelope-encode
|
|
91
|
+
fi
|
|
92
|
+
else
|
|
93
|
+
doflow_bridge_failure "$policy" dependency-missing:jq
|
|
94
|
+
fi
|
|
95
|
+
case "$policy" in
|
|
96
|
+
pre-implementation-gate|pre-bash-guard|mcp-tool-guard|stop-check|session-context|user-prompt-submit|subagent-audit) ;;
|
|
97
|
+
*) spawn_failed=1 ;;
|
|
98
|
+
esac
|
|
99
|
+
policy_path="$_DOFLOW_BRIDGE_DIR/../policies/$policy.sh"
|
|
100
|
+
if [ "$spawn_failed" -eq 1 ] || [ ! -f "$policy_path" ]; then
|
|
101
|
+
code=127; err=""; spawn_failed=1
|
|
102
|
+
else
|
|
103
|
+
if [ "$emit" = decision ] || doflow_bridge_drops_tool_streams "$policy" "$harness" "$response_event" || { [ "$harness" = kiro ] && [ "$response_event" = Stop ]; }; then exec 5>/dev/null; else exec 5>&1; fi
|
|
104
|
+
captured="$( {
|
|
105
|
+
if DOFLOW_AGENT="${DOFLOW_AGENT:-$harness}" DOFLOW_DECISION_FD=3 bash "$policy_path" 2>&1 3>&1 1>&5 5>&- <<<"$payload"; then policy_code=0; else policy_code=$?; fi
|
|
106
|
+
printf '\037%s' "$policy_code"
|
|
107
|
+
} )"
|
|
108
|
+
exec 5>&-
|
|
109
|
+
code="${captured##*$'\037'}"
|
|
110
|
+
captured="${captured%$'\037'*}"
|
|
111
|
+
fi
|
|
112
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
113
|
+
case "$line" in
|
|
114
|
+
*$'\036'*)
|
|
115
|
+
local prefix="${line%%$'\036'*}"
|
|
116
|
+
record="${line#*$'\036'}"
|
|
117
|
+
[ -z "$prefix" ] || err="$err$prefix"$'\n'
|
|
118
|
+
;;
|
|
119
|
+
*) err="$err$line"$'\n' ;;
|
|
120
|
+
esac
|
|
121
|
+
done < <(printf '%s' "$captured")
|
|
122
|
+
reason="${err%$'\n'}"
|
|
123
|
+
doflow_bridge_classify "$code" "$record" "$reason"
|
|
124
|
+
if [ "$encode_failed" -eq 1 ] && [ "$DF_BRIDGE_OUTCOME" != deny ]; then DF_BRIDGE_CODE=envelope-encode; DF_BRIDGE_POINT=envelope-encode; fi
|
|
125
|
+
if [ "$spawn_failed" -eq 1 ]; then DF_BRIDGE_OUTCOME=error; DF_BRIDGE_CODE=bridge-spawn; DF_BRIDGE_REASON=bridge-spawn; DF_BRIDGE_VERSION=0; DF_BRIDGE_POINT=bridge-spawn; fi
|
|
126
|
+
if [ -n "$DF_BRIDGE_POINT" ] && [ "$DF_BRIDGE_POINT" != decision-mismatch ] && [ "$DF_BRIDGE_POINT" != eval-error:signal-TERM ] && [ "$DF_BRIDGE_POINT" != eval-error:signal-INT ] && [ "$DF_BRIDGE_POINT" != eval-error:signal-PIPE ]; then doflow_bridge_failure "$policy" "$DF_BRIDGE_POINT"; fi
|
|
127
|
+
doflow_bridge_effect "$harness" "$event" "$policy" "$DF_BRIDGE_OUTCOME"
|
|
128
|
+
if [ "$DF_BRIDGE_EFFECT" = block ] && [ "$DF_BRIDGE_OUTCOME" != deny ]; then reason="[$policy] DoFlow policy error; details: doflow-run failure list"; fi
|
|
129
|
+
if [ "$DF_BRIDGE_OUTCOME" != deny ] && [ "$DF_BRIDGE_EFFECT" = proceed ]; then
|
|
130
|
+
case "$harness:$response_event:$DF_BRIDGE_OUTCOME" in
|
|
131
|
+
kiro:PreToolUse:error|claude:Stop:error)
|
|
132
|
+
case "$DF_BRIDGE_CODE" in
|
|
133
|
+
exit-*|signal-*)
|
|
134
|
+
reason="[$policy] DoFlow policy error; details: doflow-run failure list"
|
|
135
|
+
if [ "$harness:$response_event" = claude:Stop ]; then reason="$reason"$'\n'; fi
|
|
136
|
+
;;
|
|
137
|
+
esac
|
|
138
|
+
;;
|
|
139
|
+
*) reason="$DF_BRIDGE_CODE" ;;
|
|
140
|
+
esac
|
|
141
|
+
fi
|
|
142
|
+
decision_reason="$DF_BRIDGE_CODE"
|
|
143
|
+
if [ "$DF_BRIDGE_OUTCOME" = deny ]; then decision_reason="$reason"; fi
|
|
144
|
+
if [ "$emit" = decision ] || [ -n "${DOFLOW_BRIDGE_TRACE:-}" ]; then
|
|
145
|
+
decision="$(jq -cn --arg harness "$harness" --arg event "$event" --arg policy "$policy" --arg outcome "$DF_BRIDGE_OUTCOME" --arg code "$DF_BRIDGE_CODE" --arg reason "$decision_reason" --arg profile "$DF_BRIDGE_PROFILE" --arg effect "$DF_BRIDGE_EFFECT" --argjson version "$DF_BRIDGE_VERSION" '{v:1,harness:$harness,event:$event,policy:$policy,policyVersion:$version,outcome:$outcome,code:$code,reason:$reason,faultProfile:$profile,effect:$effect}')"
|
|
146
|
+
fi
|
|
147
|
+
if [ -n "${DOFLOW_BRIDGE_TRACE:-}" ]; then printf '%s\n' "$decision" >>"$DOFLOW_BRIDGE_TRACE" 2>/dev/null || true; fi
|
|
148
|
+
if [ "$emit" = decision ]; then
|
|
149
|
+
printf '%s\n' "$decision"
|
|
150
|
+
return 0
|
|
151
|
+
fi
|
|
152
|
+
if [ "$DF_BRIDGE_EFFECT" = block ]; then
|
|
153
|
+
status=2
|
|
154
|
+
else
|
|
155
|
+
status=0
|
|
156
|
+
if [ "$DF_BRIDGE_OUTCOME" = error ]; then
|
|
157
|
+
case "$policy:$harness:$response_event" in
|
|
158
|
+
session-context:*:*|user-prompt-submit:*:*|subagent-audit:*:*|*:kiro:PreToolUse|*:claude:Stop) status="$code" ;;
|
|
159
|
+
esac
|
|
160
|
+
fi
|
|
161
|
+
fi
|
|
162
|
+
if [ "$spawn_failed" -eq 1 ] && [ "$DF_BRIDGE_EFFECT" = proceed ]; then status=0; fi
|
|
163
|
+
if [ "$DF_BRIDGE_EFFECT" = block ]; then
|
|
164
|
+
doflow_render_response "$harness" "$event" deny "$reason" "$status"
|
|
165
|
+
local render_status=$?
|
|
166
|
+
return "$render_status"
|
|
167
|
+
fi
|
|
168
|
+
case "$policy:$DF_BRIDGE_OUTCOME:$harness:$response_event:$DF_BRIDGE_CODE" in
|
|
169
|
+
session-context:error:*:*:*|user-prompt-submit:error:*:*:*|subagent-audit:error:*:*:*|session-context:unsupported:*:*:*|user-prompt-submit:unsupported:*:*:*|subagent-audit:unsupported:*:*:*) ;;
|
|
170
|
+
*:error:codex:Stop:exit-*|*:error:codex:Stop:signal-*|*:error:codex:stop:exit-*|*:error:codex:stop:signal-*|*:error:kiro:PreToolUse:exit-*|*:error:kiro:PreToolUse:signal-*|*:error:kiro:Stop:exit-*|*:error:kiro:Stop:signal-*|*:error:claude:Stop:exit-*|*:error:claude:Stop:signal-*) ;;
|
|
171
|
+
*)
|
|
172
|
+
if ! doflow_bridge_drops_tool_stderr "$policy" "$harness" "$response_event" && [ -n "$err" ]; then printf '%s' "$err" >&2; fi
|
|
173
|
+
;;
|
|
174
|
+
esac
|
|
175
|
+
doflow_render_response "$harness" "$event" "$DF_BRIDGE_OUTCOME" "$reason" "$status"
|
|
176
|
+
local render_status=$?
|
|
177
|
+
return "$render_status"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
181
|
+
. "$_DOFLOW_BRIDGE_DIR/respond.sh"
|
|
182
|
+
doflow_bridge "${1:-}" "${2:-}" "${3:-}" "${4:-}" "${5:-}"
|
|
183
|
+
exit $?
|
|
184
|
+
fi
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
def nonempty($v): if ($v | type) == "string" and $v != "" then $v else null end;
|
|
2
|
+
def put($o; $k; $v): if $v == null then $o else $o + {($k): $v} end;
|
|
3
|
+
def event_name($fallback): if (.hook_event_name | type) == "string" then .hook_event_name else $fallback end;
|
|
4
|
+
def path_values($obj; $keys): first([$keys[] as $k | $obj[$k] | select(type == "string" and length > 0)][]) // null | if . == null then null else [.] end;
|
|
5
|
+
def string_value($obj; $keys): first([$keys[] as $k | $obj[$k] | select(type == "string") ][]) // null;
|
|
6
|
+
def session_identity($s): {session: nonempty($s)};
|
|
7
|
+
def correlation($id;$parent): {toolCallId:nonempty($id),parentToolCallId:nonempty($parent)};
|
|
8
|
+
def event_operation($e):
|
|
9
|
+
if $e == "SessionStart" or $e == "agentSpawn" then "session-start"
|
|
10
|
+
elif $e == "SessionEnd" then "session-end"
|
|
11
|
+
elif $e == "UserPromptSubmit" then "prompt"
|
|
12
|
+
elif $e == "Stop" or $e == "stop" then "stop"
|
|
13
|
+
elif $e == "SubagentStart" then "subagent-start"
|
|
14
|
+
elif $e == "SubagentStop" then "subagent-stop"
|
|
15
|
+
elif $e == "PreCompact" or $e == "PreCompress" then "compact"
|
|
16
|
+
elif $e == "PostToolUse" or $e == "AfterTool" then "post-tool"
|
|
17
|
+
else "tool" end;
|
|
18
|
+
def canonical_tool($name; $harness):
|
|
19
|
+
if ($name | type) != "string" then {operation:"tool"}
|
|
20
|
+
elif (($harness == "claude" or $harness == "codex" or $harness == "kiro") and ($name | test("^mcp__"))) or (($harness == "gemini" or $harness == "antigravity") and ($name | test("^mcp_"))) or ($harness == "pi" and ($name | test("^mcp__"))) then {operation:"mcp",tool:$name}
|
|
21
|
+
elif (($harness == "claude" or $harness == "codex" or $harness == "kiro") and (["Edit","Write","MultiEdit","apply_patch"] | index($name)) != null) or ($harness == "gemini" and (["write_file","replace"] | index($name)) != null) or ($harness == "antigravity" and (["write_to_file","replace_file_content","multi_replace_file_content","edit_file","write_file","create_file","replace_file","replace","Edit","Write","MultiEdit","apply_patch"] | index($name)) != null) or ($harness == "pi" and (["edit","write"] | index($name)) != null) then {operation:"edit",tool:$name}
|
|
22
|
+
elif (($harness == "claude" or $harness == "codex" or $harness == "kiro") and $name == "Bash") or ($harness == "gemini" and $name == "run_shell_command") or ($harness == "antigravity" and (["run_command","run_shell_command","Bash","bash"] | index($name)) != null) or ($harness == "pi" and $name == "bash") then {operation:"command",tool:$name}
|
|
23
|
+
else {operation:"tool",tool:$name} end;
|
|
24
|
+
def decode_claude:
|
|
25
|
+
(canonical_tool(.tool_name // ""; "claude")) as $t |
|
|
26
|
+
if .tool_name == null then {operation: event_operation($event), identity: session_identity(.session_id)}
|
|
27
|
+
else {operation:$t.operation, tool:$t.tool, paths:(if $t.operation == "edit" then path_values(.tool_input; ["file_path"]) else null end), command:(if $t.operation == "command" then string_value(.tool_input; ["command"]) else null end), identity:session_identity(.session_id)} end;
|
|
28
|
+
def decode_codex:
|
|
29
|
+
decode_claude as $d |
|
|
30
|
+
if .tool_name == "apply_patch" then $d + {patch:(string_value(.tool_input; ["command"]))} else $d end;
|
|
31
|
+
def decode_gemini:
|
|
32
|
+
(canonical_tool(.tool_name // ""; "gemini")) as $t |
|
|
33
|
+
if .tool_name == null then {operation:event_operation($event),identity:session_identity(.session_id)}
|
|
34
|
+
else {operation:$t.operation,tool:$t.tool,paths:(if $t.operation == "edit" then path_values(.tool_input; ["file_path","path"]) else null end),command:(if $t.operation == "command" then string_value(.tool_input; ["command"]) else null end),identity:session_identity(.session_id)} end;
|
|
35
|
+
def decode_kiro:
|
|
36
|
+
(if ((.tool_name // "") | type) == "string" and ((.tool_name // "") | startswith("mcp_")) then {operation:"mcp",tool:.tool_name}
|
|
37
|
+
elif ((.tool_name // "") | type) == "string" and ((.tool_name // "") | test("^@[^/]+/.+")) then
|
|
38
|
+
((.tool_name | capture("^@(?<server>[^/]+)/(?<tool>.+)$")) as $m | {operation:"mcp",tool:("mcp__" + $m.server + "__" + $m.tool)})
|
|
39
|
+
else canonical_tool(.tool_name // ""; "kiro") end) as $t |
|
|
40
|
+
if .tool_name == null then {operation:event_operation($event),identity:session_identity(.session_id)}
|
|
41
|
+
else {operation:$t.operation,tool:$t.tool,paths:(if $t.operation == "edit" then path_values(.tool_input; ["file_path"]) else null end),command:(if $t.operation == "command" then string_value(.tool_input; ["command"]) else null end),identity:session_identity(.session_id)} end;
|
|
42
|
+
def decode_antigravity:
|
|
43
|
+
if .toolCall == null then {operation:event_operation($event),identity:session_identity(.session_id)}
|
|
44
|
+
else (canonical_tool(.toolCall.name // ""; "antigravity")) as $t |
|
|
45
|
+
{operation:$t.operation,tool:$t.tool,paths:(if $t.operation == "edit" then path_values(.toolCall.args; ["AbsolutePath","TargetFile","Path","file_path","path"]) else null end),command:(if $t.operation == "command" then string_value(.toolCall.args; ["CommandLine","command"]) else null end),identity:session_identity(.session_id)} end;
|
|
46
|
+
def decode_pi:
|
|
47
|
+
(canonical_tool(.toolName // ""; "pi")) as $t |
|
|
48
|
+
{operation:$t.operation,tool:$t.tool,paths:(if $t.operation == "edit" then path_values(.input; ["path"]) else null end),command:(if $t.operation == "command" then string_value(.input; ["command"]) else null end),identity:session_identity(.sessionId),correlation:(correlation(.toolCallId;.parentToolCallId))};
|
|
49
|
+
if ($ARGS.named.mode // "encode") == "minimal" then {doflow_event:{version:(1),harness:$harness,event:(event_name($event)),operation:event_operation($event)}} elif type != "object" then . else
|
|
50
|
+
. as $native |
|
|
51
|
+
($ENV.DOFLOW_TASK_ID | nonempty(.)) as $task |
|
|
52
|
+
($ENV.DOFLOW_RUN_ID | nonempty(.)) as $run |
|
|
53
|
+
($ENV.DOFLOW_ATTEMPT | try tonumber catch null) as $attempt |
|
|
54
|
+
(if $ENV.DOFLOW_SESSION_ROLE == "main" or $ENV.DOFLOW_SESSION_ROLE == "worker" then $ENV.DOFLOW_SESSION_ROLE else null end) as $role |
|
|
55
|
+
(if $harness == "claude" then decode_claude elif $harness == "codex" then decode_codex elif $harness == "gemini" then decode_gemini elif $harness == "kiro" then decode_kiro elif $harness == "antigravity" then decode_antigravity elif $harness == "pi" then decode_pi else {operation:"tool"} end) as $d |
|
|
56
|
+
({version:1,harness:$harness,event:(event_name($event)),operation:$d.operation,tool:$d.tool,paths:$d.paths,command:$d.command,patch:$d.patch,taskId:$task,
|
|
57
|
+
identity:($d.identity + {run:$run,attempt:$attempt,role:$role} | with_entries(select(.value != null)) | if length > 0 then . else null end),
|
|
58
|
+
correlation:(($d.correlation // {}) | with_entries(select(.value != null)) | if length > 0 then . else null end) } | with_entries(select(.value != null))) as $envelope |
|
|
59
|
+
$native + {doflow_event:$envelope}
|
|
60
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Native hook response renderers for the shared lifecycle bridge.
|
|
3
|
+
case ${BASH_SOURCE[0]} in */*) _DOFLOW_RESPOND_DIR=${BASH_SOURCE[0]%/*} ;; *) _DOFLOW_RESPOND_DIR=. ;; esac
|
|
4
|
+
. "$_DOFLOW_RESPOND_DIR/../policies/deny-json.sh"
|
|
5
|
+
|
|
6
|
+
doflow_bridge_native_event_var() {
|
|
7
|
+
DF_NE=$2
|
|
8
|
+
case "$1:$2" in
|
|
9
|
+
kiro:preToolUse) DF_NE=PreToolUse ;;
|
|
10
|
+
kiro:agentSpawn) DF_NE=SessionStart ;;
|
|
11
|
+
kiro:stop) DF_NE=Stop ;;
|
|
12
|
+
esac
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
doflow_bridge_native_event() {
|
|
16
|
+
local harness="$1" event="$2"
|
|
17
|
+
case "$harness:$event" in
|
|
18
|
+
kiro:preToolUse) event=PreToolUse ;;
|
|
19
|
+
kiro:agentSpawn) event=SessionStart ;;
|
|
20
|
+
kiro:stop) event=Stop ;;
|
|
21
|
+
esac
|
|
22
|
+
printf '%s\n' "$event"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
doflow_render_response() {
|
|
26
|
+
local harness="$1" event="$2" outcome="$3" reason="$4" status="$5" engine="${DOFLOW_KIRO_ENGINE:-2x}"
|
|
27
|
+
doflow_bridge_native_event_var "$harness" "$event"; event=$DF_NE
|
|
28
|
+
if [ "$outcome" = deny ]; then
|
|
29
|
+
case "$harness:$event" in
|
|
30
|
+
claude:PreToolUse|codex:PreToolUse) emit_pretooluse_deny_nested "$reason" || return $?; return 0 ;;
|
|
31
|
+
gemini:BeforeTool) emit_pretooluse_deny_flat "$reason"; return 0 ;;
|
|
32
|
+
kiro:PreToolUse) printf '%s' "$reason" >&2; return 2 ;;
|
|
33
|
+
antigravity:PreToolUse)
|
|
34
|
+
if command -v jq >/dev/null 2>&1; then jq -cn --arg reason "$reason" '{decision:"deny",reason:$reason}'
|
|
35
|
+
else printf '{"decision":"deny","reason":%s}\n' "$( _doflow_json_reason "$reason" )"; fi
|
|
36
|
+
return $? ;;
|
|
37
|
+
antigravity:Stop)
|
|
38
|
+
if command -v jq >/dev/null 2>&1; then jq -cn --arg reason "$reason" '{decision:"continue",reason:$reason}'
|
|
39
|
+
else printf '{"decision":"continue","reason":%s}\n' "$( _doflow_json_reason "$reason" )"; fi
|
|
40
|
+
return $? ;;
|
|
41
|
+
pi:tool_call)
|
|
42
|
+
if command -v jq >/dev/null 2>&1; then jq -cn --arg reason "$reason" '{block:true,reason:$reason}'
|
|
43
|
+
else printf '{"block":true,"reason":%s}\n' "$( _doflow_json_reason "$reason" )"; fi
|
|
44
|
+
return $? ;;
|
|
45
|
+
kiro:Stop|kiro:stop)
|
|
46
|
+
if [ "$engine" = v3 ]; then
|
|
47
|
+
if command -v jq >/dev/null 2>&1; then jq -cn --arg reason "$reason" '{decision:"block",reason:$reason}'
|
|
48
|
+
else printf '{"decision":"block","reason":%s}\n' "$( _doflow_json_reason "$reason" )"; fi
|
|
49
|
+
return $?
|
|
50
|
+
fi
|
|
51
|
+
printf '%s' "$reason" >&2; return 2 ;;
|
|
52
|
+
codex:Stop|codex:stop) printf '%s\n' '{}' >&1; printf '%s\n' "$reason" >&2; return 2 ;;
|
|
53
|
+
*) printf '%s\n' "$reason" >&2; return 2 ;;
|
|
54
|
+
esac
|
|
55
|
+
fi
|
|
56
|
+
case "$harness:$event" in
|
|
57
|
+
codex:Stop|codex:stop) printf '%s\n' '{}' ;;
|
|
58
|
+
antigravity:PreToolUse) printf '%s\n' '{"decision":"allow"}' ;;
|
|
59
|
+
kiro:PreToolUse|claude:Stop) if [ "$status" -ne 0 ]; then printf '%s' "$reason" >&2; fi ;;
|
|
60
|
+
esac
|
|
61
|
+
return "$status"
|
|
62
|
+
}
|