@intutic/sync-daemon 2.1.0 → 2.3.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 +1 -1
- package/dist/agentReporter.d.ts +24 -11
- package/dist/agentReporter.d.ts.map +1 -1
- package/dist/agentReporter.js +20 -13
- package/dist/agentReporter.js.map +1 -1
- package/dist/configReader.d.ts +45 -25
- package/dist/configReader.d.ts.map +1 -1
- package/dist/configReader.js +70 -46
- package/dist/configReader.js.map +1 -1
- package/dist/configWriter.d.ts +32 -45
- package/dist/configWriter.d.ts.map +1 -1
- package/dist/configWriter.js +52 -686
- package/dist/configWriter.js.map +1 -1
- package/dist/disconnect/harnesses.d.ts +29 -0
- package/dist/disconnect/harnesses.d.ts.map +1 -0
- package/dist/disconnect/harnesses.js +805 -0
- package/dist/disconnect/harnesses.js.map +1 -0
- package/dist/disconnect/index.d.ts +25 -0
- package/dist/disconnect/index.d.ts.map +1 -0
- package/dist/disconnect/index.js +92 -0
- package/dist/disconnect/index.js.map +1 -0
- package/dist/disconnect/mcp.d.ts +81 -0
- package/dist/disconnect/mcp.d.ts.map +1 -0
- package/dist/disconnect/mcp.js +354 -0
- package/dist/disconnect/mcp.js.map +1 -0
- package/dist/disconnect/originals.d.ts +88 -0
- package/dist/disconnect/originals.d.ts.map +1 -0
- package/dist/disconnect/originals.js +257 -0
- package/dist/disconnect/originals.js.map +1 -0
- package/dist/disconnect/plan.d.ts +175 -0
- package/dist/disconnect/plan.d.ts.map +1 -0
- package/dist/disconnect/plan.js +580 -0
- package/dist/disconnect/plan.js.map +1 -0
- package/dist/disconnect/recognise.d.ts +43 -0
- package/dist/disconnect/recognise.d.ts.map +1 -0
- package/dist/disconnect/recognise.js +108 -0
- package/dist/disconnect/recognise.js.map +1 -0
- package/dist/disconnect/tomlLines.d.ts +39 -0
- package/dist/disconnect/tomlLines.d.ts.map +1 -0
- package/dist/disconnect/tomlLines.js +105 -0
- package/dist/disconnect/tomlLines.js.map +1 -0
- package/dist/harness/aiderConfigMerger.d.ts +29 -8
- package/dist/harness/aiderConfigMerger.d.ts.map +1 -1
- package/dist/harness/aiderConfigMerger.js +63 -12
- package/dist/harness/aiderConfigMerger.js.map +1 -1
- package/dist/harness/antigravityCliHooks.d.ts +61 -0
- package/dist/harness/antigravityCliHooks.d.ts.map +1 -0
- package/dist/harness/antigravityCliHooks.js +207 -0
- package/dist/harness/antigravityCliHooks.js.map +1 -0
- package/dist/harness/antigravityHooks.d.ts +12 -2
- package/dist/harness/antigravityHooks.d.ts.map +1 -1
- package/dist/harness/antigravityHooks.js +38 -16
- package/dist/harness/antigravityHooks.js.map +1 -1
- package/dist/harness/antigravityProducts.d.ts +40 -0
- package/dist/harness/antigravityProducts.d.ts.map +1 -0
- package/dist/harness/antigravityProducts.js +109 -0
- package/dist/harness/antigravityProducts.js.map +1 -0
- package/dist/harness/claudeAgentsMd.d.ts +29 -0
- package/dist/harness/claudeAgentsMd.d.ts.map +1 -0
- package/dist/harness/claudeAgentsMd.js +80 -0
- package/dist/harness/claudeAgentsMd.js.map +1 -0
- package/dist/harness/claudeCodeHooks.d.ts +6 -6
- package/dist/harness/claudeCodeHooks.d.ts.map +1 -1
- package/dist/harness/claudeCodeHooks.js +115 -28
- package/dist/harness/claudeCodeHooks.js.map +1 -1
- package/dist/harness/claudeProjectApproval.d.ts +53 -0
- package/dist/harness/claudeProjectApproval.d.ts.map +1 -0
- package/dist/harness/claudeProjectApproval.js +159 -0
- package/dist/harness/claudeProjectApproval.js.map +1 -0
- package/dist/harness/clineHooks.d.ts +0 -1
- package/dist/harness/clineHooks.d.ts.map +1 -1
- package/dist/harness/clineHooks.js +13 -4
- package/dist/harness/clineHooks.js.map +1 -1
- package/dist/harness/codexConfigMerger.d.ts.map +1 -1
- package/dist/harness/codexConfigMerger.js +4 -0
- package/dist/harness/codexConfigMerger.js.map +1 -1
- package/dist/harness/codexHooks.d.ts +2 -0
- package/dist/harness/codexHooks.d.ts.map +1 -1
- package/dist/harness/codexHooks.js +15 -4
- package/dist/harness/codexHooks.js.map +1 -1
- package/dist/harness/continueConfigMerger.d.ts.map +1 -1
- package/dist/harness/continueConfigMerger.js +4 -0
- package/dist/harness/continueConfigMerger.js.map +1 -1
- package/dist/harness/cursorHooks.d.ts +5 -2
- package/dist/harness/cursorHooks.d.ts.map +1 -1
- package/dist/harness/cursorHooks.js +12 -3
- package/dist/harness/cursorHooks.js.map +1 -1
- package/dist/harness/dshHooks.d.ts +5 -5
- package/dist/harness/dshHooks.d.ts.map +1 -1
- package/dist/harness/dshHooks.js +12 -5
- package/dist/harness/dshHooks.js.map +1 -1
- package/dist/harness/gateArtifacts.d.ts +35 -0
- package/dist/harness/gateArtifacts.d.ts.map +1 -0
- package/dist/harness/gateArtifacts.js +88 -0
- package/dist/harness/gateArtifacts.js.map +1 -0
- package/dist/harness/gateBody.d.ts +195 -69
- package/dist/harness/gateBody.d.ts.map +1 -1
- package/dist/harness/gateBody.js +847 -182
- package/dist/harness/gateBody.js.map +1 -1
- package/dist/harness/gateSightings.d.ts +47 -0
- package/dist/harness/gateSightings.d.ts.map +1 -0
- package/dist/harness/gateSightings.js +159 -0
- package/dist/harness/gateSightings.js.map +1 -0
- package/dist/harness/githubCopilotHooks.d.ts.map +1 -1
- package/dist/harness/githubCopilotHooks.js +11 -10
- package/dist/harness/githubCopilotHooks.js.map +1 -1
- package/dist/harness/gooseHardener.d.ts +0 -1
- package/dist/harness/gooseHardener.d.ts.map +1 -1
- package/dist/harness/gooseHardener.js +1 -1
- package/dist/harness/gooseHardener.js.map +1 -1
- package/dist/harness/gooseHooks.d.ts +29 -1
- package/dist/harness/gooseHooks.d.ts.map +1 -1
- package/dist/harness/gooseHooks.js +107 -69
- package/dist/harness/gooseHooks.js.map +1 -1
- package/dist/harness/grokHooks.d.ts +2 -2
- package/dist/harness/grokHooks.d.ts.map +1 -1
- package/dist/harness/grokHooks.js +20 -18
- package/dist/harness/grokHooks.js.map +1 -1
- package/dist/harness/hermesHooks.d.ts +18 -8
- package/dist/harness/hermesHooks.d.ts.map +1 -1
- package/dist/harness/hermesHooks.js +55 -19
- package/dist/harness/hermesHooks.js.map +1 -1
- package/dist/harness/holdRedaction.d.ts +4 -1
- package/dist/harness/holdRedaction.d.ts.map +1 -1
- package/dist/harness/holdRedaction.js +32 -8
- package/dist/harness/holdRedaction.js.map +1 -1
- package/dist/harness/jetbrainsXmlConfig.d.ts +20 -0
- package/dist/harness/jetbrainsXmlConfig.d.ts.map +1 -1
- package/dist/harness/jetbrainsXmlConfig.js +39 -18
- package/dist/harness/jetbrainsXmlConfig.js.map +1 -1
- package/dist/harness/mcpAutoWrite.d.ts +62 -25
- package/dist/harness/mcpAutoWrite.d.ts.map +1 -1
- package/dist/harness/mcpAutoWrite.js +406 -95
- package/dist/harness/mcpAutoWrite.js.map +1 -1
- package/dist/harness/museHooks.d.ts +3 -3
- package/dist/harness/museHooks.d.ts.map +1 -1
- package/dist/harness/museHooks.js +15 -7
- package/dist/harness/museHooks.js.map +1 -1
- package/dist/harness/n8nHooks.d.ts +0 -1
- package/dist/harness/n8nHooks.d.ts.map +1 -1
- package/dist/harness/n8nHooks.js +4 -1
- package/dist/harness/n8nHooks.js.map +1 -1
- package/dist/harness/openCodeHooks.d.ts +2 -2
- package/dist/harness/openCodeHooks.d.ts.map +1 -1
- package/dist/harness/openCodeHooks.js +9 -8
- package/dist/harness/openCodeHooks.js.map +1 -1
- package/dist/harness/openWebuiHooks.d.ts +0 -1
- package/dist/harness/openWebuiHooks.d.ts.map +1 -1
- package/dist/harness/openWebuiHooks.js +11 -3
- package/dist/harness/openWebuiHooks.js.map +1 -1
- package/dist/harness/openclawHooks.d.ts +75 -18
- package/dist/harness/openclawHooks.d.ts.map +1 -1
- package/dist/harness/openclawHooks.js +214 -160
- package/dist/harness/openclawHooks.js.map +1 -1
- package/dist/harness/openhandsHooks.d.ts +37 -11
- package/dist/harness/openhandsHooks.d.ts.map +1 -1
- package/dist/harness/openhandsHooks.js +148 -97
- package/dist/harness/openhandsHooks.js.map +1 -1
- package/dist/harness/piHooks.d.ts +52 -17
- package/dist/harness/piHooks.d.ts.map +1 -1
- package/dist/harness/piHooks.js +177 -163
- package/dist/harness/piHooks.js.map +1 -1
- package/dist/harness/protectedPaths.d.ts +107 -25
- package/dist/harness/protectedPaths.d.ts.map +1 -1
- package/dist/harness/protectedPaths.js +342 -48
- package/dist/harness/protectedPaths.js.map +1 -1
- package/dist/harness/rulesSection.d.ts +62 -0
- package/dist/harness/rulesSection.d.ts.map +1 -0
- package/dist/harness/rulesSection.js +145 -0
- package/dist/harness/rulesSection.js.map +1 -0
- package/dist/harness/windsurfHooks.d.ts +18 -3
- package/dist/harness/windsurfHooks.d.ts.map +1 -1
- package/dist/harness/windsurfHooks.js +41 -8
- package/dist/harness/windsurfHooks.js.map +1 -1
- package/dist/harness/windsurfJetBrainsProxy.d.ts +1 -1
- package/dist/harness/windsurfJetBrainsProxy.d.ts.map +1 -1
- package/dist/harness/windsurfJetBrainsProxy.js +5 -1
- package/dist/harness/windsurfJetBrainsProxy.js.map +1 -1
- package/dist/index.d.ts +21 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -19
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts +53 -0
- package/dist/inventory.d.ts.map +1 -0
- package/dist/inventory.js +133 -0
- package/dist/inventory.js.map +1 -0
- package/dist/lib/complianceProbes.d.ts +2 -1
- package/dist/lib/complianceProbes.d.ts.map +1 -1
- package/dist/lib/complianceProbes.js +1 -1
- package/dist/lib/complianceProbes.js.map +1 -1
- package/dist/lib/decisionsDigest.d.ts +35 -17
- package/dist/lib/decisionsDigest.d.ts.map +1 -1
- package/dist/lib/decisionsDigest.js +106 -47
- package/dist/lib/decisionsDigest.js.map +1 -1
- package/dist/lib/egressPolicy.d.ts +2 -2
- package/dist/lib/egressPolicy.js +2 -2
- package/dist/lib/mcpRegistryPy.d.ts +11 -0
- package/dist/lib/mcpRegistryPy.d.ts.map +1 -0
- package/dist/lib/mcpRegistryPy.js +173 -0
- package/dist/lib/mcpRegistryPy.js.map +1 -0
- package/dist/lib/phrasesPy.d.ts +11 -0
- package/dist/lib/phrasesPy.d.ts.map +1 -0
- package/dist/lib/phrasesPy.js +351 -0
- package/dist/lib/phrasesPy.js.map +1 -0
- package/dist/lib/policySnapshot.d.ts +63 -9
- package/dist/lib/policySnapshot.d.ts.map +1 -1
- package/dist/lib/policySnapshot.js +269 -45
- package/dist/lib/policySnapshot.js.map +1 -1
- package/dist/lib/processPoller.d.ts.map +1 -1
- package/dist/lib/processPoller.js +9 -2
- package/dist/lib/processPoller.js.map +1 -1
- package/dist/lib/regexLinearity.d.ts +43 -0
- package/dist/lib/regexLinearity.d.ts.map +1 -0
- package/dist/lib/regexLinearity.js +275 -0
- package/dist/lib/regexLinearity.js.map +1 -0
- package/dist/lib/runtimeEnv.d.ts +1 -3
- package/dist/lib/runtimeEnv.d.ts.map +1 -1
- package/dist/lib/runtimeEnv.js +0 -2
- package/dist/lib/runtimeEnv.js.map +1 -1
- package/dist/lib/sequencePy.d.ts +10 -0
- package/dist/lib/sequencePy.d.ts.map +1 -0
- package/dist/lib/sequencePy.js +102 -0
- package/dist/lib/sequencePy.js.map +1 -0
- package/dist/sessionReporter.d.ts +18 -12
- package/dist/sessionReporter.d.ts.map +1 -1
- package/dist/sessionReporter.js +37 -24
- package/dist/sessionReporter.js.map +1 -1
- package/dist/skillWriter.d.ts +23 -5
- package/dist/skillWriter.d.ts.map +1 -1
- package/dist/skillWriter.js +159 -16
- package/dist/skillWriter.js.map +1 -1
- package/dist/syncCycle.d.ts +142 -0
- package/dist/syncCycle.d.ts.map +1 -0
- package/dist/syncCycle.js +327 -0
- package/dist/syncCycle.js.map +1 -0
- package/dist/trajectoryMonitor.d.ts +2 -4
- package/dist/trajectoryMonitor.d.ts.map +1 -1
- package/dist/trajectoryMonitor.js +2 -5
- package/dist/trajectoryMonitor.js.map +1 -1
- package/dist/watcher/driftWatcher.d.ts +0 -1
- package/dist/watcher/driftWatcher.d.ts.map +1 -1
- package/dist/watcher/driftWatcher.js +1 -2
- package/dist/watcher/driftWatcher.js.map +1 -1
- package/dist/watcher/settingsGuard.d.ts +36 -7
- package/dist/watcher/settingsGuard.d.ts.map +1 -1
- package/dist/watcher/settingsGuard.js +397 -60
- package/dist/watcher/settingsGuard.js.map +1 -1
- package/dist/watcher/vscodeHookSettings.d.ts +30 -0
- package/dist/watcher/vscodeHookSettings.d.ts.map +1 -0
- package/dist/watcher/vscodeHookSettings.js +170 -0
- package/dist/watcher/vscodeHookSettings.js.map +1 -0
- package/dist/wsClient.d.ts +0 -1
- package/dist/wsClient.d.ts.map +1 -1
- package/dist/wsClient.js +0 -1
- package/dist/wsClient.js.map +1 -1
- package/package.json +5 -5
- package/dist/harness/claudeDesktopHooks.d.ts +0 -30
- package/dist/harness/claudeDesktopHooks.d.ts.map +0 -1
- package/dist/harness/claudeDesktopHooks.js +0 -254
- package/dist/harness/claudeDesktopHooks.js.map +0 -1
- package/dist/harness/continueHooks.d.ts +0 -63
- package/dist/harness/continueHooks.d.ts.map +0 -1
- package/dist/harness/continueHooks.js +0 -247
- package/dist/harness/continueHooks.js.map +0 -1
- package/dist/harness/gateKind.d.ts +0 -164
- package/dist/harness/gateKind.d.ts.map +0 -1
- package/dist/harness/gateKind.js +0 -144
- package/dist/harness/gateKind.js.map +0 -1
- package/dist/harness/rooCodeHooks.d.ts +0 -31
- package/dist/harness/rooCodeHooks.d.ts.map +0 -1
- package/dist/harness/rooCodeHooks.js +0 -286
- package/dist/harness/rooCodeHooks.js.map +0 -1
- package/dist/hashReporter.d.ts +0 -33
- package/dist/hashReporter.d.ts.map +0 -1
- package/dist/hashReporter.js +0 -69
- package/dist/hashReporter.js.map +0 -1
- package/dist/integrityStore.d.ts +0 -35
- package/dist/integrityStore.d.ts.map +0 -1
- package/dist/integrityStore.js +0 -71
- package/dist/integrityStore.js.map +0 -1
- package/dist/lib/gitWorktrees.d.ts +0 -140
- package/dist/lib/gitWorktrees.d.ts.map +0 -1
- package/dist/lib/gitWorktrees.js +0 -191
- package/dist/lib/gitWorktrees.js.map +0 -1
- package/dist/syncLoop.d.ts +0 -172
- package/dist/syncLoop.d.ts.map +0 -1
- package/dist/syncLoop.js +0 -878
- package/dist/syncLoop.js.map +0 -1
|
@@ -1,34 +1,91 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* openclawHooks.ts —
|
|
2
|
+
* openclawHooks.ts — OpenClaw plugin gate.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
4
|
+
* OpenClaw has two hook systems, and only one of them can stop a tool call.
|
|
5
|
+
* Its *internal* hooks (`hooks.internal.entries` in `~/.openclaw/openclaw.json`,
|
|
6
|
+
* `HOOK.md` handlers) react to command, session and Gateway events; the
|
|
7
|
+
* dispatcher catches a handler's error and carries on, and tool calls are not
|
|
8
|
+
* among their events at all. Earlier versions of this writer registered the
|
|
9
|
+
* gate there, as `{ event: 'PreToolUse', command }`, a shape OpenClaw keeps
|
|
10
|
+
* (hook entries are open-ended) and never runs. Tool calls are intercepted by
|
|
11
|
+
* a *plugin* hook, `before_tool_call`, registered with `api.on(...)`:
|
|
12
|
+
* https://github.com/openclaw/openclaw/blob/59309cc7777e07d52ca1be94bf8c25fd685919bc/docs/automation/hooks.md
|
|
13
|
+
* ("Choose the right surface": intercepting tools is a plugin hook).
|
|
7
14
|
*
|
|
8
|
-
*
|
|
9
|
-
* 2. Writes `~/.intutic/hooks/openclaw-check.js` — dual-path Node.js hook.
|
|
10
|
-
* 3. Writes `.intutic/env/openclaw.env` — env snippet.
|
|
11
|
-
* 4. Optionally validates with `openclaw hooks check` (non-fatal).
|
|
15
|
+
* ## The plugin
|
|
12
16
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
17
|
+
* A standalone plugin file needs no manifest when it is listed in
|
|
18
|
+
* `plugins.load.paths`; its id is the file's basename, so the file is
|
|
19
|
+
* `intutic-governance.cjs` and exports `{ id: 'intutic-governance', register }`
|
|
20
|
+
* (`docs/plugins/hooks/tool-policy.md` "Sender-aware policy in one file";
|
|
21
|
+
* `src/plugins/discovery.ts` `discoverFromPath`; the loader test "loads
|
|
22
|
+
* multiple manifestless standalone files"). `.cjs`, so the module format does
|
|
23
|
+
* not depend on any `package.json` above it. A plugin from a configured path
|
|
24
|
+
* is enabled by default unless a non-empty `plugins.allow` leaves it out
|
|
25
|
+
* (`src/plugins/config-activation-shared.ts`), so the id is added to an
|
|
26
|
+
* existing allowlist and nothing is written when there is none.
|
|
15
27
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
28
|
+
* `before_tool_call` receives `{ toolName, params, toolCallId?, runId?,
|
|
29
|
+
* derivedPaths? }` and `ctx.sessionId`, and refuses with `{ block: true,
|
|
30
|
+
* blockReason }`, which is terminal (`docs/plugins/hooks/tool-policy.md`,
|
|
31
|
+
* `src/plugins/hook-types.ts`). The global hook runner runs it fail-closed: a
|
|
32
|
+
* handler that throws or outlives its timeout blocks the call
|
|
33
|
+
* (`src/plugins/hook-runner-global.ts`: `before_tool_call: "fail-closed"`;
|
|
34
|
+
* default 15 s, `docs/plugins/hooks/reference.md`). That timeout cannot stop
|
|
35
|
+
* synchronous code, so the hook registers `timeoutMs: HOOK_TIMEOUT_SECONDS *
|
|
36
|
+
* 1000` and the gate runs under its own deadline inside it
|
|
37
|
+
* (`gateDeadlineMs('openclaw')`).
|
|
38
|
+
*
|
|
39
|
+
* The plugin embeds the shared `emitJsGate` body (`'throw'` contract) and
|
|
40
|
+
* returns OpenClaw's block result for the refusal it throws. Any other fault
|
|
41
|
+
* is a block too. MCP tools reach the hook as `<server>__<tool>`
|
|
42
|
+
* (`src/agents/agent-bundle-mcp-names.ts`, `TOOL_NAME_SEPARATOR`), which the
|
|
43
|
+
* plugin composes into `mcp__<server>__<tool>` for the allowlist.
|
|
44
|
+
*
|
|
45
|
+
* The file lives under `~/.intutic/hooks/`, a governance-protected path, so an
|
|
46
|
+
* agent cannot edit it under any gated harness.
|
|
19
47
|
*
|
|
20
48
|
* @module
|
|
21
49
|
*/
|
|
50
|
+
/** The plugin id, which OpenClaw takes from the standalone file's basename. */
|
|
51
|
+
export declare const OPENCLAW_PLUGIN_ID = "intutic-governance";
|
|
52
|
+
/** The plugin file, relative to the home directory. */
|
|
53
|
+
export declare const OPENCLAW_PLUGIN_FILE: string;
|
|
54
|
+
/** The first line of the plugin's header, which disconnect recognises it by. */
|
|
55
|
+
export declare const OPENCLAW_PLUGIN_MARKER = "Intutic OpenClaw governance plugin.";
|
|
56
|
+
/** The absolute path of the plugin file for the current home directory. */
|
|
57
|
+
export declare function openclawPluginPath(): string;
|
|
58
|
+
export declare function buildOpenclawPlugin(proxyUrl: string, workspaceRoot: string, workspaceId: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Attempt to parse a JSON5-like string by progressively stripping
|
|
61
|
+
* non-standard syntax. Returns a plain JS object or throws if unparseable.
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseJson5Like(raw: string): Record<string, unknown>;
|
|
22
64
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
65
|
+
* Adds the plugin to `plugins.load.paths`, and its id to `plugins.allow` when
|
|
66
|
+
* that list is non-empty (an empty or absent one allows every plugin).
|
|
67
|
+
* Everything else is kept, and a second run adds nothing.
|
|
68
|
+
*/
|
|
69
|
+
export declare function mergeOpenclawConfig(existing: Record<string, unknown>, pluginPath: string): Record<string, unknown>;
|
|
70
|
+
/**
|
|
71
|
+
* Write the OpenClaw governance plugin and list it in
|
|
72
|
+
* `~/.openclaw/openclaw.json`.
|
|
25
73
|
*
|
|
26
|
-
* Safe to call repeatedly
|
|
27
|
-
*
|
|
74
|
+
* Safe to call repeatedly: the plugin is replaced by atomic rename and the
|
|
75
|
+
* config merge is idempotent. A config file that does not parse is left as
|
|
76
|
+
* it is, and reported.
|
|
28
77
|
*
|
|
29
78
|
* @param workspaceRoot - Absolute workspace root path.
|
|
30
|
-
* @param proxyUrl - Intutic proxy URL written into
|
|
79
|
+
* @param proxyUrl - Intutic proxy URL written into the plugin header.
|
|
31
80
|
* @param workspaceId - Workspace ID embedded in every hook event payload.
|
|
32
81
|
*/
|
|
33
82
|
export declare function writeOpenclawHooks(workspaceRoot: string, proxyUrl: string, workspaceId?: string): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* OpenClaw's agent workspace, where it loads `AGENTS.md` and its other
|
|
85
|
+
* bootstrap files every session — never from a project:
|
|
86
|
+
* `agents.defaults.workspace` in `~/.openclaw/openclaw.json`, else
|
|
87
|
+
* `OPENCLAW_WORKSPACE_DIR`, else `~/.openclaw/workspace`
|
|
88
|
+
* (https://github.com/openclaw/openclaw/blob/98457908cf3e4fbbd0354b530e0aa0d15b560158/docs/gateway/config-agents/workspace-and-bootstrap.md).
|
|
89
|
+
*/
|
|
90
|
+
export declare function openclawAgentWorkspace(): Promise<string>;
|
|
34
91
|
//# sourceMappingURL=openclawHooks.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openclawHooks.d.ts","sourceRoot":"","sources":["../../src/harness/openclawHooks.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"openclawHooks.d.ts","sourceRoot":"","sources":["../../src/harness/openclawHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAaH,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,QAA0E,CAAA;AAE3G,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,wCAAwC,CAAA;AAM3E,2EAA2E;AAC3E,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAID,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAyHxG;AAID;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmBnE;AAKD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAalH;AAaD;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACtC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,IAAI,CAAC,CAyCf;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC,CAc9D"}
|
|
@@ -1,56 +1,96 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* openclawHooks.ts —
|
|
2
|
+
* openclawHooks.ts — OpenClaw plugin gate.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
4
|
+
* OpenClaw has two hook systems, and only one of them can stop a tool call.
|
|
5
|
+
* Its *internal* hooks (`hooks.internal.entries` in `~/.openclaw/openclaw.json`,
|
|
6
|
+
* `HOOK.md` handlers) react to command, session and Gateway events; the
|
|
7
|
+
* dispatcher catches a handler's error and carries on, and tool calls are not
|
|
8
|
+
* among their events at all. Earlier versions of this writer registered the
|
|
9
|
+
* gate there, as `{ event: 'PreToolUse', command }`, a shape OpenClaw keeps
|
|
10
|
+
* (hook entries are open-ended) and never runs. Tool calls are intercepted by
|
|
11
|
+
* a *plugin* hook, `before_tool_call`, registered with `api.on(...)`:
|
|
12
|
+
* https://github.com/openclaw/openclaw/blob/59309cc7777e07d52ca1be94bf8c25fd685919bc/docs/automation/hooks.md
|
|
13
|
+
* ("Choose the right surface": intercepting tools is a plugin hook).
|
|
7
14
|
*
|
|
8
|
-
*
|
|
9
|
-
* 2. Writes `~/.intutic/hooks/openclaw-check.js` — dual-path Node.js hook.
|
|
10
|
-
* 3. Writes `.intutic/env/openclaw.env` — env snippet.
|
|
11
|
-
* 4. Optionally validates with `openclaw hooks check` (non-fatal).
|
|
15
|
+
* ## The plugin
|
|
12
16
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
17
|
+
* A standalone plugin file needs no manifest when it is listed in
|
|
18
|
+
* `plugins.load.paths`; its id is the file's basename, so the file is
|
|
19
|
+
* `intutic-governance.cjs` and exports `{ id: 'intutic-governance', register }`
|
|
20
|
+
* (`docs/plugins/hooks/tool-policy.md` "Sender-aware policy in one file";
|
|
21
|
+
* `src/plugins/discovery.ts` `discoverFromPath`; the loader test "loads
|
|
22
|
+
* multiple manifestless standalone files"). `.cjs`, so the module format does
|
|
23
|
+
* not depend on any `package.json` above it. A plugin from a configured path
|
|
24
|
+
* is enabled by default unless a non-empty `plugins.allow` leaves it out
|
|
25
|
+
* (`src/plugins/config-activation-shared.ts`), so the id is added to an
|
|
26
|
+
* existing allowlist and nothing is written when there is none.
|
|
15
27
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
28
|
+
* `before_tool_call` receives `{ toolName, params, toolCallId?, runId?,
|
|
29
|
+
* derivedPaths? }` and `ctx.sessionId`, and refuses with `{ block: true,
|
|
30
|
+
* blockReason }`, which is terminal (`docs/plugins/hooks/tool-policy.md`,
|
|
31
|
+
* `src/plugins/hook-types.ts`). The global hook runner runs it fail-closed: a
|
|
32
|
+
* handler that throws or outlives its timeout blocks the call
|
|
33
|
+
* (`src/plugins/hook-runner-global.ts`: `before_tool_call: "fail-closed"`;
|
|
34
|
+
* default 15 s, `docs/plugins/hooks/reference.md`). That timeout cannot stop
|
|
35
|
+
* synchronous code, so the hook registers `timeoutMs: HOOK_TIMEOUT_SECONDS *
|
|
36
|
+
* 1000` and the gate runs under its own deadline inside it
|
|
37
|
+
* (`gateDeadlineMs('openclaw')`).
|
|
38
|
+
*
|
|
39
|
+
* The plugin embeds the shared `emitJsGate` body (`'throw'` contract) and
|
|
40
|
+
* returns OpenClaw's block result for the refusal it throws. Any other fault
|
|
41
|
+
* is a block too. MCP tools reach the hook as `<server>__<tool>`
|
|
42
|
+
* (`src/agents/agent-bundle-mcp-names.ts`, `TOOL_NAME_SEPARATOR`), which the
|
|
43
|
+
* plugin composes into `mcp__<server>__<tool>` for the allowlist.
|
|
44
|
+
*
|
|
45
|
+
* The file lives under `~/.intutic/hooks/`, a governance-protected path, so an
|
|
46
|
+
* agent cannot edit it under any gated harness.
|
|
19
47
|
*
|
|
20
48
|
* @module
|
|
21
49
|
*/
|
|
22
50
|
import * as fs from 'node:fs/promises';
|
|
23
51
|
import * as path from 'node:path';
|
|
24
52
|
import * as os from 'node:os';
|
|
25
|
-
import { exec } from 'node:child_process';
|
|
26
|
-
import { promisify } from 'node:util';
|
|
27
53
|
import { createLogger } from '@intutic/logger';
|
|
54
|
+
import { keepOriginal, writeOwnedFile } from '../disconnect/originals.js';
|
|
28
55
|
import { newIso } from '@intutic/id';
|
|
29
|
-
import {
|
|
56
|
+
import { HOOK_TIMEOUT_SECONDS } from '@intutic/shared-types';
|
|
57
|
+
import { emitJsGate, REVIEW_REQUESTS_BASENAME } from './gateBody.js';
|
|
30
58
|
const log = createLogger('sync-openclaw-hooks');
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
/** The plugin id, which OpenClaw takes from the standalone file's basename. */
|
|
60
|
+
export const OPENCLAW_PLUGIN_ID = 'intutic-governance';
|
|
61
|
+
/** The plugin file, relative to the home directory. */
|
|
62
|
+
export const OPENCLAW_PLUGIN_FILE = path.join('.intutic', 'hooks', 'openclaw', `${OPENCLAW_PLUGIN_ID}.cjs`);
|
|
63
|
+
/** The first line of the plugin's header, which disconnect recognises it by. */
|
|
64
|
+
export const OPENCLAW_PLUGIN_MARKER = 'Intutic OpenClaw governance plugin.';
|
|
65
|
+
function openclawConfigPath() {
|
|
66
|
+
return path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
67
|
+
}
|
|
68
|
+
/** The absolute path of the plugin file for the current home directory. */
|
|
69
|
+
export function openclawPluginPath() {
|
|
70
|
+
return path.join(os.homedir(), OPENCLAW_PLUGIN_FILE);
|
|
71
|
+
}
|
|
72
|
+
// ─── The plugin ──────────────────────────────────────────────────────────────
|
|
73
|
+
export function buildOpenclawPlugin(proxyUrl, workspaceRoot, workspaceId) {
|
|
74
|
+
const hookEventsLog = path.join(workspaceRoot, '.intutic', 'events', 'hook-events.jsonl');
|
|
75
|
+
return `/**
|
|
76
|
+
* ${OPENCLAW_PLUGIN_MARKER}
|
|
40
77
|
* Auto-generated by intutic sync-daemon. DO NOT EDIT.
|
|
41
78
|
* Proxy: ${proxyUrl}
|
|
42
79
|
* Generated: ${newIso()}
|
|
43
|
-
*
|
|
80
|
+
*
|
|
81
|
+
* A before_tool_call plugin hook, loaded through plugins.load.paths in
|
|
82
|
+
* ~/.openclaw/openclaw.json. Refuses a tool call by returning
|
|
83
|
+
* { block: true, blockReason }. See
|
|
84
|
+
* services/sync-daemon/src/harness/openclawHooks.ts.
|
|
44
85
|
*/
|
|
45
86
|
'use strict';
|
|
46
|
-
${emitJsFailClosedPrelude({ harness: 'openclaw', contract: 'exit2' })}
|
|
47
87
|
const path = require('path');
|
|
48
88
|
const os = require('os');
|
|
49
89
|
const fs = require('fs');
|
|
50
90
|
const crypto = require('crypto');
|
|
51
91
|
const https = require('https');
|
|
52
92
|
|
|
53
|
-
//
|
|
93
|
+
// Runtime credentials, read when OpenClaw loads the plugin.
|
|
54
94
|
const _runtimeEnvPath = path.join(os.homedir(), '.intutic', 'env', 'runtime.env');
|
|
55
95
|
let _intuticHost = 'https://api.intutic.ai', _intuticKey = '', _intuticWsId = ${JSON.stringify(workspaceId)};
|
|
56
96
|
try {
|
|
@@ -61,101 +101,103 @@ try {
|
|
|
61
101
|
if (k === 'INTUTIC_API_KEY' && v) _intuticKey = v;
|
|
62
102
|
if (k === 'INTUTIC_WORKSPACE_ID' && v) _intuticWsId = v;
|
|
63
103
|
});
|
|
64
|
-
} catch {
|
|
104
|
+
} catch {}
|
|
65
105
|
|
|
66
|
-
${emitJsGate({ harness: 'openclaw', contract: '
|
|
106
|
+
${emitJsGate({ harness: 'openclaw', contract: 'throw', reviewRequestFile: path.join(workspaceRoot, '.intutic', 'events', REVIEW_REQUESTS_BASENAME) })}
|
|
67
107
|
|
|
68
|
-
/**
|
|
69
|
-
* Appends a governance event to the local hook-events log (Path B)
|
|
70
|
-
* and fires a non-blocking HTTP POST to the control plane (Path A).
|
|
71
|
-
*/
|
|
72
108
|
let _intuticSessionId = '';
|
|
73
109
|
function logEvent(verdict, toolName, reason) {
|
|
74
110
|
try {
|
|
75
111
|
const ts = new Date().toISOString();
|
|
76
|
-
// incidentId = sha1(timestamp + toolName + workspaceId).slice(0,16)
|
|
77
112
|
const incidentId = crypto.createHash('sha1').update(ts + toolName + _intuticWsId).digest('hex').slice(0, 16);
|
|
113
|
+
// The event's id: random, made once here, and resent with the line it is
|
|
114
|
+
// written into, so the control plane processes the event once.
|
|
115
|
+
const eventId = crypto.randomBytes(16).toString('hex');
|
|
78
116
|
const entry = JSON.stringify({
|
|
79
|
-
// Passed through, not collapsed to two values: the advisory tier emits
|
|
80
|
-
// 'tool_flagged', and a ternary here silently recorded it as an allow.
|
|
81
117
|
event: verdict,
|
|
82
|
-
toolName,
|
|
83
|
-
reason: reason || '',
|
|
118
|
+
toolName, reason: reason || '',
|
|
84
119
|
workspaceId: _intuticWsId,
|
|
85
120
|
harnessType: 'openclaw',
|
|
86
121
|
timestamp: ts,
|
|
87
122
|
incidentId,
|
|
123
|
+
eventId,
|
|
88
124
|
...(_intuticSessionId ? { sessionId: _intuticSessionId } : {}),
|
|
89
125
|
}) + '\\n';
|
|
90
|
-
|
|
91
|
-
fs.
|
|
92
|
-
|
|
126
|
+
const logPath = ${JSON.stringify(hookEventsLog)};
|
|
127
|
+
try { fs.mkdirSync(path.dirname(logPath), { recursive: true }); } catch {}
|
|
128
|
+
fs.appendFileSync(logPath, entry, { flag: 'a' });
|
|
93
129
|
if (_intuticKey) {
|
|
94
130
|
try {
|
|
95
131
|
const body = JSON.stringify({ events: [JSON.parse(entry)] });
|
|
96
132
|
const urlObj = new URL('/api/v1/hook-events', _intuticHost);
|
|
97
133
|
const isHttps = urlObj.protocol === 'https:';
|
|
98
134
|
const mod = isHttps ? https : require('http');
|
|
99
|
-
const req = mod.request({
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
path: urlObj.pathname,
|
|
103
|
-
method: 'POST',
|
|
104
|
-
headers: {
|
|
105
|
-
'Content-Type': 'application/json',
|
|
106
|
-
'Content-Length': Buffer.byteLength(body),
|
|
107
|
-
'Authorization': 'Bearer ' + _intuticKey,
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
req.on('error', () => { /* fire-and-forget — ignore errors */ });
|
|
111
|
-
req.write(body);
|
|
112
|
-
req.end();
|
|
135
|
+
const req = mod.request({ hostname: urlObj.hostname, port: urlObj.port || (isHttps ? 443 : 80), path: urlObj.pathname, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), 'Authorization': 'Bearer ' + _intuticKey } });
|
|
136
|
+
req.on('error', () => { /* fire-and-forget */ });
|
|
137
|
+
req.write(body); req.end();
|
|
113
138
|
} catch { /* never crash the hook */ }
|
|
114
139
|
}
|
|
115
140
|
} catch { /* never crash the hook */ }
|
|
116
141
|
}
|
|
117
142
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
/**
|
|
144
|
+
* One tool call. Returns to allow; throws '[Intutic Governance] …' to refuse.
|
|
145
|
+
* Params that are not an object are refused outright: the gate reads paths
|
|
146
|
+
* and commands out of them, and a shape it cannot read is not one it can
|
|
147
|
+
* allow. apply_patch names its files in derivedPaths, and each is checked.
|
|
148
|
+
*/
|
|
149
|
+
function intuticEvaluate(event, ctx) {
|
|
150
|
+
_intuticSessionId = String((ctx && (ctx.sessionId || ctx.sessionKey)) || '');
|
|
151
|
+
let tool = String((event && event.toolName) || 'tool');
|
|
152
|
+
// OpenClaw names an MCP tool <server>__<tool>; the allowlist reads
|
|
153
|
+
// mcp__<server>__<tool>. No built-in tool id has a double underscore.
|
|
154
|
+
if (tool.indexOf('__') > 0 && tool.indexOf('mcp__') !== 0) tool = 'mcp__' + tool;
|
|
155
|
+
const params = event && event.params;
|
|
156
|
+
if (!params || typeof params !== 'object' || Array.isArray(params)) {
|
|
157
|
+
const reason = '[Intutic Governance] BLOCKED: tool "' + tool + '" was called with parameters the gate cannot read (' +
|
|
158
|
+
(params === null ? 'null' : typeof params) + '); refusing rather than allowing a call it cannot evaluate.';
|
|
159
|
+
try { logEvent('tool_blocked', tool, reason); } catch (e) {}
|
|
160
|
+
throw new Error(reason);
|
|
161
|
+
}
|
|
162
|
+
const command = params.command || params.cmd || params.script || '';
|
|
163
|
+
const direct = params.path || params.file_path || params.filePath || params.target || '';
|
|
164
|
+
const derived = event && Array.isArray(event.derivedPaths) ? event.derivedPaths.filter((p) => typeof p === 'string' && p) : [];
|
|
165
|
+
const targets = derived.length > 0 ? (direct ? [direct].concat(derived) : derived) : [direct];
|
|
166
|
+
for (const target of targets) intuticGate(tool, target, command, logEvent, _intuticWsId, params, _intuticSessionId);
|
|
167
|
+
logEvent('tool_allowed', tool, '');
|
|
168
|
+
}
|
|
141
169
|
|
|
142
|
-
|
|
143
|
-
|
|
170
|
+
/** Fail closed: a fault inside the gate is a refusal, never an allow. */
|
|
171
|
+
function beforeToolCall(event, ctx) {
|
|
172
|
+
try {
|
|
173
|
+
intuticEvaluate(event, ctx);
|
|
174
|
+
return undefined;
|
|
144
175
|
} catch (err) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
176
|
+
let reason = String((err && err.message) || err);
|
|
177
|
+
if (reason.indexOf('[Intutic Governance]') !== 0) {
|
|
178
|
+
reason = '[Intutic Governance] BLOCKED: gate fault (failing closed): ' + reason;
|
|
179
|
+
try { logEvent('tool_blocked', String((event && event.toolName) || 'unknown'), reason); } catch (e) {}
|
|
180
|
+
}
|
|
181
|
+
return { block: true, blockReason: reason };
|
|
149
182
|
}
|
|
150
|
-
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = {
|
|
186
|
+
id: ${JSON.stringify(OPENCLAW_PLUGIN_ID)},
|
|
187
|
+
name: 'Intutic governance',
|
|
188
|
+
description: 'Refuses tool calls that break the workspace policy before they run.',
|
|
189
|
+
register(api) {
|
|
190
|
+
api.on('before_tool_call', beforeToolCall, { timeoutMs: ${HOOK_TIMEOUT_SECONDS * 1000} });
|
|
191
|
+
},
|
|
192
|
+
};
|
|
151
193
|
`;
|
|
152
194
|
}
|
|
153
|
-
// ───
|
|
195
|
+
// ─── ~/.openclaw/openclaw.json ───────────────────────────────────────────────
|
|
154
196
|
/**
|
|
155
197
|
* Attempt to parse a JSON5-like string by progressively stripping
|
|
156
198
|
* non-standard syntax. Returns a plain JS object or throws if unparseable.
|
|
157
199
|
*/
|
|
158
|
-
function parseJson5Like(raw) {
|
|
200
|
+
export function parseJson5Like(raw) {
|
|
159
201
|
// Attempt 1: vanilla JSON
|
|
160
202
|
try {
|
|
161
203
|
return JSON.parse(raw);
|
|
@@ -175,98 +217,110 @@ function parseJson5Like(raw) {
|
|
|
175
217
|
stripped = stripped.replace(/,(\s*[}\]])/g, '$1');
|
|
176
218
|
return JSON.parse(stripped);
|
|
177
219
|
}
|
|
220
|
+
const asObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v) ? v : {};
|
|
178
221
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
222
|
+
* Adds the plugin to `plugins.load.paths`, and its id to `plugins.allow` when
|
|
223
|
+
* that list is non-empty (an empty or absent one allows every plugin).
|
|
224
|
+
* Everything else is kept, and a second run adds nothing.
|
|
181
225
|
*/
|
|
182
|
-
function mergeOpenclawConfig(existing,
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
const
|
|
226
|
+
export function mergeOpenclawConfig(existing, pluginPath) {
|
|
227
|
+
const plugins = asObject(existing.plugins);
|
|
228
|
+
const load = asObject(plugins.load);
|
|
229
|
+
const paths = Array.isArray(load.paths) ? load.paths : [];
|
|
230
|
+
const allow = Array.isArray(plugins.allow) ? plugins.allow : undefined;
|
|
186
231
|
return {
|
|
187
232
|
...existing,
|
|
188
|
-
|
|
189
|
-
...
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
entries: {
|
|
193
|
-
...entries,
|
|
194
|
-
'intutic-governance': {
|
|
195
|
-
enabled: true,
|
|
196
|
-
event: 'PreToolUse',
|
|
197
|
-
command: hookScriptPath,
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
},
|
|
233
|
+
plugins: {
|
|
234
|
+
...plugins,
|
|
235
|
+
...(allow && allow.length > 0 && !allow.includes(OPENCLAW_PLUGIN_ID) ? { allow: [...allow, OPENCLAW_PLUGIN_ID] } : {}),
|
|
236
|
+
load: { ...load, paths: paths.includes(pluginPath) ? paths : [...paths, pluginPath] },
|
|
201
237
|
},
|
|
202
238
|
};
|
|
203
239
|
}
|
|
240
|
+
/** Why the plugin will not load even once it is listed, when the config says so. */
|
|
241
|
+
function configBlocksPlugin(config) {
|
|
242
|
+
const plugins = asObject(config.plugins);
|
|
243
|
+
if (plugins.enabled === false)
|
|
244
|
+
return 'plugins.enabled is false';
|
|
245
|
+
if (Array.isArray(plugins.deny) && plugins.deny.includes(OPENCLAW_PLUGIN_ID))
|
|
246
|
+
return `plugins.deny lists ${OPENCLAW_PLUGIN_ID}`;
|
|
247
|
+
if (asObject(asObject(plugins.entries)[OPENCLAW_PLUGIN_ID]).enabled === false)
|
|
248
|
+
return `plugins.entries.${OPENCLAW_PLUGIN_ID}.enabled is false`;
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
204
251
|
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
205
252
|
/**
|
|
206
|
-
* Write the
|
|
207
|
-
*
|
|
253
|
+
* Write the OpenClaw governance plugin and list it in
|
|
254
|
+
* `~/.openclaw/openclaw.json`.
|
|
208
255
|
*
|
|
209
|
-
* Safe to call repeatedly
|
|
210
|
-
*
|
|
256
|
+
* Safe to call repeatedly: the plugin is replaced by atomic rename and the
|
|
257
|
+
* config merge is idempotent. A config file that does not parse is left as
|
|
258
|
+
* it is, and reported.
|
|
211
259
|
*
|
|
212
260
|
* @param workspaceRoot - Absolute workspace root path.
|
|
213
|
-
* @param proxyUrl - Intutic proxy URL written into
|
|
261
|
+
* @param proxyUrl - Intutic proxy URL written into the plugin header.
|
|
214
262
|
* @param workspaceId - Workspace ID embedded in every hook event payload.
|
|
215
263
|
*/
|
|
216
264
|
export async function writeOpenclawHooks(workspaceRoot, proxyUrl, workspaceId = '') {
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
fs.mkdir(globalHookDir, { recursive: true }),
|
|
224
|
-
fs.mkdir(hookEventsDir, { recursive: true }),
|
|
225
|
-
fs.mkdir(envDir, { recursive: true }),
|
|
226
|
-
fs.mkdir(openclawDir, { recursive: true }),
|
|
227
|
-
]);
|
|
228
|
-
// ── 2. Write Node.js hook script ──────────────────────────────────────────
|
|
229
|
-
const hookScriptPath = path.join(globalHookDir, 'openclaw-check.js');
|
|
230
|
-
const hookEventsLog = path.join(hookEventsDir, 'hook-events.jsonl');
|
|
231
|
-
const tmpScript = hookScriptPath + '.intutic-tmp';
|
|
232
|
-
await fs.writeFile(tmpScript, buildOpenclawCheckScript(hookEventsLog, workspaceId, proxyUrl), 'utf-8');
|
|
233
|
-
await fs.rename(tmpScript, hookScriptPath);
|
|
234
|
-
await fs.chmod(hookScriptPath, 0o755);
|
|
235
|
-
log.info({ action: 'openclaw_hook_script_written', path: hookScriptPath }, 'Openclaw hook script written');
|
|
236
|
-
// ── 3. JSON5-safe merge into ~/.openclaw/openclaw.json ────────────────────
|
|
237
|
-
let existingConfig = {};
|
|
265
|
+
await fs.mkdir(path.join(workspaceRoot, '.intutic', 'events'), { recursive: true });
|
|
266
|
+
const pluginPath = openclawPluginPath();
|
|
267
|
+
await writeOwnedFile(pluginPath, workspaceRoot, buildOpenclawPlugin(proxyUrl, workspaceRoot, workspaceId));
|
|
268
|
+
log.info({ action: 'openclaw_plugin_written', path: pluginPath }, 'OpenClaw governance plugin written');
|
|
269
|
+
const configPath = openclawConfigPath();
|
|
270
|
+
let raw = null;
|
|
238
271
|
try {
|
|
239
|
-
|
|
240
|
-
existingConfig = parseJson5Like(raw);
|
|
272
|
+
raw = await fs.readFile(configPath, 'utf-8');
|
|
241
273
|
}
|
|
242
|
-
catch {
|
|
243
|
-
|
|
274
|
+
catch (err) {
|
|
275
|
+
if (err.code !== 'ENOENT') {
|
|
276
|
+
log.warn({ action: 'config_merge_skipped', path: configPath, error: String(err) }, `Could not read ${configPath} — left untouched`);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
244
279
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
await
|
|
260
|
-
await fs.
|
|
261
|
-
|
|
262
|
-
|
|
280
|
+
let existing = {};
|
|
281
|
+
if (raw !== null && raw.trim() !== '') {
|
|
282
|
+
try {
|
|
283
|
+
existing = asObject(parseJson5Like(raw));
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
log.warn({ action: 'config_merge_skipped', path: configPath }, `${configPath} does not parse — left untouched; fix it and the next sync will list the Intutic plugin`);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const blocked = configBlocksPlugin(existing);
|
|
291
|
+
if (blocked) {
|
|
292
|
+
log.warn({ action: 'openclaw_plugin_disabled', path: configPath, reason: blocked }, `OpenClaw will not load the Intutic plugin: ${blocked}`);
|
|
293
|
+
}
|
|
294
|
+
await keepOriginal(configPath, workspaceRoot);
|
|
295
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
296
|
+
const tmp = configPath + '.intutic-tmp';
|
|
297
|
+
await fs.writeFile(tmp, JSON.stringify(mergeOpenclawConfig(existing, pluginPath), null, 2) + '\n', 'utf-8');
|
|
298
|
+
await fs.rename(tmp, configPath);
|
|
299
|
+
log.info({ action: 'openclaw_config_merged', path: configPath }, 'Listed the Intutic plugin in ~/.openclaw/openclaw.json');
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* OpenClaw's agent workspace, where it loads `AGENTS.md` and its other
|
|
303
|
+
* bootstrap files every session — never from a project:
|
|
304
|
+
* `agents.defaults.workspace` in `~/.openclaw/openclaw.json`, else
|
|
305
|
+
* `OPENCLAW_WORKSPACE_DIR`, else `~/.openclaw/workspace`
|
|
306
|
+
* (https://github.com/openclaw/openclaw/blob/98457908cf3e4fbbd0354b530e0aa0d15b560158/docs/gateway/config-agents/workspace-and-bootstrap.md).
|
|
307
|
+
*/
|
|
308
|
+
export async function openclawAgentWorkspace() {
|
|
309
|
+
const home = os.homedir();
|
|
310
|
+
const expand = (p) => (p === '~' || p.startsWith('~/') ? path.join(home, p.slice(1)) : path.resolve(p));
|
|
263
311
|
try {
|
|
264
|
-
const
|
|
265
|
-
|
|
312
|
+
const doc = parseJson5Like(await fs.readFile(path.join(home, '.openclaw', 'openclaw.json'), 'utf-8'));
|
|
313
|
+
const agents = doc.agents;
|
|
314
|
+
const configured = agents?.defaults?.workspace;
|
|
315
|
+
if (typeof configured === 'string' && configured.trim() !== '')
|
|
316
|
+
return expand(configured.trim());
|
|
266
317
|
}
|
|
267
|
-
catch
|
|
268
|
-
//
|
|
269
|
-
log.debug({ action: 'openclaw_hooks_check_skipped', err }, 'openclaw hooks check skipped (binary not in PATH or check failed)');
|
|
318
|
+
catch {
|
|
319
|
+
// No config, or one that does not parse: the defaults apply.
|
|
270
320
|
}
|
|
321
|
+
const fromEnv = process.env.OPENCLAW_WORKSPACE_DIR;
|
|
322
|
+
if (fromEnv)
|
|
323
|
+
return expand(fromEnv);
|
|
324
|
+
return path.join(home, '.openclaw', 'workspace');
|
|
271
325
|
}
|
|
272
326
|
//# sourceMappingURL=openclawHooks.js.map
|