@litfamily/litopencode 1.0.29 → 1.0.31
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/CHANGELOG.md +22 -0
- package/README-Ko-KR.md +7 -7
- package/README.md +7 -7
- package/dist/activation-primary-prompts.js +15 -6
- package/dist/activation-prompt-utils.d.ts +2 -2
- package/dist/activation-prompt-utils.js +9 -3
- package/dist/activation-reentry.d.ts +29 -0
- package/dist/activation-reentry.js +71 -0
- package/dist/activation-routing.d.ts +1 -1
- package/dist/activation-routing.js +40 -7
- package/dist/activation-workflow-prompts.d.ts +2 -0
- package/dist/activation-workflow-prompts.js +32 -7
- package/dist/activation.d.ts +6 -1
- package/dist/activation.js +34 -7
- package/dist/cli/args.js +14 -0
- package/dist/cli/auto-update.js +2 -4
- package/dist/cli/command-plan.d.ts +40 -0
- package/dist/cli/command-plan.js +62 -0
- package/dist/cli/doctor.d.ts +4 -1
- package/dist/cli/doctor.js +65 -4
- package/dist/cli/host-detect.d.ts +58 -0
- package/dist/cli/host-detect.js +199 -0
- package/dist/cli/install-report.js +3 -1
- package/dist/cli/install.d.ts +9 -1
- package/dist/cli/install.js +134 -31
- package/dist/cli/managed-skill-assets.d.ts +1 -1
- package/dist/cli/managed-skill-assets.js +5 -3
- package/dist/cli/office-dependencies.d.ts +2 -0
- package/dist/cli/office-dependencies.js +7 -3
- package/dist/cli/scientific-visualization-dependencies.js +1 -1
- package/dist/cli/tui-config.d.ts +27 -2
- package/dist/cli/tui-config.js +77 -2
- package/dist/cli/types.d.ts +17 -0
- package/dist/cli.js +9 -6
- package/dist/commands.d.ts +10 -1
- package/dist/commands.js +17 -3
- package/dist/features.d.ts +27 -1
- package/dist/features.js +40 -8
- package/dist/host/server.d.ts +7 -0
- package/dist/host/server.js +12 -0
- package/dist/host/tui.d.ts +7 -0
- package/dist/host/tui.js +12 -0
- package/dist/server.js +9 -0
- package/dist/skill-renames.d.ts +1 -1
- package/dist/skills.d.ts +9 -2
- package/dist/skills.js +22 -8
- package/dist/tui/mission-source-v2.d.ts +51 -0
- package/dist/tui/mission-source-v2.js +52 -0
- package/dist/v2/agents.d.ts +105 -0
- package/dist/v2/agents.js +185 -0
- package/dist/v2/commands.d.ts +44 -0
- package/dist/v2/commands.js +65 -0
- package/dist/v2/context.d.ts +67 -0
- package/dist/v2/context.js +17 -0
- package/dist/v2/events.d.ts +55 -0
- package/dist/v2/events.js +193 -0
- package/dist/v2/hooks.d.ts +39 -0
- package/dist/v2/hooks.js +61 -0
- package/dist/v2/server.d.ts +2 -0
- package/dist/v2/server.js +68 -0
- package/dist/v2/session-hooks.d.ts +64 -0
- package/dist/v2/session-hooks.js +230 -0
- package/dist/v2/tools.d.ts +26 -0
- package/dist/v2/tools.js +49 -0
- package/dist/v2/tui.d.ts +63 -0
- package/dist/v2/tui.js +259 -0
- package/docs/assets/cover-motion-still.webp +0 -0
- package/docs/assets/cover-motion.webp +0 -0
- package/docs/assets/cover.webp +0 -0
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/assets/readme/litfamily-machines.png +0 -0
- package/docs/migration.md +4 -3
- package/docs/reference-Ko-KR.md +109 -7
- package/docs/reference.md +122 -9
- package/package.json +10 -5
- package/skills/deep-interview/SKILL.md +1 -1
- package/skills/lit-brainstorm/SKILL.md +299 -0
- package/skills/lit-brainstorm/references/handoff-briefs.md +72 -0
- package/skills/lit-brainstorm/references/lenses.md +106 -0
- package/skills/lit-brainstorm/references/scoring-and-convergence.md +93 -0
- package/skills/lit-brainstorm/references/session-notes-and-lanes.md +122 -0
- package/skills/lit-docx/SKILL.md +1 -1
- package/skills/lit-docx/run.py +42 -4
- package/skills/lit-docx/scripts/convert_md_to_pdf.py +38 -0
- package/skills/lit-docx/templates/latex/acs/template.tex +2 -0
- package/skills/lit-docx/templates/latex/elsevier/template.tex +2 -0
- package/skills/lit-docx/templates/latex/ieee/template.tex +2 -0
- package/skills/lit-docx/templates/latex/korean-generic/template.tex +2 -0
- package/skills/lit-docx/templates/latex/nature/template.tex +2 -0
- package/skills/lit-humanizer/scripts/detect.mjs +1 -1
- package/skills/lit-pptx/SKILL.md +1 -1
- package/skills/lit-pptx/command.mjs +94 -0
- package/skills/lit-pptx/run.mjs +16 -6
- package/skills/lit-pptx/scripts/compile-deck.js +2 -2
- package/skills/lit-typographic-motion/SKILL.md +5 -2
- package/skills/lit-typographic-motion/THIRD_PARTY_NOTICES +3 -0
- package/skills/lit-typographic-motion/engine/browser.mjs +3 -9
- package/skills/lit-typographic-motion/engine/command.mjs +94 -0
- package/skills/lit-typographic-motion/engine/constants.mjs +29 -1
- package/skills/lit-typographic-motion/engine/cover/assets.mjs +91 -0
- package/skills/lit-typographic-motion/engine/cover/capture.mjs +173 -0
- package/skills/lit-typographic-motion/engine/cover/decode.mjs +173 -0
- package/skills/lit-typographic-motion/engine/cover/frame-worker.mjs +19 -0
- package/skills/lit-typographic-motion/engine/cover/gate.mjs +261 -0
- package/skills/lit-typographic-motion/engine/cover/image.mjs +138 -0
- package/skills/lit-typographic-motion/engine/cover/render.mjs +323 -0
- package/skills/lit-typographic-motion/engine/cover/sheet.mjs +65 -0
- package/skills/lit-typographic-motion/engine/cover/treatment.mjs +110 -0
- package/skills/lit-typographic-motion/engine/encode.mjs +1 -1
- package/skills/lit-typographic-motion/engine/look.mjs +113 -10
- package/skills/lit-typographic-motion/engine/sound.mjs +1 -1
- package/skills/lit-typographic-motion/engine/stage/capture.mjs +26 -18
- package/skills/lit-typographic-motion/engine/stage/cover-init.js +38 -0
- package/skills/lit-typographic-motion/engine/stage/loop-kit.js +102 -0
- package/skills/lit-typographic-motion/engine/stage/render.mjs +4 -4
- package/skills/lit-typographic-motion/engine/treatment.mjs +1 -0
- package/skills/lit-typographic-motion/fonts/JetBrainsMono-OFL.txt +93 -0
- package/skills/lit-typographic-motion/fonts/JetBrainsMono-wght-latin.woff2 +0 -0
- package/skills/lit-typographic-motion/fonts/pins.json +3 -1
- package/skills/lit-typographic-motion/gate.mjs +34 -1
- package/skills/lit-typographic-motion/probe.mjs +8 -3
- package/skills/lit-typographic-motion/recipes/negatives/card-scenes/expected.json +16 -0
- package/skills/lit-typographic-motion/recipes/negatives/card-scenes/index.html +35 -0
- package/skills/lit-typographic-motion/recipes/negatives/reset-sweep/expected.json +16 -0
- package/skills/lit-typographic-motion/recipes/negatives/reset-sweep/index.html +29 -0
- package/skills/lit-typographic-motion/recipes/negatives/still-glow/expected.json +17 -0
- package/skills/lit-typographic-motion/recipes/negatives/still-glow/index.html +29 -0
- package/skills/lit-typographic-motion/recipes/negatives/title-slam/expected.json +17 -0
- package/skills/lit-typographic-motion/recipes/negatives/title-slam/index.html +35 -0
- package/skills/lit-typographic-motion/recipes/orbit-loop/expected.json +62 -0
- package/skills/lit-typographic-motion/recipes/orbit-loop/index.html +100 -0
- package/skills/lit-typographic-motion/recipes/painted-light/expected.json +95 -0
- package/skills/lit-typographic-motion/recipes/painted-light/index.html +232 -0
- package/skills/lit-typographic-motion/recipes/weight-tide/expected.json +62 -0
- package/skills/lit-typographic-motion/recipes/weight-tide/index.html +81 -0
- package/skills/lit-typographic-motion/references/cover.md +423 -0
- package/skills/lit-typographic-motion/references/craft-loop.md +2 -0
- package/skills/lit-typographic-motion/references/runtime.md +17 -2
- package/skills/lit-typographic-motion/references/stage.md +3 -0
- package/skills/lit-typographic-motion/references/treatment.md +4 -0
- package/skills/lit-typographic-motion/render.mjs +30 -6
- package/skills/lit-typographic-motion/requirements-audio.txt +3 -3
- package/skills/lit-typographic-motion/runtime.mjs +12 -13
- package/skills/{durable-litgoal → litgoal}/SKILL.md +31 -12
- package/skills/managed-skill-manifest.json +176 -32
- package/skills/readme-studio/templates/hyperframes/package-lock.json +3 -3
- package/skills/readme-studio/templates/remotion/package-lock.json +3 -3
- package/skills/review-work/SKILL.md +27 -18
- package/skills/skill-rename-aliases.json +2 -1
- package/tools/check-pack-payload.mjs +4 -1
- package/tools/payload-substance-allowlist.json +1 -1
- package/tools/payload-substance-parity.json +6 -4
- package/tools/qa-cover-recipes.mjs +167 -0
- package/tools/version-manifests.json +6 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// LitOpenCode's agents on OpenCode 2. The roster, prompts, routes and permission modes are built by the
|
|
2
|
+
// same code the OpenCode 1 `config` hook uses; this module only re-expresses the result in OpenCode 2
|
|
3
|
+
// terms: an ordered permission ruleset instead of tool switches and a permission map, `system` instead
|
|
4
|
+
// of `prompt`, `steps` instead of `maxSteps`, and a model reference instead of a model string.
|
|
5
|
+
import { litOpenCodeAgents, registerLitOpenCodeAgents } from "../agents.js";
|
|
6
|
+
import { openAiModelEfforts, openaiProviderModels } from "../cli/model-catalog.js";
|
|
7
|
+
/**
|
|
8
|
+
* Agents that only plan: the host rules deny them edits, the shell, subagents and Code Mode, and the
|
|
9
|
+
* evaluate hook enforces it again. Code Mode (`execute`) runs model-written JavaScript with `fetch` and
|
|
10
|
+
* the catalog tools, which OpenCode 1 never offered; a tool whose last matching rule denies "*" is left
|
|
11
|
+
* out of the request entirely.
|
|
12
|
+
*/
|
|
13
|
+
export const planningOnlyAgents = new Set(["lit-plan"]);
|
|
14
|
+
const planningDeniedActions = ["edit", "shell", "subagent", "execute"];
|
|
15
|
+
const planningDeniedAliases = new Set([...planningDeniedActions, "write", "patch", "bash", "task"]);
|
|
16
|
+
/** OpenCode 2 renamed these tools; a migrated rule must use the new name to keep matching. */
|
|
17
|
+
export function normalizeAction(action) {
|
|
18
|
+
if (action === "write" || action === "patch")
|
|
19
|
+
return "edit";
|
|
20
|
+
if (action === "task")
|
|
21
|
+
return "subagent";
|
|
22
|
+
if (action === "bash")
|
|
23
|
+
return "shell";
|
|
24
|
+
return action;
|
|
25
|
+
}
|
|
26
|
+
function isEffect(value) {
|
|
27
|
+
return value === "allow" || value === "deny" || value === "ask";
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Turns an OpenCode 1 permission map into ordered rules, the way OpenCode 2 migrates config files:
|
|
31
|
+
* tool switches first (true allows, false denies), then permission keys, a later key replacing an
|
|
32
|
+
* earlier one in place, and a pattern map becoming one rule per pattern in its written order.
|
|
33
|
+
*/
|
|
34
|
+
export function permissionRules(tools, permission) {
|
|
35
|
+
const entries = new Map();
|
|
36
|
+
for (const [tool, enabled] of Object.entries(tools ?? {}))
|
|
37
|
+
entries.set(normalizeAction(tool), enabled ? "allow" : "deny");
|
|
38
|
+
if (isEffect(permission))
|
|
39
|
+
entries.set("*", permission);
|
|
40
|
+
else if (typeof permission === "object" && permission !== null && !Array.isArray(permission)) {
|
|
41
|
+
for (const [key, value] of Object.entries(permission))
|
|
42
|
+
entries.set(normalizeAction(key), value);
|
|
43
|
+
}
|
|
44
|
+
const rules = [];
|
|
45
|
+
for (const [action, value] of entries) {
|
|
46
|
+
if (isEffect(value)) {
|
|
47
|
+
rules.push({ action, resource: "*", effect: value });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
51
|
+
continue;
|
|
52
|
+
for (const [resource, effect] of Object.entries(value))
|
|
53
|
+
if (isEffect(effect))
|
|
54
|
+
rules.push({ action, resource, effect });
|
|
55
|
+
}
|
|
56
|
+
return rules;
|
|
57
|
+
}
|
|
58
|
+
function modelRef(model, variant) {
|
|
59
|
+
if (model === undefined)
|
|
60
|
+
return undefined;
|
|
61
|
+
const slash = model.indexOf("/");
|
|
62
|
+
if (slash <= 0 || slash === model.length - 1)
|
|
63
|
+
return undefined;
|
|
64
|
+
return {
|
|
65
|
+
providerID: model.slice(0, slash),
|
|
66
|
+
id: model.slice(slash + 1),
|
|
67
|
+
...(variant === undefined || variant === "" || variant.includes("#") ? {} : { variant })
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function requestOptions(agent) {
|
|
71
|
+
return {
|
|
72
|
+
...(agent.temperature === undefined ? {} : { temperature: agent.temperature }),
|
|
73
|
+
...(agent.top_p === undefined ? {} : { topP: agent.top_p }),
|
|
74
|
+
...(agent.maxTokens === undefined ? {} : { maxTokens: agent.maxTokens }),
|
|
75
|
+
...(agent.reasoningEffort === undefined ? {} : { reasoningEffort: agent.reasoningEffort }),
|
|
76
|
+
...(agent.textVerbosity === undefined ? {} : { textVerbosity: agent.textVerbosity })
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Builds every agent edit from the LitOpenCode config, without touching the host. */
|
|
80
|
+
export function planLitAgents(config) {
|
|
81
|
+
const target = {};
|
|
82
|
+
registerLitOpenCodeAgents(target, config);
|
|
83
|
+
// Balanced and yolo modes widen the global permission on OpenCode 1. A plugin cannot edit the global
|
|
84
|
+
// ruleset on OpenCode 2, so the same rules lead each LitOpenCode agent's own ruleset instead.
|
|
85
|
+
const globalRules = permissionRules(undefined, target.permission);
|
|
86
|
+
const agents = new Map();
|
|
87
|
+
const options = new Map();
|
|
88
|
+
for (const definition of litOpenCodeAgents) {
|
|
89
|
+
const v1 = target.agent?.[definition.id];
|
|
90
|
+
if (v1 === undefined)
|
|
91
|
+
continue;
|
|
92
|
+
const rules = [...globalRules, ...permissionRules(v1.tools, v1.permission)];
|
|
93
|
+
if (planningOnlyAgents.has(definition.id)) {
|
|
94
|
+
for (const action of planningDeniedActions)
|
|
95
|
+
rules.push({ action, resource: "*", effect: "deny" });
|
|
96
|
+
}
|
|
97
|
+
const model = modelRef(v1.model, v1.variant);
|
|
98
|
+
agents.set(definition.id, {
|
|
99
|
+
...(v1.prompt === undefined ? {} : { system: v1.prompt }),
|
|
100
|
+
...(v1.description === undefined ? {} : { description: v1.description }),
|
|
101
|
+
mode: v1.mode === "subagent" ? "subagent" : v1.mode === "primary" ? "primary" : "all",
|
|
102
|
+
hidden: v1.hidden === true,
|
|
103
|
+
...(typeof v1.color === "string" && v1.color.startsWith("#") ? { color: v1.color } : {}),
|
|
104
|
+
...(typeof v1.maxSteps === "number" && Number.isInteger(v1.maxSteps) && v1.maxSteps > 0 ? { steps: v1.maxSteps } : {}),
|
|
105
|
+
...(model === undefined ? {} : { model }),
|
|
106
|
+
permissions: rules
|
|
107
|
+
});
|
|
108
|
+
const request = requestOptions(v1);
|
|
109
|
+
if (Object.keys(request).length > 0)
|
|
110
|
+
options.set(definition.id, request);
|
|
111
|
+
}
|
|
112
|
+
return { agents, requestOptions: options, hostAgents: ["build", "plan"] };
|
|
113
|
+
}
|
|
114
|
+
/** Applies a plan inside ctx.agent.transform: creates or updates each agent, hides the host agents, sets the default. */
|
|
115
|
+
export function applyLitAgents(editor, plan) {
|
|
116
|
+
for (const id of plan.hostAgents) {
|
|
117
|
+
editor.update(id, (agent) => {
|
|
118
|
+
agent.mode = "subagent";
|
|
119
|
+
agent.hidden = true;
|
|
120
|
+
agent.permissions.push({ action: "subagent", resource: "*", effect: "deny" });
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
for (const [id, edit] of plan.agents) {
|
|
124
|
+
editor.update(id, (agent) => {
|
|
125
|
+
const { permissions, ...fields } = edit;
|
|
126
|
+
Object.assign(agent, fields);
|
|
127
|
+
// Agent rules follow the host defaults the agent already carries; the last match wins.
|
|
128
|
+
agent.permissions.push(...permissions.map((rule) => ({ ...rule })));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
editor.default("lit-loop");
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The OpenAI rows LitOpenCode routes to, added to the host catalog when it lacks one, as the OpenCode 1
|
|
135
|
+
* `config` hook does. A row the host already has is left as the host made it, and the editor cannot
|
|
136
|
+
* create a provider the user has not set up. Each effort becomes a variant shaped like the host's own
|
|
137
|
+
* OpenAI rows, so a route such as `#xhigh` resolves.
|
|
138
|
+
*/
|
|
139
|
+
export function registerOpenAiProviderModels(editor) {
|
|
140
|
+
for (const [modelID, metadata] of Object.entries(openaiProviderModels)) {
|
|
141
|
+
if (editor.get("openai", modelID) !== undefined)
|
|
142
|
+
continue;
|
|
143
|
+
editor.update("openai", modelID, (model) => {
|
|
144
|
+
model.name = metadata.name;
|
|
145
|
+
model.capabilities = { tools: metadata.tool_call, input: ["text", "image"], output: ["text"], temperature: metadata.temperature };
|
|
146
|
+
model.variants = openAiModelEfforts(modelID).map((effort) => ({
|
|
147
|
+
id: effort,
|
|
148
|
+
settings: { reasoningEffort: effort, reasoningSummary: "auto", include: ["reasoning.encrypted_content"] }
|
|
149
|
+
}));
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* OpenCode 2 runs a session that has no model of its own on the host default model: the agent's model
|
|
155
|
+
* is only read by a client that creates the session (the TUI does, `opencode2 run` does not). This sets
|
|
156
|
+
* such a root session to its agent's route, lit-loop when it names none, once the route is in the
|
|
157
|
+
* catalog. A session with a model, a delegated child (the host gives it its agent's model) and an agent
|
|
158
|
+
* LitOpenCode does not own keep the host's choice.
|
|
159
|
+
*/
|
|
160
|
+
export function createSessionModelRoute(plan, session, models) {
|
|
161
|
+
return async (sessionID) => {
|
|
162
|
+
const info = await session.get({ sessionID });
|
|
163
|
+
if (info.model !== undefined && info.model !== null)
|
|
164
|
+
return;
|
|
165
|
+
if (typeof info.parentID === "string" && info.parentID !== "")
|
|
166
|
+
return;
|
|
167
|
+
const route = plan.agents.get(typeof info.agent === "string" && info.agent !== "" ? info.agent : "lit-loop")?.model;
|
|
168
|
+
if (route === undefined)
|
|
169
|
+
return;
|
|
170
|
+
const listed = await models.list();
|
|
171
|
+
const available = Array.isArray(listed) ? listed : listed.data;
|
|
172
|
+
if (!available.some((model) => model.providerID === route.providerID && model.id === route.id && model.enabled !== false))
|
|
173
|
+
return;
|
|
174
|
+
await session.switchModel({ sessionID, model: { ...route } });
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** The second lock for planning-only agents: whatever the rules say, edits, the shell, subagents and Code Mode are denied. */
|
|
178
|
+
export function enforcePlanningOnly(evaluation) {
|
|
179
|
+
if (evaluation.agent === undefined || !planningOnlyAgents.has(evaluation.agent))
|
|
180
|
+
return;
|
|
181
|
+
if (!planningDeniedAliases.has(evaluation.action))
|
|
182
|
+
return;
|
|
183
|
+
evaluation.effect = "deny";
|
|
184
|
+
evaluation.message = `${evaluation.agent} only plans. Ask lit-loop or run /start-work to change files or run commands.`;
|
|
185
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type CommandInvocationLike = {
|
|
2
|
+
readonly sessionID: string;
|
|
3
|
+
readonly prompt: {
|
|
4
|
+
readonly text: string;
|
|
5
|
+
};
|
|
6
|
+
readonly delivery: "steer" | "queue";
|
|
7
|
+
};
|
|
8
|
+
export type CommandDefinitionLike = {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly description?: string;
|
|
11
|
+
readonly execute: (input: CommandInvocationLike) => Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
export type CommandEditorLike = {
|
|
14
|
+
add(definition: CommandDefinitionLike): void;
|
|
15
|
+
};
|
|
16
|
+
export type CommandSessionLike = {
|
|
17
|
+
readonly switchAgent: (input: {
|
|
18
|
+
readonly sessionID: string;
|
|
19
|
+
readonly agent: string;
|
|
20
|
+
}) => Promise<unknown>;
|
|
21
|
+
readonly prompt: (input: {
|
|
22
|
+
readonly sessionID: string;
|
|
23
|
+
readonly text: string;
|
|
24
|
+
readonly delivery?: "steer" | "queue";
|
|
25
|
+
readonly metadata?: {
|
|
26
|
+
readonly litopencode: {
|
|
27
|
+
readonly command: string;
|
|
28
|
+
readonly banner: string;
|
|
29
|
+
readonly nonce: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
}) => Promise<unknown>;
|
|
33
|
+
};
|
|
34
|
+
/** Lit command ids that no alias file covers in the given command roots. */
|
|
35
|
+
export declare function pluginOwnedCommandIds(roots: readonly string[]): readonly string[];
|
|
36
|
+
export type CommandMark = {
|
|
37
|
+
readonly command: string;
|
|
38
|
+
readonly banner: string;
|
|
39
|
+
};
|
|
40
|
+
export declare function createV2Commands(roots: readonly string[], session: CommandSessionLike): {
|
|
41
|
+
definitions: CommandDefinitionLike[];
|
|
42
|
+
acceptMark: (sessionID: string, marker: unknown) => CommandMark | undefined;
|
|
43
|
+
add(editor: CommandEditorLike): void;
|
|
44
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Lit slash commands on OpenCode 2. The installer writes one markdown alias file per command
|
|
2
|
+
// (`command/<id>.md`), and OpenCode 2 lets such a file replace a plugin command of the same name, so the
|
|
3
|
+
// alias files are the source of every lit command on both hosts. The plugin adds a command only when no
|
|
4
|
+
// alias file covers it, for example when the package was added with `opencode plugin` and the installer
|
|
5
|
+
// never ran. A command the plugin sends carries a one-time nonce in the user message metadata; the
|
|
6
|
+
// prompt hook accepts the command mark only with that nonce, so a typed or forged mark is dropped. A
|
|
7
|
+
// command with an agent route (the `agent:` line of its alias file) first switches the session to that
|
|
8
|
+
// agent, so `/lit-crucible` really runs in the planning-only `lit-plan`.
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { litOpenCodeCommands } from "../commands.js";
|
|
13
|
+
const aliasDirectories = ["command", "commands"];
|
|
14
|
+
function hasAliasFile(roots, id) {
|
|
15
|
+
return roots.some((root) => aliasDirectories.some((directory) => fs.existsSync(path.join(root, directory, `${id}.md`))));
|
|
16
|
+
}
|
|
17
|
+
/** Lit command ids that no alias file covers in the given command roots. */
|
|
18
|
+
export function pluginOwnedCommandIds(roots) {
|
|
19
|
+
return litOpenCodeCommands.map((command) => command.id).filter((id) => !hasAliasFile(roots, id));
|
|
20
|
+
}
|
|
21
|
+
export function createV2Commands(roots, session) {
|
|
22
|
+
const owned = new Set(pluginOwnedCommandIds(roots));
|
|
23
|
+
const pending = new Map();
|
|
24
|
+
const definitions = litOpenCodeCommands
|
|
25
|
+
.filter((command) => owned.has(command.id))
|
|
26
|
+
.map((command) => ({
|
|
27
|
+
name: command.id,
|
|
28
|
+
description: `${command.title} - ${command.description}`,
|
|
29
|
+
async execute(input) {
|
|
30
|
+
const nonce = randomUUID();
|
|
31
|
+
const mark = { command: command.id, banner: command.banner };
|
|
32
|
+
pending.set(input.sessionID, { nonce, mark });
|
|
33
|
+
const argumentsText = input.prompt.text.trim();
|
|
34
|
+
if (command.agent !== undefined)
|
|
35
|
+
await session.switchAgent({ sessionID: input.sessionID, agent: command.agent });
|
|
36
|
+
await session.prompt({
|
|
37
|
+
sessionID: input.sessionID,
|
|
38
|
+
text: argumentsText === "" ? command.activationText : `${command.activationText}\n\n${argumentsText}`,
|
|
39
|
+
delivery: input.delivery,
|
|
40
|
+
metadata: { litopencode: { ...mark, nonce } }
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}));
|
|
44
|
+
/**
|
|
45
|
+
* Checks a command mark at admission. Returns the mark when the plugin sent it for this session (and
|
|
46
|
+
* consumes the nonce), and undefined otherwise; the caller then removes the mark from the metadata.
|
|
47
|
+
*/
|
|
48
|
+
function acceptMark(sessionID, marker) {
|
|
49
|
+
if (typeof marker !== "object" || marker === null)
|
|
50
|
+
return undefined;
|
|
51
|
+
const expected = pending.get(sessionID);
|
|
52
|
+
if (expected === undefined || marker.nonce !== expected.nonce)
|
|
53
|
+
return undefined;
|
|
54
|
+
pending.delete(sessionID);
|
|
55
|
+
return expected.mark;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
definitions,
|
|
59
|
+
acceptMark,
|
|
60
|
+
add(editor) {
|
|
61
|
+
for (const definition of definitions)
|
|
62
|
+
editor.add(definition);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { SessionLookup } from "../session-lineage.ts";
|
|
2
|
+
import type { AgentEditorLike, ModelEditorLike, PermissionEvaluationLike, SessionModelDomainLike } from "./agents.ts";
|
|
3
|
+
import type { CommandEditorLike, CommandSessionLike } from "./commands.ts";
|
|
4
|
+
import type { EventDomainLike, EventModelLike, EventSessionLike } from "./events.ts";
|
|
5
|
+
import type { ToolAfterEvent, ToolBeforeEvent } from "./hooks.ts";
|
|
6
|
+
import type { ContextEvent, PromptEvent } from "./session-hooks.ts";
|
|
7
|
+
import type { ToolEditorLike } from "./tools.ts";
|
|
8
|
+
export type Registration = {
|
|
9
|
+
readonly dispose: () => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export type Transform<Editor> = (callback: (editor: Editor) => void) => Promise<Registration>;
|
|
12
|
+
type Callback<Input> = (input: Input) => Promise<void> | void;
|
|
13
|
+
export type LocationLike = {
|
|
14
|
+
readonly directory: string;
|
|
15
|
+
readonly project: {
|
|
16
|
+
readonly directory: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
type ToolHook = {
|
|
20
|
+
(name: "execute.before", callback: Callback<ToolBeforeEvent>): Promise<Registration>;
|
|
21
|
+
(name: "execute.after", callback: Callback<ToolAfterEvent>): Promise<Registration>;
|
|
22
|
+
};
|
|
23
|
+
export type SessionInfoLike = {
|
|
24
|
+
readonly parentID?: string;
|
|
25
|
+
readonly agent?: string;
|
|
26
|
+
};
|
|
27
|
+
type SessionHook = {
|
|
28
|
+
(name: "prompt", callback: Callback<PromptEvent>): Promise<Registration>;
|
|
29
|
+
(name: "context", callback: Callback<ContextEvent>): Promise<Registration>;
|
|
30
|
+
(name: "compaction", callback: Callback<ContextEvent>): Promise<Registration>;
|
|
31
|
+
};
|
|
32
|
+
export type SessionDomainLike = {
|
|
33
|
+
readonly get: (input: {
|
|
34
|
+
readonly sessionID: string;
|
|
35
|
+
}) => Promise<SessionInfoLike>;
|
|
36
|
+
};
|
|
37
|
+
export type ServerContextLike = {
|
|
38
|
+
readonly location: LocationLike;
|
|
39
|
+
readonly agent: {
|
|
40
|
+
readonly transform: Transform<AgentEditorLike>;
|
|
41
|
+
};
|
|
42
|
+
readonly tool: {
|
|
43
|
+
readonly transform: Transform<ToolEditorLike>;
|
|
44
|
+
readonly hook: ToolHook;
|
|
45
|
+
};
|
|
46
|
+
readonly permission: {
|
|
47
|
+
readonly hook: (name: "evaluate", callback: Callback<PermissionEvaluationLike>) => Promise<Registration>;
|
|
48
|
+
};
|
|
49
|
+
readonly command: {
|
|
50
|
+
readonly transform: Transform<CommandEditorLike>;
|
|
51
|
+
};
|
|
52
|
+
readonly session: SessionDomainLike & SessionModelDomainLike & CommandSessionLike & EventSessionLike & {
|
|
53
|
+
readonly hook: SessionHook;
|
|
54
|
+
};
|
|
55
|
+
readonly event: EventDomainLike;
|
|
56
|
+
readonly model: EventModelLike & {
|
|
57
|
+
readonly transform: Transform<ModelEditorLike>;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
/** The project LitOpenCode keeps its ledger in: the project root, else the directory OpenCode opened. */
|
|
61
|
+
export declare function projectRootFromLocation(location: LocationLike): string;
|
|
62
|
+
/** Session lineage and agent from OpenCode 2; a failed lookup throws, and callers treat that as a child session. */
|
|
63
|
+
export declare function createSessionLookupV2(session: SessionDomainLike): SessionLookup & ((sessionID: string) => Promise<{
|
|
64
|
+
readonly parentID?: string;
|
|
65
|
+
readonly agent?: string;
|
|
66
|
+
}>);
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** The project LitOpenCode keeps its ledger in: the project root, else the directory OpenCode opened. */
|
|
2
|
+
export function projectRootFromLocation(location) {
|
|
3
|
+
const project = location.project.directory.trim();
|
|
4
|
+
if (project !== "" && project !== "/")
|
|
5
|
+
return project;
|
|
6
|
+
const directory = location.directory.trim();
|
|
7
|
+
return directory === "" ? "." : directory;
|
|
8
|
+
}
|
|
9
|
+
/** Session lineage and agent from OpenCode 2; a failed lookup throws, and callers treat that as a child session. */
|
|
10
|
+
export function createSessionLookupV2(session) {
|
|
11
|
+
return async (sessionID) => {
|
|
12
|
+
const info = await session.get({ sessionID });
|
|
13
|
+
const parentID = typeof info.parentID === "string" && info.parentID.length > 0 ? info.parentID : undefined;
|
|
14
|
+
const agent = typeof info.agent === "string" && info.agent.length > 0 ? info.agent : undefined;
|
|
15
|
+
return { ...(parentID === undefined ? {} : { parentID }), ...(agent === undefined ? {} : { agent }) };
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type BoundedAuthorityOptions } from "../bounded-authority.ts";
|
|
2
|
+
import type { AutoHandoffConfig } from "../config.ts";
|
|
3
|
+
import type { SessionLookup } from "../session-lineage.ts";
|
|
4
|
+
import type { RuntimePaths } from "../state.ts";
|
|
5
|
+
export type StreamEvent = {
|
|
6
|
+
readonly type: string;
|
|
7
|
+
readonly data?: unknown;
|
|
8
|
+
};
|
|
9
|
+
export type EventDomainLike = {
|
|
10
|
+
readonly subscribe: (options?: {
|
|
11
|
+
readonly signal?: AbortSignal;
|
|
12
|
+
}) => AsyncIterable<StreamEvent>;
|
|
13
|
+
};
|
|
14
|
+
export type ModelInfoLike = {
|
|
15
|
+
readonly providerID: string;
|
|
16
|
+
readonly modelID?: string;
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly limit?: {
|
|
19
|
+
readonly context?: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type EventSessionLike = {
|
|
23
|
+
readonly prompt: (input: {
|
|
24
|
+
readonly sessionID: string;
|
|
25
|
+
readonly text: string;
|
|
26
|
+
readonly id?: string;
|
|
27
|
+
}) => Promise<unknown>;
|
|
28
|
+
readonly compact: (input: {
|
|
29
|
+
readonly sessionID: string;
|
|
30
|
+
}) => Promise<unknown>;
|
|
31
|
+
};
|
|
32
|
+
export type EventModelLike = {
|
|
33
|
+
readonly list: () => Promise<readonly ModelInfoLike[] | {
|
|
34
|
+
readonly data: readonly ModelInfoLike[];
|
|
35
|
+
}>;
|
|
36
|
+
};
|
|
37
|
+
export type V2EventOptions = {
|
|
38
|
+
readonly projectRoot: string;
|
|
39
|
+
readonly paths: Pick<RuntimePaths, "autoHandoffFile">;
|
|
40
|
+
readonly autoHandoffConfig: AutoHandoffConfig;
|
|
41
|
+
readonly boundedAuthority: BoundedAuthorityOptions;
|
|
42
|
+
readonly getSession: SessionLookup;
|
|
43
|
+
readonly session: EventSessionLike;
|
|
44
|
+
readonly model: EventModelLike;
|
|
45
|
+
readonly onSessionDeleted: (sessionID: string) => void;
|
|
46
|
+
};
|
|
47
|
+
export declare function createV2Events(options: V2EventOptions): {
|
|
48
|
+
handle: (event: StreamEvent) => Promise<void>;
|
|
49
|
+
start: (events: EventDomainLike) => () => Promise<void>;
|
|
50
|
+
/** Steps started and not yet ended. */
|
|
51
|
+
pendingSteps: () => number;
|
|
52
|
+
route: (text: string, mode: import("../auto-handoff.ts").AutoHandoffRouteMode) => Promise<string | undefined>;
|
|
53
|
+
/** The saved handoff, handed back once after a compaction. */
|
|
54
|
+
systemText(sessionID: string): Promise<readonly string[]>;
|
|
55
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createAutoHandoff } from "../auto-handoff-hooks.js";
|
|
2
|
+
import { LifecycleConflictError, LifecycleSafetyError, createBoundedAuthorityLifecycle, parseFencedProgress } from "../bounded-authority.js";
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function str(value) {
|
|
7
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The calls the OpenCode 1 automatic handoff makes, answered by OpenCode 2. There is no server toast on
|
|
11
|
+
* OpenCode 2, so the client has no `tui`; the handoff's own notices are not shown there.
|
|
12
|
+
*/
|
|
13
|
+
function handoffClient(session, model, statuses) {
|
|
14
|
+
const client = {
|
|
15
|
+
session: {
|
|
16
|
+
async promptAsync(input) {
|
|
17
|
+
const text = [input.body.system, ...(input.body.parts ?? []).map((part) => part.text)].filter((part) => typeof part === "string" && part !== "").join("\n\n");
|
|
18
|
+
await session.prompt({ sessionID: input.path.id, text });
|
|
19
|
+
return { data: {} };
|
|
20
|
+
},
|
|
21
|
+
async summarize(input) {
|
|
22
|
+
await session.compact({ sessionID: input.path.id });
|
|
23
|
+
return { data: true };
|
|
24
|
+
},
|
|
25
|
+
async status() {
|
|
26
|
+
return { data: Object.fromEntries(statuses) };
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
config: {
|
|
30
|
+
async providers() {
|
|
31
|
+
const listed = await model.list();
|
|
32
|
+
const models = Array.isArray(listed) ? listed : listed.data;
|
|
33
|
+
const providers = new Map();
|
|
34
|
+
for (const entry of models) {
|
|
35
|
+
const byId = providers.get(entry.providerID) ?? {};
|
|
36
|
+
byId[entry.modelID ?? entry.id] = { limit: entry.limit };
|
|
37
|
+
providers.set(entry.providerID, byId);
|
|
38
|
+
}
|
|
39
|
+
return { data: { providers: [...providers].map(([id, models]) => ({ id, models })) } };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return client;
|
|
44
|
+
}
|
|
45
|
+
export function createV2Events(options) {
|
|
46
|
+
const statuses = new Map();
|
|
47
|
+
const steps = new Map();
|
|
48
|
+
const lifecycle = createBoundedAuthorityLifecycle(options.projectRoot, options.boundedAuthority);
|
|
49
|
+
const autoHandoff = createAutoHandoff({
|
|
50
|
+
projectRoot: options.projectRoot,
|
|
51
|
+
paths: options.paths,
|
|
52
|
+
client: handoffClient(options.session, options.model, statuses),
|
|
53
|
+
config: options.autoHandoffConfig,
|
|
54
|
+
getSession: options.getSession
|
|
55
|
+
});
|
|
56
|
+
// Translated into the OpenCode 1 event shapes the handoff state machine reads.
|
|
57
|
+
async function toHandoff(type, properties) {
|
|
58
|
+
await autoHandoff.event({ event: { type, properties } });
|
|
59
|
+
}
|
|
60
|
+
async function recordProgress(sessionID, messageID, ordinal, text) {
|
|
61
|
+
let progress;
|
|
62
|
+
try {
|
|
63
|
+
progress = parseFencedProgress(text);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (progress === undefined)
|
|
69
|
+
return;
|
|
70
|
+
const partID = `${messageID}:text:${ordinal}`;
|
|
71
|
+
try {
|
|
72
|
+
const recorded = await lifecycle.recordProgress({ ...progress, sessionID, messageID, partID });
|
|
73
|
+
if (recorded.dispatchRequired !== true || recorded.continuation === undefined || recorded.continuationMessageID === undefined)
|
|
74
|
+
return;
|
|
75
|
+
try {
|
|
76
|
+
// The id is the host's dedupe key: a replayed text end admits the same message, not a second one.
|
|
77
|
+
await options.session.prompt({ sessionID, text: recorded.continuation, id: recorded.continuationMessageID });
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
await lifecycle.markContinuationSent({ workId: recorded.state.workId, sessionID: recorded.state.sessionID, revision: recorded.state.revision, messageID, partID });
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error instanceof LifecycleConflictError || error instanceof LifecycleSafetyError)
|
|
86
|
+
return;
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function handle(event) {
|
|
91
|
+
const data = isRecord(event.data) ? event.data : {};
|
|
92
|
+
const sessionID = str(data.sessionID);
|
|
93
|
+
if (sessionID === undefined)
|
|
94
|
+
return;
|
|
95
|
+
switch (event.type) {
|
|
96
|
+
case "session.deleted":
|
|
97
|
+
statuses.delete(sessionID);
|
|
98
|
+
// A step cut short (abort, crash) never ends; its entry goes with its session.
|
|
99
|
+
for (const [messageID, step] of steps)
|
|
100
|
+
if (step.sessionID === sessionID)
|
|
101
|
+
steps.delete(messageID);
|
|
102
|
+
options.onSessionDeleted(sessionID);
|
|
103
|
+
await toHandoff("session.deleted", { info: { id: sessionID } });
|
|
104
|
+
return;
|
|
105
|
+
case "session.step.started": {
|
|
106
|
+
const messageID = str(data.assistantMessageID);
|
|
107
|
+
const model = isRecord(data.model) ? data.model : {};
|
|
108
|
+
const providerID = str(model.providerID);
|
|
109
|
+
const modelID = str(model.id);
|
|
110
|
+
if (messageID === undefined || providerID === undefined || modelID === undefined)
|
|
111
|
+
return;
|
|
112
|
+
steps.set(messageID, { sessionID, providerID, modelID, ...(str(data.agent) === undefined ? {} : { agent: str(data.agent) }), started: typeof data.started === "number" ? data.started : Date.now() });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
case "session.step.ended": {
|
|
116
|
+
const messageID = str(data.assistantMessageID);
|
|
117
|
+
const step = messageID === undefined ? undefined : steps.get(messageID);
|
|
118
|
+
if (messageID === undefined || step === undefined)
|
|
119
|
+
return;
|
|
120
|
+
steps.delete(messageID);
|
|
121
|
+
await toHandoff("message.updated", {
|
|
122
|
+
info: {
|
|
123
|
+
id: messageID,
|
|
124
|
+
role: "assistant",
|
|
125
|
+
sessionID,
|
|
126
|
+
providerID: step.providerID,
|
|
127
|
+
modelID: step.modelID,
|
|
128
|
+
...(step.agent === undefined ? {} : { mode: step.agent }),
|
|
129
|
+
tokens: data.tokens,
|
|
130
|
+
time: { created: step.started },
|
|
131
|
+
...(data.finish === "error" ? { error: { name: "StepError" } } : {})
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
case "session.status":
|
|
137
|
+
statuses.set(sessionID, data.status);
|
|
138
|
+
await toHandoff("session.status", { sessionID, status: data.status });
|
|
139
|
+
return;
|
|
140
|
+
case "session.idle":
|
|
141
|
+
await toHandoff("session.idle", { sessionID });
|
|
142
|
+
return;
|
|
143
|
+
case "session.compaction.ended":
|
|
144
|
+
await toHandoff("session.compacted", { sessionID });
|
|
145
|
+
return;
|
|
146
|
+
case "session.text.ended": {
|
|
147
|
+
const messageID = str(data.assistantMessageID);
|
|
148
|
+
if (messageID === undefined || typeof data.text !== "string")
|
|
149
|
+
return;
|
|
150
|
+
await recordProgress(sessionID, messageID, typeof data.ordinal === "number" ? data.ordinal : 0, data.text);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
default:
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Reads the stream until stopped. A failing handler never ends the subscription. */
|
|
158
|
+
function start(events) {
|
|
159
|
+
const controller = new AbortController();
|
|
160
|
+
const loop = (async () => {
|
|
161
|
+
try {
|
|
162
|
+
for await (const event of events.subscribe({ signal: controller.signal })) {
|
|
163
|
+
if (controller.signal.aborted)
|
|
164
|
+
break;
|
|
165
|
+
try {
|
|
166
|
+
await handle(event);
|
|
167
|
+
}
|
|
168
|
+
catch { // no-excuse-ok: catch -- one bad event must not stop the plugin from reading the rest.
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch { // no-excuse-ok: catch -- the stream closes with an error when the instance stops.
|
|
173
|
+
}
|
|
174
|
+
})();
|
|
175
|
+
return async () => {
|
|
176
|
+
controller.abort();
|
|
177
|
+
await loop;
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
handle,
|
|
182
|
+
start,
|
|
183
|
+
/** Steps started and not yet ended. */
|
|
184
|
+
pendingSteps: () => steps.size,
|
|
185
|
+
route: autoHandoff.route,
|
|
186
|
+
/** The saved handoff, handed back once after a compaction. */
|
|
187
|
+
async systemText(sessionID) {
|
|
188
|
+
const output = { system: [] };
|
|
189
|
+
await autoHandoff.systemTransform({ sessionID }, output);
|
|
190
|
+
return output.system;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DeliverableHedgeGuardState } from "../deliverable-hedge-guard.ts";
|
|
2
|
+
import type { SessionLookup } from "../session-lineage.ts";
|
|
3
|
+
export type ToolBeforeEvent = {
|
|
4
|
+
tool: string;
|
|
5
|
+
readonly sessionID: string;
|
|
6
|
+
readonly agent: string;
|
|
7
|
+
readonly messageID: string;
|
|
8
|
+
readonly id: string;
|
|
9
|
+
input: unknown;
|
|
10
|
+
};
|
|
11
|
+
type ToolContent = string | ReadonlyArray<{
|
|
12
|
+
readonly type: string;
|
|
13
|
+
readonly text?: string;
|
|
14
|
+
}>;
|
|
15
|
+
export type ToolAfterEvent = {
|
|
16
|
+
readonly tool: string;
|
|
17
|
+
readonly sessionID: string;
|
|
18
|
+
readonly agent: string;
|
|
19
|
+
readonly messageID: string;
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly input: unknown;
|
|
22
|
+
readonly status: "completed" | "error";
|
|
23
|
+
result?: {
|
|
24
|
+
content?: ToolContent;
|
|
25
|
+
metadata?: Readonly<Record<string, unknown>>;
|
|
26
|
+
output?: unknown;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export type V2ToolHookOptions = {
|
|
30
|
+
readonly projectRoot: string;
|
|
31
|
+
readonly knowledgeCaptureEnabled: boolean;
|
|
32
|
+
readonly getSession: SessionLookup;
|
|
33
|
+
readonly deliverableHedgeGuard: DeliverableHedgeGuardState;
|
|
34
|
+
};
|
|
35
|
+
export declare function createV2ToolHooks(options: V2ToolHookOptions): {
|
|
36
|
+
executeBefore: (event: ToolBeforeEvent) => Promise<void>;
|
|
37
|
+
executeAfter: (event: ToolAfterEvent) => Promise<void>;
|
|
38
|
+
};
|
|
39
|
+
export {};
|