jeo-code 0.10.0 → 0.11.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/CHANGELOG.md +37 -0
- package/README.ja.md +35 -3
- package/README.ko.md +35 -3
- package/README.md +36 -4
- package/README.zh.md +33 -3
- package/package.json +1 -1
- package/src/agent/config-schema.ts +6 -0
- package/src/agent/context-files.ts +90 -11
- package/src/agent/notify/daemon-control.ts +79 -12
- package/src/agent/notify/session-endpoint.ts +15 -0
- package/src/agent/task-tool.ts +27 -3
- package/src/ai/model-retirements.ts +123 -0
- package/src/commands/daemon.ts +8 -0
- package/src/commands/doctor.ts +14 -0
- package/src/commands/launch.ts +222 -211
package/src/commands/launch.ts
CHANGED
|
@@ -1974,239 +1974,250 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1974
1974
|
};
|
|
1975
1975
|
|
|
1976
1976
|
if (isOneShot) {
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
// a slow, flaky, and semantically wrong round-trip. These no-arg session/system
|
|
1989
|
-
// commands have a meaningful one-shot effect (or are simple no-ops) and exit.
|
|
1990
|
-
{
|
|
1991
|
-
const cmd = messageContent.trim();
|
|
1992
|
-
if (cmd === "/exit" || cmd === "/quit") {
|
|
1993
|
-
return;
|
|
1994
|
-
}
|
|
1995
|
-
if (cmd === "/clear" || cmd === "/session new" || cmd === "/session drop" || cmd === "/session delete") {
|
|
1996
|
-
// Reset history to just the system prompt and overwrite the session file so
|
|
1997
|
-
// the persisted transcript matches (a fresh session for /session new and /session drop).
|
|
1998
|
-
history.length = 1;
|
|
1999
|
-
if (sessionId && !flags.noSession) {
|
|
2000
|
-
try {
|
|
2001
|
-
sessionId = (await createSession(cwd, undefined, sessionModel)).id;
|
|
2002
|
-
} catch { /* best-effort: in-memory clear already done */ }
|
|
2003
|
-
}
|
|
2004
|
-
console.log("(history cleared)");
|
|
2005
|
-
return;
|
|
2006
|
-
}
|
|
2007
|
-
if (cmd === "/" || cmd === "/?" || cmd === "/help") {
|
|
2008
|
-
for (const line of formatSlashCommandList("/", skillSlashDetails)) console.log(line);
|
|
2009
|
-
console.log("Tools: read / write / edit / bash / find / search. Sessions persist to .jeo/sessions/.");
|
|
2010
|
-
return;
|
|
2011
|
-
}
|
|
2012
|
-
if (cmd === "/usage") {
|
|
2013
|
-
const cfg = await readGlobalConfig();
|
|
2014
|
-
const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
|
|
2015
|
-
const result = handleUsage(ctx, sessionUsage);
|
|
2016
|
-
if (result && "lines" in result) for (const line of result.lines) console.log(line);
|
|
2017
|
-
return;
|
|
2018
|
-
}
|
|
2019
|
-
if (cmd === "/context") {
|
|
2020
|
-
const cfg = await readGlobalConfig();
|
|
2021
|
-
const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
|
|
2022
|
-
const result = await handleContext(ctx);
|
|
2023
|
-
if (result && "lines" in result) for (const line of result.lines) console.log(line);
|
|
2024
|
-
return;
|
|
2025
|
-
}
|
|
2026
|
-
if (cmd === "/tools") {
|
|
2027
|
-
const cfg = await readGlobalConfig();
|
|
2028
|
-
const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
|
|
2029
|
-
const result = await handleTools(ctx);
|
|
2030
|
-
if (result && "lines" in result) for (const line of result.lines) console.log(line);
|
|
2031
|
-
return;
|
|
2032
|
-
}
|
|
2033
|
-
if (cmd === "/hotkeys") {
|
|
2034
|
-
const cfg = await readGlobalConfig();
|
|
2035
|
-
const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
|
|
2036
|
-
const result = handleHotkeys(ctx);
|
|
2037
|
-
if (result && "lines" in result) for (const line of result.lines) console.log(line);
|
|
2038
|
-
return;
|
|
1977
|
+
// A one-shot run has ~17 exit points below. The session notify endpoint (a
|
|
1978
|
+
// listening socket + a discovery file the Telegram daemon polls) is created for
|
|
1979
|
+
// the WHOLE launch lifetime up at `startSessionNotifyEndpoint`, but its teardown
|
|
1980
|
+
// lives on the interactive REPL's exit path — which none of those 17 returns
|
|
1981
|
+
// reach. The endpoint's socket/timer are unref'd so a leak can no longer HANG the
|
|
1982
|
+
// process, but a one-shot run would still leave a stale discovery file behind for
|
|
1983
|
+
// the daemon to keep dialling. One `finally` covers every exit point.
|
|
1984
|
+
try {
|
|
1985
|
+
let messageContent = joinedArgs;
|
|
1986
|
+
if (!process.stdin.isTTY && joinedArgs.length === 0) {
|
|
1987
|
+
messageContent = (await Bun.stdin.text()).trim();
|
|
2039
1988
|
}
|
|
2040
|
-
if (
|
|
2041
|
-
|
|
2042
|
-
const cfg = await readGlobalConfig();
|
|
2043
|
-
const activeModel = sessionModel || defaultModel;
|
|
2044
|
-
// `Config` (state.ts) predates config-schema.ts's `compaction` field and is
|
|
2045
|
-
// out of this change's scope — read the passthrough-preserved value via a
|
|
2046
|
-
// narrow structural cast instead of widening the shared interface.
|
|
2047
|
-
const focus = parsed.focus ?? (cfg as { compaction?: { handoffFocus?: string } }).compaction?.handoffFocus;
|
|
2048
|
-
const res = await buildHandoffDocument(history, { model: activeModel, focus });
|
|
2049
|
-
for (const line of handoffLines(res, process.stdout.columns)) console.log(line);
|
|
1989
|
+
if (!messageContent) {
|
|
1990
|
+
console.log("No input provided.");
|
|
2050
1991
|
return;
|
|
2051
1992
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
1993
|
+
// One-shot piped/`-p` input that is a control slash command must be handled
|
|
1994
|
+
// HERE — never forwarded to the model. `echo "/clear" | jeo` (and Ctrl-D after
|
|
1995
|
+
// a piped command) previously sent the literal "/clear" to the LLM as a prompt:
|
|
1996
|
+
// a slow, flaky, and semantically wrong round-trip. These no-arg session/system
|
|
1997
|
+
// commands have a meaningful one-shot effect (or are simple no-ops) and exit.
|
|
1998
|
+
{
|
|
1999
|
+
const cmd = messageContent.trim();
|
|
2000
|
+
if (cmd === "/exit" || cmd === "/quit") {
|
|
2001
|
+
return;
|
|
2061
2002
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
delete process.env.JEO_WIKI_ROOT;
|
|
2074
|
-
sessionWikiRoot = undefined;
|
|
2075
|
-
await refreshSessionMemory("");
|
|
2076
|
-
} else if (decision.kind === "set") {
|
|
2077
|
-
await saveConfigPatch(() => ({ wikiRoot: decision.persistArg }));
|
|
2078
|
-
process.env.JEO_WIKI_ROOT = decision.root;
|
|
2079
|
-
sessionWikiRoot = decision.root;
|
|
2080
|
-
await refreshSessionMemory("");
|
|
2003
|
+
if (cmd === "/clear" || cmd === "/session new" || cmd === "/session drop" || cmd === "/session delete") {
|
|
2004
|
+
// Reset history to just the system prompt and overwrite the session file so
|
|
2005
|
+
// the persisted transcript matches (a fresh session for /session new and /session drop).
|
|
2006
|
+
history.length = 1;
|
|
2007
|
+
if (sessionId && !flags.noSession) {
|
|
2008
|
+
try {
|
|
2009
|
+
sessionId = (await createSession(cwd, undefined, sessionModel)).id;
|
|
2010
|
+
} catch { /* best-effort: in-memory clear already done */ }
|
|
2011
|
+
}
|
|
2012
|
+
console.log("(history cleared)");
|
|
2013
|
+
return;
|
|
2081
2014
|
}
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
return;
|
|
2087
|
-
}
|
|
2088
|
-
if (cmd === "/evolve") {
|
|
2089
|
-
await runEvolveSimulation(animateAsciiArt);
|
|
2090
|
-
return;
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
// One skill run (bundle workflow → engine; regular skill → agent turn). Shared by the
|
|
2094
|
-
// single-invocation path and the `$a $b …` chain path so every `$` skill actually runs.
|
|
2095
|
-
const runOneSkillShot = async (inv: SkillInvocation): Promise<void> => {
|
|
2096
|
-
const isBundleWorkflow = isWorkflowSkill(inv.skill.name);
|
|
2097
|
-
if (isBundleWorkflow) {
|
|
2098
|
-
const startMsg: Message = {
|
|
2099
|
-
role: "system",
|
|
2100
|
-
content: `[workflow:${inv.skill.name}:start]${inv.intent ? ` intent: ${inv.intent}` : ""}`
|
|
2101
|
-
};
|
|
2102
|
-
history.push(startMsg);
|
|
2103
|
-
if (sessionId) {
|
|
2104
|
-
await appendMessage(sessionId, startMsg, cwd);
|
|
2015
|
+
if (cmd === "/" || cmd === "/?" || cmd === "/help") {
|
|
2016
|
+
for (const line of formatSlashCommandList("/", skillSlashDetails)) console.log(line);
|
|
2017
|
+
console.log("Tools: read / write / edit / bash / find / search. Sessions persist to .jeo/sessions/.");
|
|
2018
|
+
return;
|
|
2105
2019
|
}
|
|
2020
|
+
if (cmd === "/usage") {
|
|
2021
|
+
const cfg = await readGlobalConfig();
|
|
2022
|
+
const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
|
|
2023
|
+
const result = handleUsage(ctx, sessionUsage);
|
|
2024
|
+
if (result && "lines" in result) for (const line of result.lines) console.log(line);
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
if (cmd === "/context") {
|
|
2028
|
+
const cfg = await readGlobalConfig();
|
|
2029
|
+
const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
|
|
2030
|
+
const result = await handleContext(ctx);
|
|
2031
|
+
if (result && "lines" in result) for (const line of result.lines) console.log(line);
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
if (cmd === "/tools") {
|
|
2035
|
+
const cfg = await readGlobalConfig();
|
|
2036
|
+
const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
|
|
2037
|
+
const result = await handleTools(ctx);
|
|
2038
|
+
if (result && "lines" in result) for (const line of result.lines) console.log(line);
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
if (cmd === "/hotkeys") {
|
|
2042
|
+
const cfg = await readGlobalConfig();
|
|
2043
|
+
const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
|
|
2044
|
+
const result = handleHotkeys(ctx);
|
|
2045
|
+
if (result && "lines" in result) for (const line of result.lines) console.log(line);
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
if (cmd === "/handoff" || cmd.startsWith("/handoff ")) {
|
|
2049
|
+
const parsed = parseHandoffCommand(cmd) ?? {};
|
|
2050
|
+
const cfg = await readGlobalConfig();
|
|
2051
|
+
const activeModel = sessionModel || defaultModel;
|
|
2052
|
+
// `Config` (state.ts) predates config-schema.ts's `compaction` field and is
|
|
2053
|
+
// out of this change's scope — read the passthrough-preserved value via a
|
|
2054
|
+
// narrow structural cast instead of widening the shared interface.
|
|
2055
|
+
const focus = parsed.focus ?? (cfg as { compaction?: { handoffFocus?: string } }).compaction?.handoffFocus;
|
|
2056
|
+
const res = await buildHandoffDocument(history, { model: activeModel, focus });
|
|
2057
|
+
for (const line of handoffLines(res, process.stdout.columns)) console.log(line);
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
if (cmd === "/theme" || cmd.startsWith("/theme ")) {
|
|
2061
|
+
const decision = decideThemeSlash(cmd, listThemes(), resolveTheme().name);
|
|
2062
|
+
for (const line of decision.lines) console.log(line);
|
|
2063
|
+
if (decision.kind === "set") {
|
|
2064
|
+
process.env.JEO_TUI_THEME = decision.themeName!;
|
|
2065
|
+
await saveConfigPatch(raw => ({ theme: decision.themeName }));
|
|
2066
|
+
// No refreshUiTheme() here — one-shot mode returns immediately after this
|
|
2067
|
+
// block, so there is no live TUI paint state to re-resolve (and
|
|
2068
|
+
// refreshUiTheme is declared later in this closure, out of scope here).
|
|
2069
|
+
}
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
if (cmd === "/wiki" || cmd.startsWith("/wiki ")) {
|
|
2073
|
+
const decision = decideWikiSlash(
|
|
2074
|
+
cmd,
|
|
2075
|
+
resolveWikiRoot(await readGlobalConfig()),
|
|
2076
|
+
!!jeoEnv("WIKI_ROOT"),
|
|
2077
|
+
);
|
|
2078
|
+
for (const line of decision.lines) console.log(line);
|
|
2079
|
+
if (decision.kind === "clear") {
|
|
2080
|
+
await saveConfigPatch(() => ({ wikiRoot: undefined }));
|
|
2081
|
+
delete process.env.JEO_WIKI_ROOT;
|
|
2082
|
+
sessionWikiRoot = undefined;
|
|
2083
|
+
await refreshSessionMemory("");
|
|
2084
|
+
} else if (decision.kind === "set") {
|
|
2085
|
+
await saveConfigPatch(() => ({ wikiRoot: decision.persistArg }));
|
|
2086
|
+
process.env.JEO_WIKI_ROOT = decision.root;
|
|
2087
|
+
sessionWikiRoot = decision.root;
|
|
2088
|
+
await refreshSessionMemory("");
|
|
2089
|
+
}
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
if (cmd === "/config" || cmd === "/settings") {
|
|
2093
|
+
for (const line of await buildConfigPanelLines({ sessionModel, sessionThinking, sessionId })) console.log(line);
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
if (cmd === "/evolve") {
|
|
2097
|
+
await runEvolveSimulation(animateAsciiArt);
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
// One skill run (bundle workflow → engine; regular skill → agent turn). Shared by the
|
|
2102
|
+
// single-invocation path and the `$a $b …` chain path so every `$` skill actually runs.
|
|
2103
|
+
const runOneSkillShot = async (inv: SkillInvocation): Promise<void> => {
|
|
2104
|
+
const isBundleWorkflow = isWorkflowSkill(inv.skill.name);
|
|
2105
|
+
if (isBundleWorkflow) {
|
|
2106
|
+
const startMsg: Message = {
|
|
2107
|
+
role: "system",
|
|
2108
|
+
content: `[workflow:${inv.skill.name}:start]${inv.intent ? ` intent: ${inv.intent}` : ""}`
|
|
2109
|
+
};
|
|
2110
|
+
history.push(startMsg);
|
|
2111
|
+
if (sessionId) {
|
|
2112
|
+
await appendMessage(sessionId, startMsg, cwd);
|
|
2113
|
+
}
|
|
2106
2114
|
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2115
|
+
const harness = createInFlightAbortHarness({
|
|
2116
|
+
captureEsc: false,
|
|
2117
|
+
onAbortNotice: msg => console.log(msg),
|
|
2118
|
+
onHardExit: () => forceExitFromCtrlC(),
|
|
2119
|
+
});
|
|
2120
|
+
const ac = harness.controller;
|
|
2113
2121
|
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2122
|
+
const opts = {
|
|
2123
|
+
cwd,
|
|
2124
|
+
signal: ac.signal,
|
|
2125
|
+
onProgress: (e: { skill: string; phase: string; detail?: string }) => console.log(`[workflow:${e.skill}] ${e.phase}${e.detail ? ` — ${e.detail}` : ""}`),
|
|
2126
|
+
io: {
|
|
2127
|
+
output: (line: string) => {
|
|
2128
|
+
console.log(line);
|
|
2129
|
+
},
|
|
2130
|
+
// This `$a $b …` one-shot chain runs BEFORE the interactive REPL's
|
|
2131
|
+
// `promptInput`/`rl` exist (this whole dispatch is reached and returns
|
|
2132
|
+
// long before the `const promptInput = …` further down the function
|
|
2133
|
+
// executes) — referencing `promptInput` here throws a TDZ
|
|
2134
|
+
// ReferenceError ("Cannot access 'promptInput' before initialization")
|
|
2135
|
+
// the instant the workflow engine asks its first question. That error
|
|
2136
|
+
// is caught and stashed into session history (never printed to the
|
|
2137
|
+
// terminal), so the visible symptom is just a frozen prompt forever
|
|
2138
|
+
// (the "jeo --tmux $deep-interview hangs" / "$deep-interview hangs"
|
|
2139
|
+
// report). Omitting `input` here is safe and correct: no other
|
|
2140
|
+
// readline exists yet at this point, so deep-interview.ts's own
|
|
2141
|
+
// fallback `createInterface({input: process.stdin, ...})` (used
|
|
2142
|
+
// whenever `opts.io?.input` is absent) has nothing to fight over raw
|
|
2143
|
+
// mode with. The INTERACTIVE `runSkillInvocation` sibling path below
|
|
2144
|
+
// runs AFTER `promptInput` is declared and correctly wires it — do not
|
|
2145
|
+
// "fix" this block by copying that wiring here again.
|
|
2121
2146
|
},
|
|
2122
|
-
// This `$a $b …` one-shot chain runs BEFORE the interactive REPL's
|
|
2123
|
-
// `promptInput`/`rl` exist (this whole dispatch is reached and returns
|
|
2124
|
-
// long before the `const promptInput = …` further down the function
|
|
2125
|
-
// executes) — referencing `promptInput` here throws a TDZ
|
|
2126
|
-
// ReferenceError ("Cannot access 'promptInput' before initialization")
|
|
2127
|
-
// the instant the workflow engine asks its first question. That error
|
|
2128
|
-
// is caught and stashed into session history (never printed to the
|
|
2129
|
-
// terminal), so the visible symptom is just a frozen prompt forever
|
|
2130
|
-
// (the "jeo --tmux $deep-interview hangs" / "$deep-interview hangs"
|
|
2131
|
-
// report). Omitting `input` here is safe and correct: no other
|
|
2132
|
-
// readline exists yet at this point, so deep-interview.ts's own
|
|
2133
|
-
// fallback `createInterface({input: process.stdin, ...})` (used
|
|
2134
|
-
// whenever `opts.io?.input` is absent) has nothing to fight over raw
|
|
2135
|
-
// mode with. The INTERACTIVE `runSkillInvocation` sibling path below
|
|
2136
|
-
// runs AFTER `promptInput` is declared and correctly wires it — do not
|
|
2137
|
-
// "fix" this block by copying that wiring here again.
|
|
2138
|
-
},
|
|
2139
2147
|
|
|
2140
|
-
|
|
2148
|
+
args: inv.skill.name === "deep-interview" ? (inv.intent ? inv.intent.split(/\s+/) : []) : undefined
|
|
2141
2149
|
|
|
2142
|
-
|
|
2150
|
+
};
|
|
2143
2151
|
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2152
|
+
let ok = false;
|
|
2153
|
+
let reason: string | undefined;
|
|
2154
|
+
try {
|
|
2155
|
+
const res: { ok: boolean; reason?: string } = await runWorkflowEngine(inv.skill.name, opts);
|
|
2156
|
+
ok = res.ok;
|
|
2157
|
+
reason = res.reason;
|
|
2158
|
+
} catch (err: any) {
|
|
2159
|
+
ok = false;
|
|
2160
|
+
reason = err.message;
|
|
2161
|
+
} finally {
|
|
2162
|
+
harness.dispose();
|
|
2163
|
+
}
|
|
2156
2164
|
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
+
const endMsg: Message = {
|
|
2166
|
+
role: "system",
|
|
2167
|
+
content: ok
|
|
2168
|
+
? `[workflow:${inv.skill.name}:finish]`
|
|
2169
|
+
: `[workflow:${inv.skill.name}:abort]${reason ? ` reason: ${reason}` : ""}`
|
|
2170
|
+
};
|
|
2171
|
+
if (sessionId) {
|
|
2172
|
+
await appendMessage(sessionId, endMsg, cwd);
|
|
2173
|
+
}
|
|
2174
|
+
return;
|
|
2165
2175
|
}
|
|
2166
|
-
return;
|
|
2167
|
-
}
|
|
2168
2176
|
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2177
|
+
const useOneShotTui = shouldUseOneShotTui(flags.noTui);
|
|
2178
|
+
if (!useOneShotTui) {
|
|
2179
|
+
console.log(`▶ Running skill: ${inv.skill.name}${inv.intent ? ` — ${inv.intent}` : ""}`);
|
|
2180
|
+
}
|
|
2181
|
+
const task = buildSkillTask(inv.skill, inv.intent, inv.invokedAs);
|
|
2182
|
+
const { reply, rendered, usage } = await runTurn(task, useOneShotTui, undefined, { userCard: null });
|
|
2183
|
+
if (!rendered) console.log(stripMarkdown(renderMarkdownTables(reply)) + usage);
|
|
2184
|
+
else if (usage) console.log(usage.trim());
|
|
2185
|
+
};
|
|
2178
2186
|
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2187
|
+
// `$a $b … [intent]` — run every resolved skill in order; a lone `$skill` is a chain of 1.
|
|
2188
|
+
const skillChain = parseSkillChain(messageContent, resolvedSkills);
|
|
2189
|
+
if (skillChain && skillChain.invocations.length) {
|
|
2190
|
+
if (skillChain.unresolved.length) {
|
|
2191
|
+
console.log(`(skipping unknown skill${skillChain.unresolved.length > 1 ? "s" : ""}: ${skillChain.unresolved.map(u => `$${u}`).join(", ")})`);
|
|
2192
|
+
}
|
|
2193
|
+
if (skillChain.invocations.length > 1) {
|
|
2194
|
+
console.log(`▶ Chaining ${skillChain.invocations.length} skills: ${skillChain.invocations.map(i => `$${i.skill.name}`).join(" → ")}`);
|
|
2195
|
+
}
|
|
2196
|
+
for (const inv of skillChain.invocations) {
|
|
2197
|
+
await runOneSkillShot(inv);
|
|
2198
|
+
}
|
|
2199
|
+
await maybeDistillOneShot();
|
|
2200
|
+
return;
|
|
2184
2201
|
}
|
|
2185
|
-
|
|
2186
|
-
|
|
2202
|
+
// A `$skill` invocation resolves to a single skill (by name, declared alias, or unique prefix).
|
|
2203
|
+
const skillInvocation = parseSkillInvocation(messageContent, resolvedSkills);
|
|
2204
|
+
if (skillInvocation) {
|
|
2205
|
+
await runOneSkillShot(skillInvocation);
|
|
2206
|
+
await maybeDistillOneShot();
|
|
2207
|
+
return;
|
|
2187
2208
|
}
|
|
2188
|
-
|
|
2189
|
-
await
|
|
2209
|
+
try {
|
|
2210
|
+
const { reply, rendered, usage } = await runTurn(messageContent, shouldUseOneShotTui(flags.noTui));
|
|
2211
|
+
if (!rendered) console.log(stripMarkdown(renderMarkdownTables(reply)) + usage);
|
|
2212
|
+
else if (usage) console.log(usage.trim());
|
|
2213
|
+
} catch (err) {
|
|
2214
|
+
console.log(`! ${friendlyProviderError(err)}`);
|
|
2190
2215
|
}
|
|
2191
2216
|
await maybeDistillOneShot();
|
|
2192
2217
|
return;
|
|
2218
|
+
} finally {
|
|
2219
|
+
await sessionNotifyEndpoint?.stop().catch(() => {});
|
|
2193
2220
|
}
|
|
2194
|
-
// A `$skill` invocation resolves to a single skill (by name, declared alias, or unique prefix).
|
|
2195
|
-
const skillInvocation = parseSkillInvocation(messageContent, resolvedSkills);
|
|
2196
|
-
if (skillInvocation) {
|
|
2197
|
-
await runOneSkillShot(skillInvocation);
|
|
2198
|
-
await maybeDistillOneShot();
|
|
2199
|
-
return;
|
|
2200
|
-
}
|
|
2201
|
-
try {
|
|
2202
|
-
const { reply, rendered, usage } = await runTurn(messageContent, shouldUseOneShotTui(flags.noTui));
|
|
2203
|
-
if (!rendered) console.log(stripMarkdown(renderMarkdownTables(reply)) + usage);
|
|
2204
|
-
else if (usage) console.log(usage.trim());
|
|
2205
|
-
} catch (err) {
|
|
2206
|
-
console.log(`! ${friendlyProviderError(err)}`);
|
|
2207
|
-
}
|
|
2208
|
-
await maybeDistillOneShot();
|
|
2209
|
-
return;
|
|
2210
2221
|
}
|
|
2211
2222
|
|
|
2212
2223
|
// INTERACTIVE mode
|