@litfamily/litopencode 1.0.28 → 1.0.30
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 +28 -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/deliverable-hedge-guard.js +21 -9
- 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 +11 -1
- 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/rules/bundled-rules/lit-humanizer.md +2 -0
- 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/SKILL.md +4 -0
- package/skills/lit-humanizer/scripts/core.mjs +81 -1
- package/skills/lit-humanizer/scripts/detect.mjs +4 -3
- package/skills/lit-humanizer/scripts/guard-scan.mjs +12 -6
- 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/runtime.mjs +12 -13
- package/skills/{durable-litgoal → litgoal}/SKILL.md +31 -12
- package/skills/managed-skill-manifest.json +175 -31
- 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,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 {};
|
package/dist/v2/hooks.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Tool hooks on OpenCode 2, reusing the OpenCode 1 guards. OpenCode 2 renamed two tools (bash is shell,
|
|
2
|
+
// task is subagent) and names a file argument `path`, so each call is shown to the guards under the
|
|
3
|
+
// names they know. A throw in `execute.before` fails that one call; `execute.after` must never throw.
|
|
4
|
+
import { createToolExecuteAfterHook, createToolExecuteBeforeHook } from "../hooks.js";
|
|
5
|
+
const v1ToolNames = { shell: "bash", subagent: "task" };
|
|
6
|
+
function v1ToolName(tool) {
|
|
7
|
+
return v1ToolNames[tool] ?? tool;
|
|
8
|
+
}
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
/** The arguments as the OpenCode 1 guards read them: a `path` also answers to `filePath`. */
|
|
13
|
+
function v1Args(input) {
|
|
14
|
+
if (!isRecord(input) || typeof input.path !== "string" || "filePath" in input)
|
|
15
|
+
return { args: input, aliased: false };
|
|
16
|
+
return { args: { ...input, filePath: input.path }, aliased: true };
|
|
17
|
+
}
|
|
18
|
+
function contentText(content) {
|
|
19
|
+
if (content === undefined)
|
|
20
|
+
return "";
|
|
21
|
+
if (typeof content === "string")
|
|
22
|
+
return content;
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
export function createV2ToolHooks(options) {
|
|
26
|
+
const before = createToolExecuteBeforeHook({ getSession: options.getSession, deliverableHedgeGuard: options.deliverableHedgeGuard });
|
|
27
|
+
const after = createToolExecuteAfterHook({
|
|
28
|
+
projectRoot: options.projectRoot,
|
|
29
|
+
knowledgeCaptureEnabled: options.knowledgeCaptureEnabled,
|
|
30
|
+
deliverableHedgeGuard: options.deliverableHedgeGuard
|
|
31
|
+
});
|
|
32
|
+
async function executeBefore(event) {
|
|
33
|
+
const { args, aliased } = v1Args(event.input);
|
|
34
|
+
const output = { args };
|
|
35
|
+
await before({ tool: v1ToolName(event.tool), sessionID: event.sessionID, callID: event.id }, output);
|
|
36
|
+
if (output.args === args)
|
|
37
|
+
return;
|
|
38
|
+
if (aliased && isRecord(output.args)) {
|
|
39
|
+
const { filePath: _alias, ...rest } = output.args;
|
|
40
|
+
event.input = rest;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
event.input = output.args;
|
|
44
|
+
}
|
|
45
|
+
async function executeAfter(event) {
|
|
46
|
+
if (event.status !== "completed" || event.result === undefined)
|
|
47
|
+
return;
|
|
48
|
+
const text = contentText(event.result.content);
|
|
49
|
+
if (text === undefined)
|
|
50
|
+
return;
|
|
51
|
+
const output = { title: "", output: text, metadata: { ...(event.result.metadata ?? {}) } };
|
|
52
|
+
try {
|
|
53
|
+
await after({ tool: v1ToolName(event.tool), sessionID: event.sessionID, callID: event.id, args: v1Args(event.input).args }, output);
|
|
54
|
+
}
|
|
55
|
+
catch { // no-excuse-ok: catch -- OpenCode 2 treats a throw after a tool ran as a crash; the result stays as it was.
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
event.result = { ...event.result, content: output.output, metadata: output.metadata };
|
|
59
|
+
}
|
|
60
|
+
return { executeBefore, executeAfter };
|
|
61
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// LitOpenCode's OpenCode 2 server plugin. OpenCode 2 runs one instance per project location and calls
|
|
2
|
+
// `setup` once for it; every registration below is released by the host when the instance stops, and
|
|
3
|
+
// the returned cleanup releases what the plugin itself holds. There is no automatic self-update here:
|
|
4
|
+
// OpenCode 2 updates plugins itself (`opencode plugin update` and its update policy).
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { loadConfig } from "../config.js";
|
|
7
|
+
import { createDeliverableHedgeGuard } from "../deliverable-hedge-guard.js";
|
|
8
|
+
import { createLogger } from "../logger.js";
|
|
9
|
+
import { defaultOpenCodeConfigRoot } from "../state.js";
|
|
10
|
+
import { applyLitAgents, createSessionModelRoute, enforcePlanningOnly, planLitAgents, registerOpenAiProviderModels } from "./agents.js";
|
|
11
|
+
import { createSessionLookupV2, projectRootFromLocation } from "./context.js";
|
|
12
|
+
import { createV2Commands } from "./commands.js";
|
|
13
|
+
import { createV2Events } from "./events.js";
|
|
14
|
+
import { createV2ToolHooks } from "./hooks.js";
|
|
15
|
+
import { createV2SessionHooks } from "./session-hooks.js";
|
|
16
|
+
import { addLitTools, litToolsForProject } from "./tools.js";
|
|
17
|
+
export async function setupLitOpenCodeServer(context) {
|
|
18
|
+
const root = projectRootFromLocation(context.location);
|
|
19
|
+
const loaded = await loadConfig(root);
|
|
20
|
+
const logger = createLogger(loaded.paths);
|
|
21
|
+
const getSession = createSessionLookupV2(context.session);
|
|
22
|
+
const deliverableHedgeGuard = createDeliverableHedgeGuard({ projectRoot: context.location.directory.trim() || root });
|
|
23
|
+
const agentPlan = planLitAgents(loaded.config);
|
|
24
|
+
const toolHooks = createV2ToolHooks({
|
|
25
|
+
projectRoot: root,
|
|
26
|
+
knowledgeCaptureEnabled: loaded.config.knowledge.capture,
|
|
27
|
+
getSession,
|
|
28
|
+
deliverableHedgeGuard
|
|
29
|
+
});
|
|
30
|
+
await context.model.transform(registerOpenAiProviderModels);
|
|
31
|
+
await context.agent.transform((editor) => applyLitAgents(editor, agentPlan));
|
|
32
|
+
await context.permission.hook("evaluate", enforcePlanningOnly);
|
|
33
|
+
const tools = litToolsForProject(root);
|
|
34
|
+
await context.tool.transform((editor) => addLitTools(editor, tools));
|
|
35
|
+
await context.tool.hook("execute.before", toolHooks.executeBefore);
|
|
36
|
+
await context.tool.hook("execute.after", toolHooks.executeAfter);
|
|
37
|
+
const commands = createV2Commands([defaultOpenCodeConfigRoot(), path.join(root, ".opencode")], context.session);
|
|
38
|
+
await context.command.transform((editor) => commands.add(editor));
|
|
39
|
+
const events = createV2Events({
|
|
40
|
+
projectRoot: root,
|
|
41
|
+
paths: loaded.paths,
|
|
42
|
+
autoHandoffConfig: loaded.config.autoHandoff,
|
|
43
|
+
boundedAuthority: loaded.config.boundedAuthority,
|
|
44
|
+
getSession,
|
|
45
|
+
session: context.session,
|
|
46
|
+
model: context.model,
|
|
47
|
+
onSessionDeleted: (sessionID) => sessionHooks.clear(sessionID)
|
|
48
|
+
});
|
|
49
|
+
const sessionHooks = createV2SessionHooks({
|
|
50
|
+
projectRoot: root,
|
|
51
|
+
config: loaded.config,
|
|
52
|
+
getSession,
|
|
53
|
+
routeModel: createSessionModelRoute(agentPlan, context.session, context.model),
|
|
54
|
+
requestOptions: agentPlan.requestOptions,
|
|
55
|
+
autoHandoffRoute: events.route,
|
|
56
|
+
extraSystem: events.systemText,
|
|
57
|
+
acceptCommandMark: commands.acceptMark
|
|
58
|
+
});
|
|
59
|
+
await context.session.hook("prompt", sessionHooks.prompt);
|
|
60
|
+
await context.session.hook("context", sessionHooks.context);
|
|
61
|
+
await context.session.hook("compaction", sessionHooks.compaction);
|
|
62
|
+
const stopEvents = events.start(context.event);
|
|
63
|
+
return async () => {
|
|
64
|
+
await stopEvents();
|
|
65
|
+
deliverableHedgeGuard.dispose();
|
|
66
|
+
await logger.dispose();
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type ChatActivationMode } from "../activation-routing.ts";
|
|
2
|
+
import { type AutoHandoffRouteMode } from "../auto-handoff.ts";
|
|
3
|
+
import type { LitOpenCodeConfig } from "../config.ts";
|
|
4
|
+
import type { AgentRequestOptions } from "./agents.ts";
|
|
5
|
+
export type SystemPartLike = {
|
|
6
|
+
readonly type: "text";
|
|
7
|
+
readonly text: string;
|
|
8
|
+
};
|
|
9
|
+
export type PromptEvent = {
|
|
10
|
+
readonly sessionID: string;
|
|
11
|
+
readonly messageID: string;
|
|
12
|
+
prompt: {
|
|
13
|
+
text: string;
|
|
14
|
+
};
|
|
15
|
+
metadata?: Record<string, unknown>;
|
|
16
|
+
};
|
|
17
|
+
/** An OpenCode 2 request message: an instance of the host's own message class. */
|
|
18
|
+
export type MessageLike = {
|
|
19
|
+
readonly role: string;
|
|
20
|
+
readonly content: readonly unknown[];
|
|
21
|
+
};
|
|
22
|
+
export type ContextEvent = {
|
|
23
|
+
readonly sessionID: string;
|
|
24
|
+
readonly agent: string;
|
|
25
|
+
system: SystemPartLike[];
|
|
26
|
+
messages?: MessageLike[];
|
|
27
|
+
options: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
export type SessionAgentLookup = (sessionID: string) => Promise<{
|
|
30
|
+
readonly parentID?: string;
|
|
31
|
+
readonly agent?: string;
|
|
32
|
+
} | undefined>;
|
|
33
|
+
export type V2SessionHookOptions = {
|
|
34
|
+
readonly projectRoot: string;
|
|
35
|
+
readonly config: Pick<LitOpenCodeConfig, "outputStyle" | "boundedAuthority">;
|
|
36
|
+
readonly getSession: SessionAgentLookup;
|
|
37
|
+
/** Puts a session that has no model of its own on its agent's route. */
|
|
38
|
+
readonly routeModel?: (sessionID: string) => Promise<void>;
|
|
39
|
+
readonly requestOptions: ReadonlyMap<string, AgentRequestOptions>;
|
|
40
|
+
readonly autoHandoffRoute?: (text: string, mode: AutoHandoffRouteMode) => Promise<string | undefined>;
|
|
41
|
+
/** Extra system text for this request, such as the saved handoff after a compaction. */
|
|
42
|
+
readonly extraSystem?: (sessionID: string) => Promise<readonly string[]>;
|
|
43
|
+
readonly onActivation?: (sessionID: string, mode: ChatActivationMode | string) => void;
|
|
44
|
+
/** Accepts a command mark the plugin itself sent for this session; anything else is dropped. */
|
|
45
|
+
readonly acceptCommandMark?: (sessionID: string, marker: unknown) => {
|
|
46
|
+
readonly command: string;
|
|
47
|
+
readonly banner: string;
|
|
48
|
+
} | undefined;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* `opencode2 run "lit fix it"` sends the message wrapped in double quotes (each multi-word argument is
|
|
52
|
+
* quoted, inner quotes escaped). The prompt hook runs in the server, so the run's argv is not available
|
|
53
|
+
* the way it is on OpenCode 1; a message that is exactly one such quoted argument is read without the
|
|
54
|
+
* quotes. Quoted text inside a longer prompt stays inactive. The server cannot tell a run argument from
|
|
55
|
+
* a TUI prompt typed entirely inside double quotes, so such a typed `"lit fix it"` activates as well;
|
|
56
|
+
* on OpenCode 1 it stayed inert.
|
|
57
|
+
*/
|
|
58
|
+
export declare function unwrapRunArgument(input: string): string;
|
|
59
|
+
export declare function createV2SessionHooks(options: V2SessionHookOptions): {
|
|
60
|
+
prompt: (event: PromptEvent) => Promise<void>;
|
|
61
|
+
context: (event: ContextEvent) => Promise<void>;
|
|
62
|
+
compaction: (event: ContextEvent) => Promise<void>;
|
|
63
|
+
clear(sessionID: string): void;
|
|
64
|
+
};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Prompt admission and request context on OpenCode 2. OpenCode 1 appended activation text to the user
|
|
2
|
+
// message as an extra part; OpenCode 2 has no parts. Here the `prompt` hook marks a lit prompt in the
|
|
3
|
+
// user message metadata (which the TUI reads for the ignition toast and the footer line), and the
|
|
4
|
+
// `context` hook hands the activation text, the static rules and the reader contract to every model
|
|
5
|
+
// request of that session as system parts. Text that comes from the project or a lifecycle file rather
|
|
6
|
+
// than from LitOpenCode's own instructions (local knowledge, a start-work receipt, an auto-handoff setting
|
|
7
|
+
// reply) rides the latest user message instead, the channel it had on OpenCode 1.
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { createActivationMemory, decideReentry } from "../activation-reentry.js";
|
|
10
|
+
import { detectChatActivationMode, promptForChatActivationMode } from "../activation-routing.js";
|
|
11
|
+
import { startWorkPromptInjection } from "../activation-workflow-prompts.js";
|
|
12
|
+
import { autoHandoffSettingText } from "../auto-handoff.js";
|
|
13
|
+
import { applyTrustedStartWorkDirective, parseStartWorkLifecycleDirective } from "../bounded-authority-hooks.js";
|
|
14
|
+
import { queryKnowledge } from "../knowledge.js";
|
|
15
|
+
import { createLitGoalOperations } from "../ledger.js";
|
|
16
|
+
import { applyCompactionRuleReset, applyStaticRuleInjection } from "../rules/hooks.js";
|
|
17
|
+
import { isReadOnlyWorkflowFamilyPlan } from "../workflow-families.js";
|
|
18
|
+
const bundledRulesDir = fileURLToPath(new URL("../../rules/bundled-rules", import.meta.url));
|
|
19
|
+
/**
|
|
20
|
+
* `opencode2 run "lit fix it"` sends the message wrapped in double quotes (each multi-word argument is
|
|
21
|
+
* quoted, inner quotes escaped). The prompt hook runs in the server, so the run's argv is not available
|
|
22
|
+
* the way it is on OpenCode 1; a message that is exactly one such quoted argument is read without the
|
|
23
|
+
* quotes. Quoted text inside a longer prompt stays inactive. The server cannot tell a run argument from
|
|
24
|
+
* a TUI prompt typed entirely inside double quotes, so such a typed `"lit fix it"` activates as well;
|
|
25
|
+
* on OpenCode 1 it stayed inert.
|
|
26
|
+
*/
|
|
27
|
+
export function unwrapRunArgument(input) {
|
|
28
|
+
const match = /^"((?:[^"\\]|\\.)*)"$/su.exec(input);
|
|
29
|
+
return match === null ? input : match[1].replace(/\\"/gu, '"');
|
|
30
|
+
}
|
|
31
|
+
function text(value) {
|
|
32
|
+
return { type: "text", text: value };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Adds text parts to the latest user message. The host builds each message with its own class, so the
|
|
36
|
+
* copy is made with that class; false when there is no user message to carry the text.
|
|
37
|
+
*/
|
|
38
|
+
function appendToLatestUserMessage(messages, texts) {
|
|
39
|
+
if (messages === undefined)
|
|
40
|
+
return false;
|
|
41
|
+
let index = messages.length - 1;
|
|
42
|
+
while (index >= 0 && messages[index].role !== "user")
|
|
43
|
+
index -= 1;
|
|
44
|
+
if (index < 0)
|
|
45
|
+
return false;
|
|
46
|
+
const latest = messages[index];
|
|
47
|
+
const Message = latest.constructor;
|
|
48
|
+
messages[index] = new Message({ ...latest, content: [...latest.content, ...texts.map(text)] });
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
export function createV2SessionHooks(options) {
|
|
52
|
+
const operations = createLitGoalOperations(options.projectRoot);
|
|
53
|
+
const sessions = new Map();
|
|
54
|
+
// The latest activation stays in every request of its session, so a repeated lit only adds a note to it.
|
|
55
|
+
const activationMemory = createActivationMemory();
|
|
56
|
+
function stateFor(sessionID) {
|
|
57
|
+
let state = sessions.get(sessionID);
|
|
58
|
+
if (state === undefined) {
|
|
59
|
+
state = { turn: [], user: [] };
|
|
60
|
+
sessions.set(sessionID, state);
|
|
61
|
+
}
|
|
62
|
+
return state;
|
|
63
|
+
}
|
|
64
|
+
async function lookup(sessionID) {
|
|
65
|
+
try {
|
|
66
|
+
const session = await options.getSession(sessionID);
|
|
67
|
+
if (session === undefined)
|
|
68
|
+
return { child: true };
|
|
69
|
+
return { child: session.parentID !== undefined, ...(session.agent === undefined ? {} : { agent: session.agent }) };
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Fail closed: a session whose lineage cannot be read is treated as delegated.
|
|
73
|
+
return { child: true };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Keeps a command mark only when the plugin sent it; returns true for such a trusted command turn. */
|
|
77
|
+
function admitCommandMark(event) {
|
|
78
|
+
const metadata = event.metadata;
|
|
79
|
+
if (metadata === undefined || !("litopencode" in metadata))
|
|
80
|
+
return false;
|
|
81
|
+
const { litopencode: marker, ...rest } = metadata;
|
|
82
|
+
const accepted = options.acceptCommandMark?.(event.sessionID, marker);
|
|
83
|
+
if (accepted === undefined) {
|
|
84
|
+
event.metadata = Object.keys(rest).length === 0 ? undefined : rest;
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
event.metadata = { ...rest, litopencode: { command: accepted.command, banner: accepted.banner, source: "command" } };
|
|
88
|
+
options.onActivation?.(event.sessionID, accepted.command);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
async function prompt(event) {
|
|
92
|
+
const input = unwrapRunArgument(event.prompt.text);
|
|
93
|
+
try {
|
|
94
|
+
await options.routeModel?.(event.sessionID);
|
|
95
|
+
}
|
|
96
|
+
catch { // no-excuse-ok: catch -- a failed route switch must never block the user message.
|
|
97
|
+
// The session keeps the model the host chose.
|
|
98
|
+
}
|
|
99
|
+
if (admitCommandMark(event)) {
|
|
100
|
+
const state = stateFor(event.sessionID);
|
|
101
|
+
state.turn = [];
|
|
102
|
+
state.user = [];
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const session = await lookup(event.sessionID);
|
|
106
|
+
if (session.child)
|
|
107
|
+
return;
|
|
108
|
+
const state = stateFor(event.sessionID);
|
|
109
|
+
state.turn = [];
|
|
110
|
+
state.user = [];
|
|
111
|
+
if (input.trim() === "")
|
|
112
|
+
return;
|
|
113
|
+
if (options.autoHandoffRoute !== undefined) {
|
|
114
|
+
const reply = await options.autoHandoffRoute(input, "chat");
|
|
115
|
+
if (reply !== undefined) {
|
|
116
|
+
state.user.push(autoHandoffSettingText(reply));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const knowledge = await queryKnowledge(options.projectRoot, input.slice(0, 4096));
|
|
122
|
+
if (knowledge.text !== "")
|
|
123
|
+
state.user.push(knowledge.text);
|
|
124
|
+
}
|
|
125
|
+
catch { // no-excuse-ok: catch -- local relevance must never block the user message.
|
|
126
|
+
// A malformed or unreadable knowledge store stays silent instead of inventing context.
|
|
127
|
+
}
|
|
128
|
+
const mode = detectChatActivationMode(input, session.agent);
|
|
129
|
+
if (mode === undefined)
|
|
130
|
+
return;
|
|
131
|
+
const trigger = mode === "lit-handoff" ? "handoff" : mode === "lit-scientific-visualization" ? mode : "lit";
|
|
132
|
+
let activation = promptForChatActivationMode(mode, input);
|
|
133
|
+
if (mode === "start-work" && session.agent !== "lit-plan") {
|
|
134
|
+
const directive = parseStartWorkLifecycleDirective(input, { chat: true });
|
|
135
|
+
if (directive?.action === "resume") {
|
|
136
|
+
try {
|
|
137
|
+
const receipt = await applyTrustedStartWorkDirective(options.projectRoot, event.sessionID, directive, options.config.boundedAuthority);
|
|
138
|
+
state.user.push(receipt.text);
|
|
139
|
+
activation = startWorkPromptInjection;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
activation = "BLOCKED: bounded-authority lifecycle resume rejected. Use /start-work with the exact pending boundary, grant, work id, session, and CAS revision.";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const reentry = await decideReentry({
|
|
147
|
+
memory: activationMemory,
|
|
148
|
+
projectRoot: options.projectRoot,
|
|
149
|
+
sessionID: event.sessionID,
|
|
150
|
+
mode,
|
|
151
|
+
text: input
|
|
152
|
+
});
|
|
153
|
+
if (reentry.kind === "continue") {
|
|
154
|
+
state.turn.push(reentry.text);
|
|
155
|
+
event.metadata = { ...(event.metadata ?? {}), litopencode: { mode: reentry.mode, trigger, source: "prompt", reentry: "continue" } };
|
|
156
|
+
options.onActivation?.(event.sessionID, reentry.mode);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (reentry.kind === "resume")
|
|
160
|
+
activation = `${reentry.line}\n${activation}`;
|
|
161
|
+
else if (reentry.kind === "confirm")
|
|
162
|
+
activation = `${activation}\n${reentry.line}`;
|
|
163
|
+
state.activation = activation;
|
|
164
|
+
event.metadata = {
|
|
165
|
+
...(event.metadata ?? {}),
|
|
166
|
+
litopencode: { mode, trigger, source: "prompt", ...(reentry.kind === "full" ? {} : { reentry: reentry.kind }) }
|
|
167
|
+
};
|
|
168
|
+
options.onActivation?.(event.sessionID, mode);
|
|
169
|
+
if (reentry.kind !== "confirm" && mode !== "lit-handoff" && mode !== "lit-scientific-visualization") {
|
|
170
|
+
activationMemory.remember(event.sessionID, mode);
|
|
171
|
+
}
|
|
172
|
+
if (mode === "lit-task" || mode === "lit-recap" || mode === "lit-brainstorm" || isReadOnlyWorkflowFamilyPlan(mode))
|
|
173
|
+
return;
|
|
174
|
+
try {
|
|
175
|
+
await operations.init();
|
|
176
|
+
await operations.append({
|
|
177
|
+
type: "prompt.activated",
|
|
178
|
+
trigger,
|
|
179
|
+
source: "prompt",
|
|
180
|
+
mode,
|
|
181
|
+
sessionID: event.sessionID,
|
|
182
|
+
messageID: event.messageID,
|
|
183
|
+
...(session.agent === undefined ? {} : { agent: session.agent }),
|
|
184
|
+
timestamp: new Date().toISOString()
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch { // no-excuse-ok: catch -- activation must survive best-effort ledger failure.
|
|
188
|
+
// The activation text is the primary surface; the ledger write is best-effort.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function context(event) {
|
|
192
|
+
const state = sessions.get(event.sessionID);
|
|
193
|
+
if (state?.activation !== undefined)
|
|
194
|
+
event.system.push(text(state.activation));
|
|
195
|
+
for (const entry of state?.turn ?? [])
|
|
196
|
+
event.system.push(text(entry));
|
|
197
|
+
const user = state?.user ?? [];
|
|
198
|
+
if (user.length > 0 && !appendToLatestUserMessage(event.messages, user)) {
|
|
199
|
+
for (const entry of user)
|
|
200
|
+
event.system.push(text(entry));
|
|
201
|
+
}
|
|
202
|
+
for (const entry of (await options.extraSystem?.(event.sessionID)) ?? [])
|
|
203
|
+
event.system.push(text(entry));
|
|
204
|
+
const rules = { system: [] };
|
|
205
|
+
await applyStaticRuleInjection({ projectRoot: options.projectRoot, bundledRulesDir, outputStyle: options.config.outputStyle }, { sessionID: event.sessionID }, rules);
|
|
206
|
+
for (const entry of rules.system)
|
|
207
|
+
event.system.push(text(entry));
|
|
208
|
+
const request = options.requestOptions.get(event.agent);
|
|
209
|
+
if (request !== undefined) {
|
|
210
|
+
for (const [key, value] of Object.entries(request))
|
|
211
|
+
if (event.options[key] === undefined)
|
|
212
|
+
event.options[key] = value;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async function compaction(event) {
|
|
216
|
+
const output = { context: [] };
|
|
217
|
+
await applyCompactionRuleReset({ sessionID: event.sessionID }, output);
|
|
218
|
+
for (const entry of output.context)
|
|
219
|
+
event.system.push(text(entry));
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
prompt,
|
|
223
|
+
context,
|
|
224
|
+
compaction,
|
|
225
|
+
clear(sessionID) {
|
|
226
|
+
sessions.delete(sessionID);
|
|
227
|
+
activationMemory.forget(sessionID);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|