cclaw-cli 7.7.1 → 8.1.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.
- package/README.md +211 -134
- package/dist/artifact-frontmatter.d.ts +51 -0
- package/dist/artifact-frontmatter.js +131 -0
- package/dist/artifact-paths.d.ts +7 -27
- package/dist/artifact-paths.js +20 -249
- package/dist/cancel.d.ts +16 -0
- package/dist/cancel.js +66 -0
- package/dist/cli.d.ts +2 -27
- package/dist/cli.js +107 -511
- package/dist/compound.d.ts +26 -0
- package/dist/compound.js +96 -0
- package/dist/config.d.ts +14 -51
- package/dist/config.js +23 -359
- package/dist/constants.d.ts +11 -18
- package/dist/constants.js +19 -106
- package/dist/content/antipatterns.d.ts +1 -0
- package/dist/content/antipatterns.js +109 -0
- package/dist/content/artifact-templates.d.ts +10 -0
- package/dist/content/artifact-templates.js +550 -0
- package/dist/content/cancel-command.d.ts +2 -2
- package/dist/content/cancel-command.js +25 -17
- package/dist/content/core-agents.d.ts +9 -233
- package/dist/content/core-agents.js +39 -768
- package/dist/content/decision-protocol.d.ts +1 -12
- package/dist/content/decision-protocol.js +27 -20
- package/dist/content/examples.d.ts +8 -42
- package/dist/content/examples.js +293 -425
- package/dist/content/idea-command.d.ts +2 -0
- package/dist/content/idea-command.js +38 -0
- package/dist/content/iron-laws.d.ts +4 -138
- package/dist/content/iron-laws.js +18 -197
- package/dist/content/meta-skill.d.ts +1 -3
- package/dist/content/meta-skill.js +57 -134
- package/dist/content/node-hooks.d.ts +12 -8
- package/dist/content/node-hooks.js +188 -838
- package/dist/content/recovery.d.ts +8 -0
- package/dist/content/recovery.js +179 -0
- package/dist/content/reference-patterns.d.ts +4 -13
- package/dist/content/reference-patterns.js +260 -389
- package/dist/content/research-playbooks.d.ts +8 -8
- package/dist/content/research-playbooks.js +108 -121
- package/dist/content/review-loop.d.ts +6 -192
- package/dist/content/review-loop.js +29 -731
- package/dist/content/skills.d.ts +8 -38
- package/dist/content/skills.js +681 -732
- package/dist/content/specialist-prompts/architect.d.ts +1 -0
- package/dist/content/specialist-prompts/architect.js +225 -0
- package/dist/content/specialist-prompts/brainstormer.d.ts +1 -0
- package/dist/content/specialist-prompts/brainstormer.js +168 -0
- package/dist/content/specialist-prompts/index.d.ts +2 -0
- package/dist/content/specialist-prompts/index.js +14 -0
- package/dist/content/specialist-prompts/planner.d.ts +1 -0
- package/dist/content/specialist-prompts/planner.js +182 -0
- package/dist/content/specialist-prompts/reviewer.d.ts +1 -0
- package/dist/content/specialist-prompts/reviewer.js +193 -0
- package/dist/content/specialist-prompts/security-reviewer.d.ts +1 -0
- package/dist/content/specialist-prompts/security-reviewer.js +133 -0
- package/dist/content/specialist-prompts/slice-builder.d.ts +1 -0
- package/dist/content/specialist-prompts/slice-builder.js +232 -0
- package/dist/content/stage-playbooks.d.ts +8 -0
- package/dist/content/stage-playbooks.js +404 -0
- package/dist/content/start-command.d.ts +2 -12
- package/dist/content/start-command.js +221 -207
- package/dist/flow-state.d.ts +21 -178
- package/dist/flow-state.js +67 -170
- package/dist/fs-utils.d.ts +6 -26
- package/dist/fs-utils.js +29 -162
- package/dist/gitignore.d.ts +2 -1
- package/dist/gitignore.js +51 -34
- package/dist/harness-detect.d.ts +10 -0
- package/dist/harness-detect.js +29 -0
- package/dist/harness-prompt.d.ts +26 -0
- package/dist/harness-prompt.js +142 -0
- package/dist/install.d.ts +35 -15
- package/dist/install.js +238 -1347
- package/dist/knowledge-store.d.ts +19 -163
- package/dist/knowledge-store.js +56 -590
- package/dist/logger.d.ts +8 -3
- package/dist/logger.js +13 -4
- package/dist/orchestrator-routing.d.ts +29 -0
- package/dist/orchestrator-routing.js +156 -0
- package/dist/run-persistence.d.ts +7 -118
- package/dist/run-persistence.js +29 -845
- package/dist/runtime/run-hook.entry.d.ts +1 -3
- package/dist/runtime/run-hook.entry.js +19 -4
- package/dist/runtime/run-hook.mjs +13 -1024
- package/dist/types.d.ts +25 -261
- package/dist/types.js +8 -36
- package/package.json +6 -3
- package/dist/artifact-linter/brainstorm.d.ts +0 -2
- package/dist/artifact-linter/brainstorm.js +0 -353
- package/dist/artifact-linter/design.d.ts +0 -18
- package/dist/artifact-linter/design.js +0 -444
- package/dist/artifact-linter/findings-dedup.d.ts +0 -56
- package/dist/artifact-linter/findings-dedup.js +0 -232
- package/dist/artifact-linter/plan.d.ts +0 -2
- package/dist/artifact-linter/plan.js +0 -826
- package/dist/artifact-linter/review-army.d.ts +0 -49
- package/dist/artifact-linter/review-army.js +0 -520
- package/dist/artifact-linter/review.d.ts +0 -2
- package/dist/artifact-linter/review.js +0 -113
- package/dist/artifact-linter/scope.d.ts +0 -2
- package/dist/artifact-linter/scope.js +0 -158
- package/dist/artifact-linter/shared.d.ts +0 -637
- package/dist/artifact-linter/shared.js +0 -2163
- package/dist/artifact-linter/ship.d.ts +0 -2
- package/dist/artifact-linter/ship.js +0 -250
- package/dist/artifact-linter/spec.d.ts +0 -2
- package/dist/artifact-linter/spec.js +0 -176
- package/dist/artifact-linter/tdd.d.ts +0 -118
- package/dist/artifact-linter/tdd.js +0 -1404
- package/dist/artifact-linter.d.ts +0 -15
- package/dist/artifact-linter.js +0 -517
- package/dist/codex-feature-flag.d.ts +0 -58
- package/dist/codex-feature-flag.js +0 -193
- package/dist/content/closeout-guidance.d.ts +0 -14
- package/dist/content/closeout-guidance.js +0 -44
- package/dist/content/diff-command.d.ts +0 -1
- package/dist/content/diff-command.js +0 -43
- package/dist/content/harness-doc.d.ts +0 -1
- package/dist/content/harness-doc.js +0 -65
- package/dist/content/hook-events.d.ts +0 -9
- package/dist/content/hook-events.js +0 -23
- package/dist/content/hook-manifest.d.ts +0 -81
- package/dist/content/hook-manifest.js +0 -156
- package/dist/content/hooks.d.ts +0 -11
- package/dist/content/hooks.js +0 -1972
- package/dist/content/idea.d.ts +0 -60
- package/dist/content/idea.js +0 -416
- package/dist/content/language-policy.d.ts +0 -2
- package/dist/content/language-policy.js +0 -13
- package/dist/content/learnings.d.ts +0 -6
- package/dist/content/learnings.js +0 -141
- package/dist/content/observe.d.ts +0 -19
- package/dist/content/observe.js +0 -86
- package/dist/content/opencode-plugin.d.ts +0 -1
- package/dist/content/opencode-plugin.js +0 -635
- package/dist/content/review-prompts.d.ts +0 -1
- package/dist/content/review-prompts.js +0 -104
- package/dist/content/runtime-shared-snippets.d.ts +0 -8
- package/dist/content/runtime-shared-snippets.js +0 -80
- package/dist/content/session-hooks.d.ts +0 -7
- package/dist/content/session-hooks.js +0 -107
- package/dist/content/skills-elicitation.d.ts +0 -1
- package/dist/content/skills-elicitation.js +0 -167
- package/dist/content/stage-command.d.ts +0 -2
- package/dist/content/stage-command.js +0 -17
- package/dist/content/stage-schema.d.ts +0 -117
- package/dist/content/stage-schema.js +0 -955
- package/dist/content/stages/_lint-metadata/index.d.ts +0 -2
- package/dist/content/stages/_lint-metadata/index.js +0 -97
- package/dist/content/stages/brainstorm.d.ts +0 -2
- package/dist/content/stages/brainstorm.js +0 -184
- package/dist/content/stages/design.d.ts +0 -2
- package/dist/content/stages/design.js +0 -288
- package/dist/content/stages/index.d.ts +0 -8
- package/dist/content/stages/index.js +0 -11
- package/dist/content/stages/plan.d.ts +0 -2
- package/dist/content/stages/plan.js +0 -191
- package/dist/content/stages/review.d.ts +0 -2
- package/dist/content/stages/review.js +0 -240
- package/dist/content/stages/schema-types.d.ts +0 -203
- package/dist/content/stages/schema-types.js +0 -1
- package/dist/content/stages/scope.d.ts +0 -2
- package/dist/content/stages/scope.js +0 -254
- package/dist/content/stages/ship.d.ts +0 -2
- package/dist/content/stages/ship.js +0 -159
- package/dist/content/stages/spec.d.ts +0 -2
- package/dist/content/stages/spec.js +0 -170
- package/dist/content/stages/tdd.d.ts +0 -4
- package/dist/content/stages/tdd.js +0 -273
- package/dist/content/state-contracts.d.ts +0 -1
- package/dist/content/state-contracts.js +0 -63
- package/dist/content/status-command.d.ts +0 -4
- package/dist/content/status-command.js +0 -109
- package/dist/content/subagent-context-skills.d.ts +0 -4
- package/dist/content/subagent-context-skills.js +0 -279
- package/dist/content/subagents.d.ts +0 -3
- package/dist/content/subagents.js +0 -997
- package/dist/content/templates.d.ts +0 -26
- package/dist/content/templates.js +0 -1692
- package/dist/content/track-render-context.d.ts +0 -18
- package/dist/content/track-render-context.js +0 -53
- package/dist/content/tree-command.d.ts +0 -1
- package/dist/content/tree-command.js +0 -64
- package/dist/content/utility-skills.d.ts +0 -30
- package/dist/content/utility-skills.js +0 -160
- package/dist/content/view-command.d.ts +0 -2
- package/dist/content/view-command.js +0 -92
- package/dist/delegation.d.ts +0 -649
- package/dist/delegation.js +0 -1539
- package/dist/early-loop.d.ts +0 -70
- package/dist/early-loop.js +0 -302
- package/dist/execution-topology.d.ts +0 -44
- package/dist/execution-topology.js +0 -95
- package/dist/gate-evidence.d.ts +0 -85
- package/dist/gate-evidence.js +0 -631
- package/dist/harness-adapters.d.ts +0 -151
- package/dist/harness-adapters.js +0 -756
- package/dist/harness-selection.d.ts +0 -31
- package/dist/harness-selection.js +0 -214
- package/dist/hook-schema.d.ts +0 -6
- package/dist/hook-schema.js +0 -114
- package/dist/hook-schemas/claude-hooks.v1.json +0 -10
- package/dist/hook-schemas/codex-hooks.v1.json +0 -10
- package/dist/hook-schemas/cursor-hooks.v1.json +0 -13
- package/dist/init-detect.d.ts +0 -2
- package/dist/init-detect.js +0 -50
- package/dist/internal/advance-stage/advance.d.ts +0 -89
- package/dist/internal/advance-stage/advance.js +0 -655
- package/dist/internal/advance-stage/cancel-run.d.ts +0 -8
- package/dist/internal/advance-stage/cancel-run.js +0 -19
- package/dist/internal/advance-stage/flow-state-coercion.d.ts +0 -3
- package/dist/internal/advance-stage/flow-state-coercion.js +0 -81
- package/dist/internal/advance-stage/helpers.d.ts +0 -14
- package/dist/internal/advance-stage/helpers.js +0 -145
- package/dist/internal/advance-stage/hook.d.ts +0 -8
- package/dist/internal/advance-stage/hook.js +0 -40
- package/dist/internal/advance-stage/parsers.d.ts +0 -72
- package/dist/internal/advance-stage/parsers.js +0 -357
- package/dist/internal/advance-stage/proactive-delegation-trace.d.ts +0 -24
- package/dist/internal/advance-stage/proactive-delegation-trace.js +0 -56
- package/dist/internal/advance-stage/review-loop.d.ts +0 -16
- package/dist/internal/advance-stage/review-loop.js +0 -199
- package/dist/internal/advance-stage/rewind.d.ts +0 -14
- package/dist/internal/advance-stage/rewind.js +0 -108
- package/dist/internal/advance-stage/start-flow.d.ts +0 -13
- package/dist/internal/advance-stage/start-flow.js +0 -241
- package/dist/internal/advance-stage/verify.d.ts +0 -21
- package/dist/internal/advance-stage/verify.js +0 -185
- package/dist/internal/advance-stage.d.ts +0 -7
- package/dist/internal/advance-stage.js +0 -138
- package/dist/internal/cohesion-contract-stub.d.ts +0 -24
- package/dist/internal/cohesion-contract-stub.js +0 -148
- package/dist/internal/compound-readiness.d.ts +0 -23
- package/dist/internal/compound-readiness.js +0 -102
- package/dist/internal/detect-public-api-changes.d.ts +0 -5
- package/dist/internal/detect-public-api-changes.js +0 -45
- package/dist/internal/detect-supply-chain-changes.d.ts +0 -6
- package/dist/internal/detect-supply-chain-changes.js +0 -138
- package/dist/internal/early-loop-status.d.ts +0 -7
- package/dist/internal/early-loop-status.js +0 -93
- package/dist/internal/envelope-validate.d.ts +0 -7
- package/dist/internal/envelope-validate.js +0 -66
- package/dist/internal/flow-state-repair.d.ts +0 -20
- package/dist/internal/flow-state-repair.js +0 -104
- package/dist/internal/plan-split-waves.d.ts +0 -190
- package/dist/internal/plan-split-waves.js +0 -764
- package/dist/internal/runtime-integrity.d.ts +0 -7
- package/dist/internal/runtime-integrity.js +0 -268
- package/dist/internal/slice-commit.d.ts +0 -7
- package/dist/internal/slice-commit.js +0 -619
- package/dist/internal/tdd-loop-status.d.ts +0 -14
- package/dist/internal/tdd-loop-status.js +0 -68
- package/dist/internal/tdd-red-evidence.d.ts +0 -7
- package/dist/internal/tdd-red-evidence.js +0 -153
- package/dist/internal/waiver-grant.d.ts +0 -62
- package/dist/internal/waiver-grant.js +0 -294
- package/dist/internal/wave-status.d.ts +0 -74
- package/dist/internal/wave-status.js +0 -506
- package/dist/managed-resources.d.ts +0 -53
- package/dist/managed-resources.js +0 -313
- package/dist/policy.d.ts +0 -10
- package/dist/policy.js +0 -167
- package/dist/retro-gate.d.ts +0 -9
- package/dist/retro-gate.js +0 -47
- package/dist/run-archive.d.ts +0 -61
- package/dist/run-archive.js +0 -391
- package/dist/runs.d.ts +0 -2
- package/dist/runs.js +0 -2
- package/dist/stack-detection.d.ts +0 -116
- package/dist/stack-detection.js +0 -489
- package/dist/streaming/event-stream.d.ts +0 -31
- package/dist/streaming/event-stream.js +0 -114
- package/dist/tdd-cycle.d.ts +0 -107
- package/dist/tdd-cycle.js +0 -289
- package/dist/tdd-verification-evidence.d.ts +0 -17
- package/dist/tdd-verification-evidence.js +0 -122
- package/dist/track-heuristics.d.ts +0 -27
- package/dist/track-heuristics.js +0 -154
- package/dist/util/slice-id.d.ts +0 -58
- package/dist/util/slice-id.js +0 -89
- package/dist/worktree-manager.d.ts +0 -20
- package/dist/worktree-manager.js +0 -108
|
@@ -1,635 +0,0 @@
|
|
|
1
|
-
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
|
-
import { META_SKILL_NAME } from "./meta-skill.js";
|
|
3
|
-
import { SHARED_FLOW_AND_KNOWLEDGE_SNIPPETS, SHARED_STAGE_SUPPORT_SNIPPETS } from "./runtime-shared-snippets.js";
|
|
4
|
-
export function opencodePluginJs(_options = {}) {
|
|
5
|
-
return `// cclaw OpenCode plugin — generated by npx cclaw-cli sync
|
|
6
|
-
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
7
|
-
import { readFile, stat } from "node:fs/promises";
|
|
8
|
-
import { basename, join } from "node:path";
|
|
9
|
-
|
|
10
|
-
export default function cclawPlugin(ctx) {
|
|
11
|
-
const root = ctx.directory || process.cwd();
|
|
12
|
-
const runtimeDir = join(root, "${RUNTIME_ROOT}");
|
|
13
|
-
const stateDir = join(runtimeDir, "state");
|
|
14
|
-
const logsDir = join(runtimeDir, "logs");
|
|
15
|
-
const pluginLogPath = join(logsDir, "opencode-plugin.log");
|
|
16
|
-
const configPath = join(runtimeDir, "config.yaml");
|
|
17
|
-
const flowStatePath = join(stateDir, "flow-state.json");
|
|
18
|
-
const knowledgePath = join(runtimeDir, "knowledge.jsonl");
|
|
19
|
-
const metaSkillPath = join(runtimeDir, "skills/${META_SKILL_NAME}/SKILL.md");
|
|
20
|
-
${SHARED_FLOW_AND_KNOWLEDGE_SNIPPETS}
|
|
21
|
-
${SHARED_STAGE_SUPPORT_SNIPPETS}
|
|
22
|
-
|
|
23
|
-
function ensureRuntimeDirs() {
|
|
24
|
-
try {
|
|
25
|
-
mkdirSync(runtimeDir, { recursive: true });
|
|
26
|
-
} catch {
|
|
27
|
-
// ignore
|
|
28
|
-
}
|
|
29
|
-
try {
|
|
30
|
-
mkdirSync(stateDir, { recursive: true });
|
|
31
|
-
} catch {
|
|
32
|
-
// ignore
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Diagnostic log used instead of console.error on the hot path.
|
|
38
|
-
* Writing to a file keeps hook failures and unknown events from
|
|
39
|
-
* racing OpenCode's TUI renderer (which causes the overlapping-text
|
|
40
|
-
* artifact users have reported). Best-effort: any I/O failure is
|
|
41
|
-
* swallowed so logging never itself blocks or throws.
|
|
42
|
-
*/
|
|
43
|
-
function logToFile(line) {
|
|
44
|
-
try {
|
|
45
|
-
mkdirSync(logsDir, { recursive: true });
|
|
46
|
-
} catch {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
const timestamp = new Date().toISOString();
|
|
50
|
-
try {
|
|
51
|
-
appendFileSync(pluginLogPath, timestamp + " " + line + "\\n", "utf8");
|
|
52
|
-
} catch {
|
|
53
|
-
// ignore — never let logging fail the hook
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function readFlowState() {
|
|
58
|
-
try {
|
|
59
|
-
const raw = await readFile(flowStatePath, "utf8");
|
|
60
|
-
return summarizeFlowState(JSON.parse(raw));
|
|
61
|
-
} catch {
|
|
62
|
-
return summarizeFlowState({});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function readFileText(filePath) {
|
|
67
|
-
try {
|
|
68
|
-
return await readFile(filePath, "utf8");
|
|
69
|
-
} catch {
|
|
70
|
-
return "";
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function readKnowledgeDigest(stage) {
|
|
75
|
-
const raw = await readFileText(knowledgePath);
|
|
76
|
-
return parseKnowledgeDigest(raw, stage, 6).lines;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function readStageSupportContext(stage) {
|
|
80
|
-
if (!isKnownStageId(stage)) return [];
|
|
81
|
-
const parts = [];
|
|
82
|
-
const contract = (await readFileText(join(runtimeDir, "templates/state-contracts", stage + ".json"))).trim();
|
|
83
|
-
if (contract.length > 0) {
|
|
84
|
-
parts.push(
|
|
85
|
-
"Current stage state contract (read before drafting or editing the stage artifact):\\n" +
|
|
86
|
-
contract
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
const reviewPromptName = reviewPromptFileName(stage);
|
|
90
|
-
if (reviewPromptName) {
|
|
91
|
-
const prompt = (await readFileText(join(runtimeDir, "skills/review-prompts", reviewPromptName))).trim();
|
|
92
|
-
if (prompt.length > 0) {
|
|
93
|
-
parts.push(
|
|
94
|
-
"Current stage calibrated review prompt (use before asking for approval/completion):\\n" +
|
|
95
|
-
prompt
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return parts;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const BOOTSTRAP_MARKER = "<!-- cclaw-bootstrap-v1 -->";
|
|
103
|
-
|
|
104
|
-
async function buildBootstrap() {
|
|
105
|
-
const flow = await readFlowState();
|
|
106
|
-
const parts = [
|
|
107
|
-
BOOTSTRAP_MARKER,
|
|
108
|
-
\`cclaw loaded. Flow: stage=\${flow.stage} (\${flow.completed}/8 completed, run=\${flow.activeRunId}). Active artifacts: \${activeArtifactsPathLabel("${RUNTIME_ROOT}")}\`
|
|
109
|
-
];
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const knowledge = await readKnowledgeDigest(flow.stage);
|
|
114
|
-
if (knowledge.length > 0) parts.push("Knowledge digest (top relevant entries):", ...knowledge);
|
|
115
|
-
|
|
116
|
-
const stageSupport = await readStageSupportContext(flow.stage);
|
|
117
|
-
if (stageSupport.length > 0) parts.push(...stageSupport);
|
|
118
|
-
|
|
119
|
-
parts.push(
|
|
120
|
-
"If you discover a non-obvious rule or pattern during stage work, add it to the current artifact ## Learnings section; stage-complete harvests it into .cclaw/knowledge.jsonl. If this plugin does not load, run \`npx cclaw-cli sync\`, verify opencode.json(.c) includes the cclaw plugin registration, then run \`npx cclaw-cli sync\`. Direct JSONL append is only for explicit manual learnings operations."
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
const meta = (await readFileText(metaSkillPath)).trim();
|
|
124
|
-
if (meta) parts.push("", meta);
|
|
125
|
-
return parts.join("\\n");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
let bootstrapCache = "";
|
|
129
|
-
let bootstrapMtimes = new Map();
|
|
130
|
-
let bootstrapRefreshPromise = null;
|
|
131
|
-
const BOOTSTRAP_SOURCE_PATHS = [
|
|
132
|
-
flowStatePath,
|
|
133
|
-
knowledgePath,
|
|
134
|
-
metaSkillPath,
|
|
135
|
-
...STAGE_IDS.map((stage) => join(runtimeDir, "templates/state-contracts", stage + ".json")),
|
|
136
|
-
...REVIEW_PROMPT_FILES.map((file) => join(runtimeDir, "skills/review-prompts", file))
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
async function readMtimeMs(filePath) {
|
|
140
|
-
try {
|
|
141
|
-
const st = await stat(filePath);
|
|
142
|
-
return Number.isFinite(st.mtimeMs) ? st.mtimeMs : 0;
|
|
143
|
-
} catch {
|
|
144
|
-
return 0;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async function snapshotBootstrapMtimes() {
|
|
149
|
-
const next = new Map();
|
|
150
|
-
for (const filePath of BOOTSTRAP_SOURCE_PATHS) {
|
|
151
|
-
next.set(filePath, await readMtimeMs(filePath));
|
|
152
|
-
}
|
|
153
|
-
return next;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async function bootstrapNeedsRefresh() {
|
|
157
|
-
if (!bootstrapCache) return true;
|
|
158
|
-
for (const filePath of BOOTSTRAP_SOURCE_PATHS) {
|
|
159
|
-
const prev = bootstrapMtimes.get(filePath) ?? 0;
|
|
160
|
-
const now = await readMtimeMs(filePath);
|
|
161
|
-
if (prev !== now) return true;
|
|
162
|
-
}
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async function refreshBootstrapCache(force = false) {
|
|
167
|
-
if (!force && !(await bootstrapNeedsRefresh())) {
|
|
168
|
-
return bootstrapCache;
|
|
169
|
-
}
|
|
170
|
-
if (bootstrapRefreshPromise) {
|
|
171
|
-
return bootstrapRefreshPromise;
|
|
172
|
-
}
|
|
173
|
-
bootstrapRefreshPromise = (async () => {
|
|
174
|
-
const nextBootstrap = await buildBootstrap();
|
|
175
|
-
const nextMtimes = await snapshotBootstrapMtimes();
|
|
176
|
-
bootstrapCache = nextBootstrap;
|
|
177
|
-
bootstrapMtimes = nextMtimes;
|
|
178
|
-
return bootstrapCache;
|
|
179
|
-
})();
|
|
180
|
-
try {
|
|
181
|
-
return await bootstrapRefreshPromise;
|
|
182
|
-
} finally {
|
|
183
|
-
bootstrapRefreshPromise = null;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function getBootstrap() {
|
|
188
|
-
return bootstrapCache;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const MAX_CONCURRENT_HOOKS = 2;
|
|
192
|
-
let runningHookTasks = 0;
|
|
193
|
-
const pendingHookTasks = [];
|
|
194
|
-
|
|
195
|
-
function runNextHookTask() {
|
|
196
|
-
if (runningHookTasks >= MAX_CONCURRENT_HOOKS) return;
|
|
197
|
-
const queued = pendingHookTasks.shift();
|
|
198
|
-
if (!queued) return;
|
|
199
|
-
runningHookTasks += 1;
|
|
200
|
-
queued()
|
|
201
|
-
.catch(() => false)
|
|
202
|
-
.finally(() => {
|
|
203
|
-
runningHookTasks -= 1;
|
|
204
|
-
runNextHookTask();
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function scheduleHookTask(task) {
|
|
209
|
-
return new Promise((resolve) => {
|
|
210
|
-
const wrapped = async () => {
|
|
211
|
-
try {
|
|
212
|
-
resolve(await task());
|
|
213
|
-
} catch {
|
|
214
|
-
resolve(false);
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
pendingHookTasks.push(wrapped);
|
|
218
|
-
runNextHookTask();
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const lastHookStderr = new Map();
|
|
223
|
-
function recordHookStderr(hookName, stderr) {
|
|
224
|
-
if (typeof hookName !== "string" || hookName.length === 0) return;
|
|
225
|
-
const trimmed = typeof stderr === "string" ? stderr.trim() : "";
|
|
226
|
-
if (trimmed.length === 0) {
|
|
227
|
-
lastHookStderr.delete(hookName);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
lastHookStderr.set(hookName, trimmed);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* A hook process can exit non-zero for two very different reasons:
|
|
235
|
-
* (a) the guard legitimately decided to refuse an operation
|
|
236
|
-
* (strict-mode refusal — this is the *only* case we ever want
|
|
237
|
-
* to surface as a block to the user), or
|
|
238
|
-
* (b) the hook infrastructure itself failed — the runtime crashed,
|
|
239
|
-
* a child binary was missing, stderr is a chunk of yargs help
|
|
240
|
-
* from some unrelated process, timeout, etc.
|
|
241
|
-
*
|
|
242
|
-
* Treating (b) as a block is what produces the "guard blocked
|
|
243
|
-
* tool.execute.before" error on a user who did nothing wrong. The
|
|
244
|
-
* heuristic below trims guaranteed-infra signals so only cleanly
|
|
245
|
-
* structured guard output is eligible for a real strict-mode block.
|
|
246
|
-
*/
|
|
247
|
-
const INFRA_NOISE_PATTERNS = [
|
|
248
|
-
/^\\s*(Usage|Options|Commands|Examples|Positionals|Aliases):/im,
|
|
249
|
-
/^\\s*--[a-z][a-z0-9-]*\\b.*\\[(string|boolean|number|array)\\]/im,
|
|
250
|
-
/\\bcommand (not found|failed)\\b/i,
|
|
251
|
-
/\\bno such file or directory\\b/i,
|
|
252
|
-
/\\bCannot find module\\b/i,
|
|
253
|
-
/\\bThrowsCompletion\\b/,
|
|
254
|
-
/\\b(Reference|Syntax|Type|Range)Error\\b/,
|
|
255
|
-
/^\\s*at [^\\n]+\\([^)]*:\\d+:\\d+\\)/im,
|
|
256
|
-
/^\\s*node:internal\\b/im
|
|
257
|
-
];
|
|
258
|
-
function looksLikeInfrastructureFailure(stderr) {
|
|
259
|
-
if (typeof stderr !== "string") return true;
|
|
260
|
-
const trimmed = stderr.trim();
|
|
261
|
-
if (trimmed.length === 0) return true;
|
|
262
|
-
for (const pattern of INFRA_NOISE_PATTERNS) {
|
|
263
|
-
if (pattern.test(trimmed)) return true;
|
|
264
|
-
}
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function resolveNodeExecutable() {
|
|
269
|
-
const override = typeof process.env.CCLAW_NODE_EXECUTABLE === "string"
|
|
270
|
-
? process.env.CCLAW_NODE_EXECUTABLE.trim()
|
|
271
|
-
: "";
|
|
272
|
-
if (override.length > 0) return override;
|
|
273
|
-
|
|
274
|
-
const execName = basename(process.execPath || "").toLowerCase();
|
|
275
|
-
if (execName === "node" || execName === "node.exe") {
|
|
276
|
-
return process.execPath;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// OpenCode can host plugins from its own CLI binary, making
|
|
280
|
-
// process.execPath point at opencode instead of Node. Fall back to the
|
|
281
|
-
// user's Node on PATH so generated cclaw hooks execute as JavaScript.
|
|
282
|
-
return "node";
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
async function runHookScript(hookName, payload = {}) {
|
|
286
|
-
const { spawn } = await import("node:child_process");
|
|
287
|
-
const hookRuntimePath = join(root, "${RUNTIME_ROOT}/hooks/run-hook.mjs");
|
|
288
|
-
const input = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
289
|
-
return scheduleHookTask(() => new Promise((resolve) => {
|
|
290
|
-
let stderr = "";
|
|
291
|
-
let settled = false;
|
|
292
|
-
const finish = (ok) => {
|
|
293
|
-
if (settled) return;
|
|
294
|
-
settled = true;
|
|
295
|
-
recordHookStderr(hookName, stderr);
|
|
296
|
-
resolve(ok);
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
let child;
|
|
300
|
-
try {
|
|
301
|
-
child = spawn(resolveNodeExecutable(), [hookRuntimePath, hookName], {
|
|
302
|
-
cwd: root,
|
|
303
|
-
stdio: ["pipe", "ignore", "pipe"]
|
|
304
|
-
});
|
|
305
|
-
} catch {
|
|
306
|
-
finish(false);
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Tool.execute.before is a user-facing hot path: 20s is far too
|
|
311
|
-
// long to wait on a guard. 5s gives the hook real breathing room
|
|
312
|
-
// (typical runtime is well under 500ms) while capping the worst-
|
|
313
|
-
// case stall at a number the user will still tolerate.
|
|
314
|
-
const timer = setTimeout(() => {
|
|
315
|
-
child.kill("SIGKILL");
|
|
316
|
-
if (stderr.length > 0) {
|
|
317
|
-
logToFile("hook timeout: " + hookName + " stderr=" + stderr.slice(-1200));
|
|
318
|
-
} else {
|
|
319
|
-
logToFile("hook timeout: " + hookName + " (no stderr)");
|
|
320
|
-
}
|
|
321
|
-
finish(false);
|
|
322
|
-
}, 5_000);
|
|
323
|
-
|
|
324
|
-
child.stderr?.on("data", (chunk) => {
|
|
325
|
-
stderr += String(chunk ?? "");
|
|
326
|
-
if (stderr.length > 4000) {
|
|
327
|
-
stderr = stderr.slice(-4000);
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
child.on("error", () => {
|
|
331
|
-
clearTimeout(timer);
|
|
332
|
-
finish(false);
|
|
333
|
-
});
|
|
334
|
-
child.on("close", (code) => {
|
|
335
|
-
clearTimeout(timer);
|
|
336
|
-
const ok = code === 0;
|
|
337
|
-
if (!ok && stderr.length > 0) {
|
|
338
|
-
logToFile("hook failed: " + hookName + " exit=" + code + " stderr=" + stderr.slice(-1200));
|
|
339
|
-
}
|
|
340
|
-
finish(ok);
|
|
341
|
-
});
|
|
342
|
-
if (child.stdin) {
|
|
343
|
-
child.stdin.on("error", (error) => {
|
|
344
|
-
const code =
|
|
345
|
-
error && typeof error === "object" && "code" in error
|
|
346
|
-
? String(error.code)
|
|
347
|
-
: "";
|
|
348
|
-
if (code === "EPIPE" || code === "ERR_STREAM_DESTROYED") {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
clearTimeout(timer);
|
|
352
|
-
finish(false);
|
|
353
|
-
});
|
|
354
|
-
try {
|
|
355
|
-
child.stdin.end(input);
|
|
356
|
-
} catch (error) {
|
|
357
|
-
const code =
|
|
358
|
-
error && typeof error === "object" && "code" in error
|
|
359
|
-
? String(error.code)
|
|
360
|
-
: "";
|
|
361
|
-
if (code !== "EPIPE" && code !== "ERR_STREAM_DESTROYED") {
|
|
362
|
-
clearTimeout(timer);
|
|
363
|
-
finish(false);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}));
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function normalizeToolPayload(input, output) {
|
|
371
|
-
if (typeof output === "undefined") return input ?? {};
|
|
372
|
-
return { input: input ?? {}, output: output ?? {} };
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Read-only tools cannot mutate state or execute arbitrary code, so
|
|
377
|
-
* running prompt/workflow guards on them is pure overhead and — worse —
|
|
378
|
-
* surfaces a hard block when guards are misconfigured. OpenCode tool
|
|
379
|
-
* names vary (Claude/Codex use PascalCase; opencode-native often uses
|
|
380
|
-
* lowercase), so we normalize and match against a tight allow-list.
|
|
381
|
-
* Anything not in this list (bash, edit, write, patch, task, run, …)
|
|
382
|
-
* still runs through guards.
|
|
383
|
-
*/
|
|
384
|
-
const SAFE_READONLY_TOOLS = new Set([
|
|
385
|
-
// Filesystem / search reads — no state mutation possible.
|
|
386
|
-
"read",
|
|
387
|
-
"glob",
|
|
388
|
-
"grep",
|
|
389
|
-
"list",
|
|
390
|
-
"ls",
|
|
391
|
-
"view",
|
|
392
|
-
"find",
|
|
393
|
-
// Network reads — no local state mutation.
|
|
394
|
-
"webfetch",
|
|
395
|
-
"websearch",
|
|
396
|
-
// User-facing question / ask tools: they only ask the human for
|
|
397
|
-
// input and cannot touch the filesystem or execute code. Blocking
|
|
398
|
-
// them strands the plugin mid-decision (see OpenCode's \`question\`).
|
|
399
|
-
"question",
|
|
400
|
-
"ask",
|
|
401
|
-
"askuser",
|
|
402
|
-
"askquestion",
|
|
403
|
-
"ask_question",
|
|
404
|
-
"ask_user",
|
|
405
|
-
"ask_user_question",
|
|
406
|
-
"askuserquestion",
|
|
407
|
-
"request_user_input",
|
|
408
|
-
"requestuserinput",
|
|
409
|
-
"prompt",
|
|
410
|
-
// Thinking / scratchpad tools — pure reasoning with no side effects.
|
|
411
|
-
"think",
|
|
412
|
-
"thinking",
|
|
413
|
-
// Todo bookkeeping tools — they write only inside the harness's own
|
|
414
|
-
// session state, not project files, and blocking them breaks agent
|
|
415
|
-
// planning without protecting anything.
|
|
416
|
-
"todo",
|
|
417
|
-
"todoread",
|
|
418
|
-
"todowrite",
|
|
419
|
-
"todo_read",
|
|
420
|
-
"todo_write"
|
|
421
|
-
]);
|
|
422
|
-
|
|
423
|
-
function isSafeReadOnlyTool(payload) {
|
|
424
|
-
if (!payload || typeof payload !== "object") return false;
|
|
425
|
-
const candidates = [
|
|
426
|
-
payload.tool,
|
|
427
|
-
payload.name,
|
|
428
|
-
payload.tool_name,
|
|
429
|
-
payload.toolName
|
|
430
|
-
];
|
|
431
|
-
const inner = payload.input;
|
|
432
|
-
if (inner && typeof inner === "object") {
|
|
433
|
-
candidates.push(inner.tool, inner.name, inner.tool_name, inner.toolName);
|
|
434
|
-
}
|
|
435
|
-
for (const candidate of candidates) {
|
|
436
|
-
if (typeof candidate !== "string" || candidate.length === 0) continue;
|
|
437
|
-
if (SAFE_READONLY_TOOLS.has(candidate.toLowerCase())) return true;
|
|
438
|
-
}
|
|
439
|
-
return false;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* Strictness derived from (in order of precedence): CCLAW_STRICTNESS
|
|
444
|
-
* env override, \`.cclaw/config.yaml\` key \`strictness\`, or the
|
|
445
|
-
* library default of "advisory". The plugin only ever *blocks* tool
|
|
446
|
-
* execution when strictness resolves to "strict"; in advisory mode
|
|
447
|
-
* guard failures are logged and the tool call proceeds. This mirrors
|
|
448
|
-
* the Ralph-loop / hook-runtime semantics of
|
|
449
|
-
* \`DEFAULT_STRICTNESS = advisory\`, so the plugin can no longer
|
|
450
|
-
* accidentally be the stricter half of a mismatched pair.
|
|
451
|
-
*/
|
|
452
|
-
async function readConfigStrictness() {
|
|
453
|
-
try {
|
|
454
|
-
if (!existsSync(configPath)) return "";
|
|
455
|
-
const raw = await readFileText(configPath);
|
|
456
|
-
if (typeof raw !== "string" || raw.length === 0) return "";
|
|
457
|
-
const match = raw.match(/^\\s*strictness\\s*:\\s*([A-Za-z0-9_-]+)/m);
|
|
458
|
-
return match && typeof match[1] === "string" ? match[1].trim().toLowerCase() : "";
|
|
459
|
-
} catch {
|
|
460
|
-
return "";
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
async function resolveStrictness() {
|
|
465
|
-
const envRaw = typeof process.env.CCLAW_STRICTNESS === "string"
|
|
466
|
-
? process.env.CCLAW_STRICTNESS.trim().toLowerCase()
|
|
467
|
-
: "";
|
|
468
|
-
if (envRaw === "strict") return "strict";
|
|
469
|
-
if (envRaw === "advisory" || envRaw === "off" || envRaw === "disabled" || envRaw === "none") {
|
|
470
|
-
return "advisory";
|
|
471
|
-
}
|
|
472
|
-
const fileRaw = await readConfigStrictness();
|
|
473
|
-
if (fileRaw === "strict") return "strict";
|
|
474
|
-
return "advisory";
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* cclaw considers itself "active" in a project when both the state
|
|
479
|
-
* file and the hook runtime script exist. If either is missing the
|
|
480
|
-
* plugin behaves as a no-op for guards — this project hasn't been
|
|
481
|
-
* initialized (or the install is corrupt) and blocking every tool
|
|
482
|
-
* call would strand the user.
|
|
483
|
-
*/
|
|
484
|
-
function isCclawInitialized() {
|
|
485
|
-
try {
|
|
486
|
-
const hookRuntimePath = join(runtimeDir, "hooks/run-hook.mjs");
|
|
487
|
-
return existsSync(flowStatePath) && existsSync(hookRuntimePath);
|
|
488
|
-
} catch {
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
let notInitializedAdvised = false;
|
|
494
|
-
function noteNotInitialized() {
|
|
495
|
-
if (notInitializedAdvised) return;
|
|
496
|
-
notInitializedAdvised = true;
|
|
497
|
-
logToFile(
|
|
498
|
-
"guards skipped: cclaw is not initialized in this project. " +
|
|
499
|
-
"Run \`npx cclaw-cli init\` in " + root + " to activate flow enforcement."
|
|
500
|
-
);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Escape hatch for a user stuck behind a misbehaving guard chain.
|
|
505
|
-
* Reads a small set of env vars that all mean "turn cclaw off for this
|
|
506
|
-
* session": CCLAW_DISABLE=1 (primary), CCLAW_STRICTNESS=off, or
|
|
507
|
-
* CCLAW_GUARDS=off. Anything truthy disables both guards and the
|
|
508
|
-
* advisory path. Logged once so users can confirm the bypass is in
|
|
509
|
-
* effect without cluttering the TUI.
|
|
510
|
-
*/
|
|
511
|
-
const DISABLE_ENV_KEYS = ["CCLAW_DISABLE", "CCLAW_GUARDS", "CCLAW_STRICTNESS"];
|
|
512
|
-
let disabledAdvised = false;
|
|
513
|
-
function isCclawDisabled() {
|
|
514
|
-
for (const key of DISABLE_ENV_KEYS) {
|
|
515
|
-
const raw = process.env[key];
|
|
516
|
-
if (typeof raw !== "string") continue;
|
|
517
|
-
const value = raw.trim().toLowerCase();
|
|
518
|
-
if (value.length === 0) continue;
|
|
519
|
-
if (key === "CCLAW_STRICTNESS") {
|
|
520
|
-
if (value === "off" || value === "disabled" || value === "none") {
|
|
521
|
-
return { disabled: true, key, value };
|
|
522
|
-
}
|
|
523
|
-
continue;
|
|
524
|
-
}
|
|
525
|
-
if (
|
|
526
|
-
value === "1" ||
|
|
527
|
-
value === "true" ||
|
|
528
|
-
value === "yes" ||
|
|
529
|
-
value === "on" ||
|
|
530
|
-
value === "off" ||
|
|
531
|
-
value === "disabled"
|
|
532
|
-
) {
|
|
533
|
-
if (key === "CCLAW_GUARDS" && (value === "on" || value === "true" || value === "yes" || value === "1")) {
|
|
534
|
-
continue;
|
|
535
|
-
}
|
|
536
|
-
return { disabled: true, key, value };
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
return { disabled: false, key: "", value: "" };
|
|
540
|
-
}
|
|
541
|
-
function noteDisabled(reason) {
|
|
542
|
-
if (disabledAdvised) return;
|
|
543
|
-
disabledAdvised = true;
|
|
544
|
-
logToFile(
|
|
545
|
-
"guards disabled by env " + reason.key + "=" + reason.value + ". " +
|
|
546
|
-
"All tool calls will pass through without prompt/workflow checks."
|
|
547
|
-
);
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
function resolveEventType(payload) {
|
|
551
|
-
if (typeof payload === "string") return payload;
|
|
552
|
-
if (payload && typeof payload === "object") {
|
|
553
|
-
if (typeof payload.type === "string") return payload.type;
|
|
554
|
-
if (typeof payload.eventType === "string") return payload.eventType;
|
|
555
|
-
if (typeof payload.kind === "string") return payload.kind;
|
|
556
|
-
if (typeof payload.topic === "string") return payload.topic;
|
|
557
|
-
if (typeof payload.name === "string") return payload.name;
|
|
558
|
-
if (payload.event && typeof payload.event === "object") {
|
|
559
|
-
if (typeof payload.event.type === "string") return payload.event.type;
|
|
560
|
-
if (typeof payload.event.eventType === "string") return payload.event.eventType;
|
|
561
|
-
if (typeof payload.event.kind === "string") return payload.event.kind;
|
|
562
|
-
if (typeof payload.event.topic === "string") return payload.event.topic;
|
|
563
|
-
if (typeof payload.event.name === "string") return payload.event.name;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
return "";
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function resolveEventData(payload) {
|
|
570
|
-
if (payload && typeof payload === "object") {
|
|
571
|
-
if (payload.event && typeof payload.event === "object") {
|
|
572
|
-
if (payload.event.data && typeof payload.event.data === "object") {
|
|
573
|
-
return payload.event.data;
|
|
574
|
-
}
|
|
575
|
-
if (payload.event.payload && typeof payload.event.payload === "object") {
|
|
576
|
-
return payload.event.payload;
|
|
577
|
-
}
|
|
578
|
-
return payload.event;
|
|
579
|
-
}
|
|
580
|
-
if (payload.data && typeof payload.data === "object") {
|
|
581
|
-
return payload.data;
|
|
582
|
-
}
|
|
583
|
-
if (payload.payload && typeof payload.payload === "object") {
|
|
584
|
-
return payload.payload;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
return payload;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
ensureRuntimeDirs();
|
|
591
|
-
void refreshBootstrapCache(true);
|
|
592
|
-
|
|
593
|
-
return {
|
|
594
|
-
event: async (payload) => {
|
|
595
|
-
const eventType = resolveEventType(payload);
|
|
596
|
-
const eventData = resolveEventData(payload);
|
|
597
|
-
if (!eventType) {
|
|
598
|
-
const keys =
|
|
599
|
-
payload && typeof payload === "object"
|
|
600
|
-
? Object.keys(payload).slice(0, 10).join(", ")
|
|
601
|
-
: typeof payload;
|
|
602
|
-
logToFile("unknown event payload keys: " + keys);
|
|
603
|
-
}
|
|
604
|
-
const isSessionLifecycle =
|
|
605
|
-
eventType === "session.created" ||
|
|
606
|
-
eventType === "session.resumed" ||
|
|
607
|
-
eventType === "session.compacted" ||
|
|
608
|
-
eventType === "session.cleared" ||
|
|
609
|
-
eventType === "session.updated";
|
|
610
|
-
if (isSessionLifecycle) {
|
|
611
|
-
// Keep OpenCode aligned with Claude/Cursor/Codex: session-start is
|
|
612
|
-
// the canonical rehydrate path.
|
|
613
|
-
await runHookScript("session-start", eventData ?? {});
|
|
614
|
-
await refreshBootstrapCache(true);
|
|
615
|
-
}
|
|
616
|
-
if (eventType === "session.idle") {
|
|
617
|
-
await runHookScript("stop-handoff", { loop_count: 0 });
|
|
618
|
-
}
|
|
619
|
-
},
|
|
620
|
-
"experimental.chat.system.transform": (payload) => {
|
|
621
|
-
const bootstrap = getBootstrap();
|
|
622
|
-
if (!bootstrap) return payload;
|
|
623
|
-
if (typeof payload === "string") {
|
|
624
|
-
return payload.includes(BOOTSTRAP_MARKER) ? payload : \`\${payload}\\n\\n\${bootstrap}\`;
|
|
625
|
-
}
|
|
626
|
-
if (payload && typeof payload === "object" && typeof payload.system === "string") {
|
|
627
|
-
if (payload.system.includes(BOOTSTRAP_MARKER)) return payload;
|
|
628
|
-
return { ...payload, system: \`\${payload.system}\\n\\n\${bootstrap}\` };
|
|
629
|
-
}
|
|
630
|
-
return payload;
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
`;
|
|
635
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const REVIEW_PROMPTS: Record<string, string>;
|