@elyracode/coding-agent 0.9.6 → 0.9.8

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.8] - 2026-06-10
4
+
5
+ ### Added
6
+ - `/goal <cmd> --budget <usd>`: cost-capped autonomy — the goal loop stops and reports once the spending cap is reached, keeping the work done so far
7
+ - `/replay <model>`: replay the last turn on a new branch with a different model; the original branch is kept for comparison via `/tree`, and the replay cost is reported
8
+ - `/learn [topic]`: extract a reusable skill from the current session — writes a draft `SKILL.md` to the user skills directory for review and `/reload`
9
+
10
+ ### Changed
11
+ - `elyra update` now checks the npm registry first and reports "Elyra is up to date (version)" instead of reinstalling and claiming success when already on the latest version; use `--force` to reinstall anyway
12
+
13
+ ## [0.9.7] - 2026-06-10
14
+
3
15
  ## [0.9.6] - 2026-06-09
4
16
 
5
17
  ## [0.9.5] - 2026-06-07
@@ -0,0 +1,34 @@
1
+ import type { AgentMessage } from "@elyracode/agent-core";
2
+ import { type Api, type Model } from "@elyracode/ai";
3
+ /** Directory where extracted skills are written */
4
+ export declare function getLearnedSkillsDir(): string;
5
+ export interface ExtractedSkill {
6
+ name: string;
7
+ content: string;
8
+ filePath: string;
9
+ }
10
+ export interface ExtractSkillResult {
11
+ skill?: ExtractedSkill;
12
+ skipped: boolean;
13
+ error?: string;
14
+ }
15
+ /** Parse the model output: validate frontmatter and extract the skill name. */
16
+ export declare function parseSkillOutput(output: string): {
17
+ name: string;
18
+ content: string;
19
+ } | undefined;
20
+ /** Find a directory name that doesn't collide with an existing skill. */
21
+ export declare function resolveSkillDir(baseDir: string, name: string): string;
22
+ /**
23
+ * Extract a reusable skill from a session's messages and write it to the
24
+ * user's skills directory. Returns the created skill, or skipped=true when
25
+ * the session contains no teachable procedure.
26
+ */
27
+ export declare function extractSkillFromSession(options: {
28
+ messages: readonly AgentMessage[];
29
+ model: Model<Api>;
30
+ apiKey: string;
31
+ headers?: Record<string, string>;
32
+ nameHint?: string;
33
+ }): Promise<ExtractSkillResult>;
34
+ //# sourceMappingURL=skill-extraction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-extraction.d.ts","sourceRoot":"","sources":["../../src/core/skill-extraction.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,KAAK,GAAG,EAAyC,KAAK,KAAK,EAAE,MAAM,eAAe,CAAC;AAI5F,mDAAmD;AACnD,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAiED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IAClC,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAO9F;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQrE;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE;IACtD,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAClC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA6C9B"}
@@ -0,0 +1,136 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { completeSimple } from "@elyracode/ai";
4
+ import { getAgentDir } from "../config.js";
5
+ /** Directory where extracted skills are written */
6
+ export function getLearnedSkillsDir() {
7
+ return join(getAgentDir(), "skills");
8
+ }
9
+ const SKILL_EXTRACTION_PROMPT = `You are analyzing a coding session to extract ONE reusable skill.
10
+
11
+ A skill is a repeatable procedure the user taught or demonstrated during this session: a workflow, a recipe, a sequence of steps that would be valuable to reuse in future sessions (e.g. "generate a migration for this project", "release flow", "how to add a provider").
12
+
13
+ Given the conversation below, decide whether it contains such a teachable procedure.
14
+
15
+ If it does NOT (the session was exploration, Q&A, or a one-off change), respond with exactly: SKIP
16
+
17
+ If it does, respond with a complete SKILL.md file in this exact format (and nothing else):
18
+
19
+ ---
20
+ name: <kebab-case-name, max 5 words>
21
+ description: <one sentence: what the skill does and when to use it, max 200 chars>
22
+ ---
23
+
24
+ # <Title>
25
+
26
+ <Step-by-step instructions, written for an AI agent to follow.
27
+ Be concrete: exact commands, file paths, and checks from THIS session.
28
+ Include verification steps. Max 40 lines.>
29
+
30
+ Rules:
31
+ - Extract the GENERAL procedure, not session-specific noise
32
+ - Prefer the corrected/final approach if the session contained trial and error
33
+ - Use only facts from the conversation; never invent commands`;
34
+ function extractText(message) {
35
+ return message.content
36
+ .filter((c) => c.type === "text")
37
+ .map((c) => c.text)
38
+ .join("");
39
+ }
40
+ function serializeConversation(messages) {
41
+ const lines = [];
42
+ for (const msg of messages) {
43
+ if (!("role" in msg))
44
+ continue;
45
+ const m = msg;
46
+ if (m.role === "user" && typeof m.content === "string") {
47
+ lines.push(`User: ${m.content}`);
48
+ }
49
+ else if (m.role === "user" && Array.isArray(m.content)) {
50
+ const text = m.content
51
+ .filter((c) => c.type === "text")
52
+ .map((c) => c.text ?? "")
53
+ .join("\n");
54
+ if (text)
55
+ lines.push(`User: ${text}`);
56
+ }
57
+ else if (m.role === "assistant" && Array.isArray(m.content)) {
58
+ const blocks = m.content;
59
+ const text = blocks
60
+ .filter((c) => c.type === "text")
61
+ .map((c) => c.text ?? "")
62
+ .join("\n");
63
+ if (text)
64
+ lines.push(`Assistant: ${text.slice(0, 2000)}`);
65
+ const toolCalls = blocks.filter((c) => c.type === "toolCall");
66
+ for (const t of toolCalls) {
67
+ const args = typeof t.arguments === "object" ? JSON.stringify(t.arguments).slice(0, 300) : "";
68
+ lines.push(` [Tool: ${t.name} ${args}]`);
69
+ }
70
+ }
71
+ }
72
+ return lines.slice(-150).join("\n");
73
+ }
74
+ /** Parse the model output: validate frontmatter and extract the skill name. */
75
+ export function parseSkillOutput(output) {
76
+ const trimmed = output.trim();
77
+ if (!trimmed.startsWith("---"))
78
+ return undefined;
79
+ const nameMatch = trimmed.match(/^name:\s*([a-z0-9][a-z0-9-]*)\s*$/m);
80
+ const descriptionMatch = trimmed.match(/^description:\s*(.+)$/m);
81
+ if (!nameMatch || !descriptionMatch)
82
+ return undefined;
83
+ return { name: nameMatch[1], content: `${trimmed}\n` };
84
+ }
85
+ /** Find a directory name that doesn't collide with an existing skill. */
86
+ export function resolveSkillDir(baseDir, name) {
87
+ let candidate = join(baseDir, name);
88
+ let suffix = 2;
89
+ while (existsSync(candidate)) {
90
+ candidate = join(baseDir, `${name}-${suffix}`);
91
+ suffix++;
92
+ }
93
+ return candidate;
94
+ }
95
+ /**
96
+ * Extract a reusable skill from a session's messages and write it to the
97
+ * user's skills directory. Returns the created skill, or skipped=true when
98
+ * the session contains no teachable procedure.
99
+ */
100
+ export async function extractSkillFromSession(options) {
101
+ const { messages, model, apiKey, headers, nameHint } = options;
102
+ const conversation = serializeConversation(messages);
103
+ if (!conversation.trim()) {
104
+ return { skipped: true };
105
+ }
106
+ const hint = nameHint ? `\n\nThe user wants the skill to be about: ${nameHint}` : "";
107
+ try {
108
+ const result = await completeSimple(model, {
109
+ systemPrompt: SKILL_EXTRACTION_PROMPT,
110
+ messages: [
111
+ {
112
+ role: "user",
113
+ content: `<conversation>\n${conversation}\n</conversation>${hint}`,
114
+ timestamp: Date.now(),
115
+ },
116
+ ],
117
+ }, { apiKey, headers });
118
+ const output = extractText(result).trim();
119
+ if (!output || output === "SKIP") {
120
+ return { skipped: true };
121
+ }
122
+ const parsed = parseSkillOutput(output);
123
+ if (!parsed) {
124
+ return { skipped: false, error: "Model produced an invalid skill format" };
125
+ }
126
+ const skillDir = resolveSkillDir(getLearnedSkillsDir(), parsed.name);
127
+ mkdirSync(skillDir, { recursive: true });
128
+ const filePath = join(skillDir, "SKILL.md");
129
+ writeFileSync(filePath, parsed.content, "utf-8");
130
+ return { skipped: false, skill: { name: parsed.name, content: parsed.content, filePath } };
131
+ }
132
+ catch (error) {
133
+ return { skipped: false, error: error instanceof Error ? error.message : "Skill extraction failed" };
134
+ }
135
+ }
136
+ //# sourceMappingURL=skill-extraction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-extraction.js","sourceRoot":"","sources":["../../src/core/skill-extraction.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport type { AgentMessage } from \"@elyracode/agent-core\";\nimport { type Api, type AssistantMessage, completeSimple, type Model } from \"@elyracode/ai\";\n\nimport { getAgentDir } from \"../config.js\";\n\n/** Directory where extracted skills are written */\nexport function getLearnedSkillsDir(): string {\n\treturn join(getAgentDir(), \"skills\");\n}\n\nconst SKILL_EXTRACTION_PROMPT = `You are analyzing a coding session to extract ONE reusable skill.\n\nA skill is a repeatable procedure the user taught or demonstrated during this session: a workflow, a recipe, a sequence of steps that would be valuable to reuse in future sessions (e.g. \"generate a migration for this project\", \"release flow\", \"how to add a provider\").\n\nGiven the conversation below, decide whether it contains such a teachable procedure.\n\nIf it does NOT (the session was exploration, Q&A, or a one-off change), respond with exactly: SKIP\n\nIf it does, respond with a complete SKILL.md file in this exact format (and nothing else):\n\n---\nname: <kebab-case-name, max 5 words>\ndescription: <one sentence: what the skill does and when to use it, max 200 chars>\n---\n\n# <Title>\n\n<Step-by-step instructions, written for an AI agent to follow.\nBe concrete: exact commands, file paths, and checks from THIS session.\nInclude verification steps. Max 40 lines.>\n\nRules:\n- Extract the GENERAL procedure, not session-specific noise\n- Prefer the corrected/final approach if the session contained trial and error\n- Use only facts from the conversation; never invent commands`;\n\nfunction extractText(message: AssistantMessage): string {\n\treturn message.content\n\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t.map((c) => c.text)\n\t\t.join(\"\");\n}\n\nfunction serializeConversation(messages: readonly AgentMessage[]): string {\n\tconst lines: string[] = [];\n\tfor (const msg of messages) {\n\t\tif (!(\"role\" in msg)) continue;\n\t\tconst m = msg as { role: string; content: unknown };\n\t\tif (m.role === \"user\" && typeof m.content === \"string\") {\n\t\t\tlines.push(`User: ${m.content}`);\n\t\t} else if (m.role === \"user\" && Array.isArray(m.content)) {\n\t\t\tconst text = (m.content as { type: string; text?: string }[])\n\t\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t\t.map((c) => c.text ?? \"\")\n\t\t\t\t.join(\"\\n\");\n\t\t\tif (text) lines.push(`User: ${text}`);\n\t\t} else if (m.role === \"assistant\" && Array.isArray(m.content)) {\n\t\t\tconst blocks = m.content as { type: string; text?: string; name?: string; arguments?: unknown }[];\n\t\t\tconst text = blocks\n\t\t\t\t.filter((c) => c.type === \"text\")\n\t\t\t\t.map((c) => c.text ?? \"\")\n\t\t\t\t.join(\"\\n\");\n\t\t\tif (text) lines.push(`Assistant: ${text.slice(0, 2000)}`);\n\t\t\tconst toolCalls = blocks.filter((c) => c.type === \"toolCall\");\n\t\t\tfor (const t of toolCalls) {\n\t\t\t\tconst args = typeof t.arguments === \"object\" ? JSON.stringify(t.arguments).slice(0, 300) : \"\";\n\t\t\t\tlines.push(` [Tool: ${t.name} ${args}]`);\n\t\t\t}\n\t\t}\n\t}\n\treturn lines.slice(-150).join(\"\\n\");\n}\n\nexport interface ExtractedSkill {\n\tname: string;\n\tcontent: string;\n\tfilePath: string;\n}\n\nexport interface ExtractSkillResult {\n\tskill?: ExtractedSkill;\n\tskipped: boolean;\n\terror?: string;\n}\n\n/** Parse the model output: validate frontmatter and extract the skill name. */\nexport function parseSkillOutput(output: string): { name: string; content: string } | undefined {\n\tconst trimmed = output.trim();\n\tif (!trimmed.startsWith(\"---\")) return undefined;\n\tconst nameMatch = trimmed.match(/^name:\\s*([a-z0-9][a-z0-9-]*)\\s*$/m);\n\tconst descriptionMatch = trimmed.match(/^description:\\s*(.+)$/m);\n\tif (!nameMatch || !descriptionMatch) return undefined;\n\treturn { name: nameMatch[1], content: `${trimmed}\\n` };\n}\n\n/** Find a directory name that doesn't collide with an existing skill. */\nexport function resolveSkillDir(baseDir: string, name: string): string {\n\tlet candidate = join(baseDir, name);\n\tlet suffix = 2;\n\twhile (existsSync(candidate)) {\n\t\tcandidate = join(baseDir, `${name}-${suffix}`);\n\t\tsuffix++;\n\t}\n\treturn candidate;\n}\n\n/**\n * Extract a reusable skill from a session's messages and write it to the\n * user's skills directory. Returns the created skill, or skipped=true when\n * the session contains no teachable procedure.\n */\nexport async function extractSkillFromSession(options: {\n\tmessages: readonly AgentMessage[];\n\tmodel: Model<Api>;\n\tapiKey: string;\n\theaders?: Record<string, string>;\n\tnameHint?: string;\n}): Promise<ExtractSkillResult> {\n\tconst { messages, model, apiKey, headers, nameHint } = options;\n\n\tconst conversation = serializeConversation(messages);\n\tif (!conversation.trim()) {\n\t\treturn { skipped: true };\n\t}\n\n\tconst hint = nameHint ? `\\n\\nThe user wants the skill to be about: ${nameHint}` : \"\";\n\n\ttry {\n\t\tconst result = await completeSimple(\n\t\t\tmodel,\n\t\t\t{\n\t\t\t\tsystemPrompt: SKILL_EXTRACTION_PROMPT,\n\t\t\t\tmessages: [\n\t\t\t\t\t{\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: `<conversation>\\n${conversation}\\n</conversation>${hint}`,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{ apiKey, headers },\n\t\t);\n\n\t\tconst output = extractText(result).trim();\n\t\tif (!output || output === \"SKIP\") {\n\t\t\treturn { skipped: true };\n\t\t}\n\n\t\tconst parsed = parseSkillOutput(output);\n\t\tif (!parsed) {\n\t\t\treturn { skipped: false, error: \"Model produced an invalid skill format\" };\n\t\t}\n\n\t\tconst skillDir = resolveSkillDir(getLearnedSkillsDir(), parsed.name);\n\t\tmkdirSync(skillDir, { recursive: true });\n\t\tconst filePath = join(skillDir, \"SKILL.md\");\n\t\twriteFileSync(filePath, parsed.content, \"utf-8\");\n\n\t\treturn { skipped: false, skill: { name: parsed.name, content: parsed.content, filePath } };\n\t} catch (error) {\n\t\treturn { skipped: false, error: error instanceof Error ? error.message : \"Skill extraction failed\" };\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"slash-commands.d.ts","sourceRoot":"","sources":["../../src/core/slash-commands.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAElE,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,sBAAsB,EAAE,aAAa,CAAC,mBAAmB,CAoDrE,CAAC"}
1
+ {"version":3,"file":"slash-commands.d.ts","sourceRoot":"","sources":["../../src/core/slash-commands.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAElE,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,sBAAsB,EAAE,aAAa,CAAC,mBAAmB,CAyDrE,CAAC"}
@@ -47,7 +47,12 @@ export const BUILTIN_SLASH_COMMANDS = [
47
47
  { name: "reload", description: "Reload keybindings, extensions, skills, prompts, and themes" },
48
48
  { name: "packages", description: "Browse and install available extensions" },
49
49
  { name: "ext", description: "Browse and install available extensions (alias for /packages)" },
50
- { name: "goal", description: "Set a goal: agent keeps working until the command exits with code 0" },
50
+ {
51
+ name: "goal",
52
+ description: "Set a goal: agent keeps working until the command exits with code 0 (--budget <usd> caps spend)",
53
+ },
54
+ { name: "learn", description: "Extract a reusable skill from the current session" },
55
+ { name: "replay", description: "Replay the last turn on a new branch with a different model" },
51
56
  { name: "update", description: "Update Elyra and installed extensions to the latest version and restart" },
52
57
  { name: "quit", description: `Quit ${APP_NAME}` },
53
58
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"slash-commands.js","sourceRoot":"","sources":["../../src/core/slash-commands.ts"],"names":[],"mappings":"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","sourcesContent":["import { APP_NAME } from \"../config.js\";\nimport type { SourceInfo } from \"./source-info.js\";\n\nexport type SlashCommandSource = \"extension\" | \"prompt\" | \"skill\";\n\nexport interface SlashCommandInfo {\n\tname: string;\n\tdescription?: string;\n\tsource: SlashCommandSource;\n\tsourceInfo: SourceInfo;\n}\n\nexport interface BuiltinSlashCommand {\n\tname: string;\n\tdescription: string;\n}\n\nexport const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [\n\t{ name: \"help\", description: \"Show help with example prompts and useful commands\" },\n\t{ name: \"skills\", description: \"List loaded skills, or install from registry: /skills install <name>\" },\n\t{ name: \"init\", description: \"Set up Elyra for this project (.elyra/ directory, extensions, blueprints)\" },\n\t{ name: \"settings\", description: \"Open settings menu\" },\n\t{ name: \"model\", description: \"Select model (opens selector UI)\" },\n\t{ name: \"scoped-models\", description: \"Enable/disable models for Ctrl+P cycling\" },\n\t{ name: \"export\", description: \"Export session (HTML default, or specify path: .html/.jsonl)\" },\n\t{ name: \"import\", description: \"Import and resume a session from a JSONL file\" },\n\t{ name: \"share\", description: \"Share session as a secret GitHub gist\" },\n\t{ name: \"copy\", description: \"Copy last agent message to clipboard\" },\n\t{ name: \"name\", description: \"Set session display name\" },\n\t{ name: \"session\", description: \"Show session info and stats\" },\n\t{ name: \"changelog\", description: \"Show changelog entries\" },\n\t{ name: \"hotkeys\", description: \"Show all keyboard shortcuts\" },\n\t{ name: \"fork\", description: \"Create a new fork from a previous user message\" },\n\t{ name: \"clone\", description: \"Duplicate the current session at the current position\" },\n\t{ name: \"tree\", description: \"Navigate session tree (switch branches)\" },\n\t{ name: \"rewind\", description: \"Rewind session and file state to a previous point\" },\n\t{ name: \"login\", description: \"Configure provider authentication\" },\n\t{ name: \"logout\", description: \"Remove provider authentication\" },\n\t{ name: \"new\", description: \"Start a new session\" },\n\t{ name: \"compact\", description: \"Manually compact the session context\" },\n\t{ name: \"resume\", description: \"Resume a different session\" },\n\t{ name: \"cost\", description: \"Show session cost and token usage\" },\n\t{\n\t\tname: \"diff\",\n\t\tdescription: \"Show changes (git diff). Flags: --session, --cached, --editor (open in $EDITOR), or a path\",\n\t},\n\t{\n\t\tname: \"review\",\n\t\tdescription: \"Get an independent code review of your changes from the model. Flags: --session, or a path\",\n\t},\n\t{\n\t\tname: \"bisect\",\n\t\tdescription:\n\t\t\t\"Find which agent turn introduced a problem. /bisect <command> runs it automatically (like git bisect run)\",\n\t},\n\t{ name: \"pin\", description: \"Pin a file to context (survives compaction): /pin <path>\" },\n\t{ name: \"memory\", description: \"View or manage project memory\" },\n\t{ name: \"unpin\", description: \"Unpin a file from context: /unpin <path>\" },\n\t{ name: \"pins\", description: \"List pinned context files\" },\n\t{ name: \"blueprint\", description: \"Apply a session blueprint: /blueprint [name]\" },\n\t{ name: \"snippet\", description: \"Send a saved instruction snippet: /snippet [name]\" },\n\t{ name: \"theme\", description: \"Switch color theme (opens selector, or /theme <name>)\" },\n\t{ name: \"footer\", description: \"Toggle minimal footer (hide token stats and context usage)\" },\n\t{ name: \"reload\", description: \"Reload keybindings, extensions, skills, prompts, and themes\" },\n\t{ name: \"packages\", description: \"Browse and install available extensions\" },\n\t{ name: \"ext\", description: \"Browse and install available extensions (alias for /packages)\" },\n\t{ name: \"goal\", description: \"Set a goal: agent keeps working until the command exits with code 0\" },\n\t{ name: \"update\", description: \"Update Elyra and installed extensions to the latest version and restart\" },\n\t{ name: \"quit\", description: `Quit ${APP_NAME}` },\n];\n"]}
1
+ {"version":3,"file":"slash-commands.js","sourceRoot":"","sources":["../../src/core/slash-commands.ts"],"names":[],"mappings":"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","sourcesContent":["import { APP_NAME } from \"../config.js\";\nimport type { SourceInfo } from \"./source-info.js\";\n\nexport type SlashCommandSource = \"extension\" | \"prompt\" | \"skill\";\n\nexport interface SlashCommandInfo {\n\tname: string;\n\tdescription?: string;\n\tsource: SlashCommandSource;\n\tsourceInfo: SourceInfo;\n}\n\nexport interface BuiltinSlashCommand {\n\tname: string;\n\tdescription: string;\n}\n\nexport const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [\n\t{ name: \"help\", description: \"Show help with example prompts and useful commands\" },\n\t{ name: \"skills\", description: \"List loaded skills, or install from registry: /skills install <name>\" },\n\t{ name: \"init\", description: \"Set up Elyra for this project (.elyra/ directory, extensions, blueprints)\" },\n\t{ name: \"settings\", description: \"Open settings menu\" },\n\t{ name: \"model\", description: \"Select model (opens selector UI)\" },\n\t{ name: \"scoped-models\", description: \"Enable/disable models for Ctrl+P cycling\" },\n\t{ name: \"export\", description: \"Export session (HTML default, or specify path: .html/.jsonl)\" },\n\t{ name: \"import\", description: \"Import and resume a session from a JSONL file\" },\n\t{ name: \"share\", description: \"Share session as a secret GitHub gist\" },\n\t{ name: \"copy\", description: \"Copy last agent message to clipboard\" },\n\t{ name: \"name\", description: \"Set session display name\" },\n\t{ name: \"session\", description: \"Show session info and stats\" },\n\t{ name: \"changelog\", description: \"Show changelog entries\" },\n\t{ name: \"hotkeys\", description: \"Show all keyboard shortcuts\" },\n\t{ name: \"fork\", description: \"Create a new fork from a previous user message\" },\n\t{ name: \"clone\", description: \"Duplicate the current session at the current position\" },\n\t{ name: \"tree\", description: \"Navigate session tree (switch branches)\" },\n\t{ name: \"rewind\", description: \"Rewind session and file state to a previous point\" },\n\t{ name: \"login\", description: \"Configure provider authentication\" },\n\t{ name: \"logout\", description: \"Remove provider authentication\" },\n\t{ name: \"new\", description: \"Start a new session\" },\n\t{ name: \"compact\", description: \"Manually compact the session context\" },\n\t{ name: \"resume\", description: \"Resume a different session\" },\n\t{ name: \"cost\", description: \"Show session cost and token usage\" },\n\t{\n\t\tname: \"diff\",\n\t\tdescription: \"Show changes (git diff). Flags: --session, --cached, --editor (open in $EDITOR), or a path\",\n\t},\n\t{\n\t\tname: \"review\",\n\t\tdescription: \"Get an independent code review of your changes from the model. Flags: --session, or a path\",\n\t},\n\t{\n\t\tname: \"bisect\",\n\t\tdescription:\n\t\t\t\"Find which agent turn introduced a problem. /bisect <command> runs it automatically (like git bisect run)\",\n\t},\n\t{ name: \"pin\", description: \"Pin a file to context (survives compaction): /pin <path>\" },\n\t{ name: \"memory\", description: \"View or manage project memory\" },\n\t{ name: \"unpin\", description: \"Unpin a file from context: /unpin <path>\" },\n\t{ name: \"pins\", description: \"List pinned context files\" },\n\t{ name: \"blueprint\", description: \"Apply a session blueprint: /blueprint [name]\" },\n\t{ name: \"snippet\", description: \"Send a saved instruction snippet: /snippet [name]\" },\n\t{ name: \"theme\", description: \"Switch color theme (opens selector, or /theme <name>)\" },\n\t{ name: \"footer\", description: \"Toggle minimal footer (hide token stats and context usage)\" },\n\t{ name: \"reload\", description: \"Reload keybindings, extensions, skills, prompts, and themes\" },\n\t{ name: \"packages\", description: \"Browse and install available extensions\" },\n\t{ name: \"ext\", description: \"Browse and install available extensions (alias for /packages)\" },\n\t{\n\t\tname: \"goal\",\n\t\tdescription: \"Set a goal: agent keeps working until the command exits with code 0 (--budget <usd> caps spend)\",\n\t},\n\t{ name: \"learn\", description: \"Extract a reusable skill from the current session\" },\n\t{ name: \"replay\", description: \"Replay the last turn on a new branch with a different model\" },\n\t{ name: \"update\", description: \"Update Elyra and installed extensions to the latest version and restart\" },\n\t{ name: \"quit\", description: `Quit ${APP_NAME}` },\n];\n"]}
@@ -320,6 +320,9 @@ export declare class InteractiveMode {
320
320
  private checkForVersionUpdate;
321
321
  private handleInAppUpdate;
322
322
  private handleGoalCommand;
323
+ private getLastUserMessageText;
324
+ private handleReplayCommand;
325
+ private handleLearnCommand;
323
326
  private checkGoalCondition;
324
327
  private handleSkillsInstallCommand;
325
328
  private handleHelpCommand;
@@ -1 +1 @@
1
- {"version":3,"file":"interactive-mode.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/interactive-mode.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"interactive-mode.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/interactive-mode.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,7 @@ import { DefaultPackageManager } from "../../core/package-manager.js";
21
21
  import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
22
22
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
23
23
  import { SessionManager } from "../../core/session-manager.js";
24
+ import { extractSkillFromSession } from "../../core/skill-extraction.js";
24
25
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
25
26
  import { detectStack, getSuggestedPackage } from "../../core/stack-detector.js";
26
27
  import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
@@ -2235,6 +2236,16 @@ export class InteractiveMode {
2235
2236
  this.handleGoalCommand(text.slice(5).trim());
2236
2237
  return;
2237
2238
  }
2239
+ if (text.startsWith("/learn")) {
2240
+ this.editor.setText("");
2241
+ await this.handleLearnCommand(text.slice(6).trim());
2242
+ return;
2243
+ }
2244
+ if (text.startsWith("/replay")) {
2245
+ this.editor.setText("");
2246
+ await this.handleReplayCommand(text.slice(7).trim());
2247
+ return;
2248
+ }
2238
2249
  // Handle bash command (! for normal, !! for excluded from context)
2239
2250
  if (text.startsWith("!")) {
2240
2251
  const isExcluded = text.startsWith("!!");
@@ -3673,14 +3684,156 @@ export class InteractiveMode {
3673
3684
  this.showStatus("Goal cleared");
3674
3685
  }
3675
3686
  else {
3676
- this.showStatus("No active goal. Set one with: /goal <command>\n\nExample: /goal npm run check");
3687
+ this.showStatus("No active goal. Set one with: /goal <command>\n\nExamples:\n /goal npm run check\n /goal npm test --budget 2.50 (stop and ask once $2.50 has been spent)");
3677
3688
  }
3678
3689
  return;
3679
3690
  }
3680
- this._activeGoal = { condition: args, checkCommand: args };
3681
- const goalDisplay = theme.fg("accent", args);
3691
+ // Extract an optional --budget <usd> flag (anywhere in the args).
3692
+ let budgetUsd;
3693
+ let command = args;
3694
+ const budgetMatch = command.match(/\s*--budget[= ]\$?(\d+(?:\.\d+)?)\s*/);
3695
+ if (budgetMatch) {
3696
+ budgetUsd = Number.parseFloat(budgetMatch[1]);
3697
+ command = command.replace(budgetMatch[0], " ").trim();
3698
+ if (!command) {
3699
+ this.showError("Usage: /goal <command> [--budget <usd>]");
3700
+ return;
3701
+ }
3702
+ if (!Number.isFinite(budgetUsd) || budgetUsd <= 0) {
3703
+ this.showError("Invalid --budget value. Use a positive dollar amount, e.g. --budget 2.50");
3704
+ return;
3705
+ }
3706
+ }
3707
+ this._activeGoal = {
3708
+ condition: command,
3709
+ checkCommand: command,
3710
+ budgetUsd,
3711
+ startCostUsd: budgetUsd !== undefined ? this.session.getSessionStats().cost : undefined,
3712
+ };
3713
+ const goalDisplay = theme.fg("accent", command);
3714
+ const budgetNote = budgetUsd !== undefined ? ` Spending cap: ${theme.fg("accent", `$${budgetUsd.toFixed(2)}`)}.` : "";
3682
3715
  this.chatContainer.addChild(new Spacer(1));
3683
- this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("accent", "Goal set:"))} ${goalDisplay}\n${theme.fg("muted", "The agent will keep working until this command exits with code 0. Type /goal to clear.")}`, 1, 0));
3716
+ this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("accent", "Goal set:"))} ${goalDisplay}\n${theme.fg("muted", `The agent will keep working until this command exits with code 0.${budgetNote} Type /goal to clear.`)}`, 1, 0));
3717
+ this.ui.requestRender();
3718
+ }
3719
+ getLastUserMessageText() {
3720
+ const messages = this.session.state.messages;
3721
+ for (let i = messages.length - 1; i >= 0; i--) {
3722
+ const msg = messages[i];
3723
+ if (msg.role !== "user")
3724
+ continue;
3725
+ if (typeof msg.content === "string")
3726
+ return msg.content;
3727
+ if (Array.isArray(msg.content)) {
3728
+ const text = msg.content
3729
+ .filter((c) => c.type === "text")
3730
+ .map((c) => c.text ?? "")
3731
+ .join("\n")
3732
+ .trim();
3733
+ if (text)
3734
+ return text;
3735
+ }
3736
+ }
3737
+ return undefined;
3738
+ }
3739
+ async handleReplayCommand(args) {
3740
+ if (!args) {
3741
+ this.showError("Usage: /replay <model> \u2014 replay the last turn on a new branch with a different model.\n\nExample: /replay haiku\nThe original branch is kept; compare results with /tree.");
3742
+ return;
3743
+ }
3744
+ if (this.session.isStreaming) {
3745
+ this.showWarning("Wait for the current response to finish before replaying.");
3746
+ return;
3747
+ }
3748
+ const current = this.session.model;
3749
+ const pattern = args.toLowerCase();
3750
+ const available = this.session.modelRegistry.getAvailable();
3751
+ const candidates = available.filter((m) => m.id.toLowerCase().includes(pattern) ||
3752
+ m.name.toLowerCase().includes(pattern) ||
3753
+ `${m.provider}/${m.id}`.toLowerCase().includes(pattern));
3754
+ if (candidates.length === 0) {
3755
+ this.showError(`No available model matches "${args}". Browse models with /model.`);
3756
+ return;
3757
+ }
3758
+ const exact = candidates.find((m) => m.id.toLowerCase() === pattern);
3759
+ const target = exact ?? candidates[0];
3760
+ if (current && target.id === current.id && target.provider === current.provider) {
3761
+ this.showWarning(`Already on ${target.name || target.id}. Pick a different model to replay against.`);
3762
+ return;
3763
+ }
3764
+ // Capture the prompt before rewinding, as a fallback source.
3765
+ const lastUserText = this.getLastUserMessageText();
3766
+ const points = this.session.getRewindPoints();
3767
+ if (points.length < 2) {
3768
+ this.showStatus("Nothing to replay yet \u2014 run at least one prompt first.");
3769
+ return;
3770
+ }
3771
+ const targetPoint = points[points.length - 2];
3772
+ const result = await this.session.rewind(targetPoint.entryId);
3773
+ if (result.cancelled) {
3774
+ this.ui.requestRender();
3775
+ return;
3776
+ }
3777
+ if (result.error) {
3778
+ this.showError(`Replay rewind failed: ${result.error}`);
3779
+ return;
3780
+ }
3781
+ // Rebuild the chat UI on the new branch point.
3782
+ this.chatContainer.clear();
3783
+ this.renderInitialMessages();
3784
+ const promptText = result.editorText?.trim() || lastUserText;
3785
+ if (!promptText) {
3786
+ this.showError("Could not find the last user message to replay.");
3787
+ return;
3788
+ }
3789
+ await this.session.setModel(target);
3790
+ const originalLabel = current ? current.name || current.id : "previous model";
3791
+ this.chatContainer.addChild(new Spacer(1));
3792
+ this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("accent", "Replaying last turn"))} ${theme.fg("muted", "with")} ${theme.fg("accent", target.name || target.id)} ${theme.fg("muted", `(was ${originalLabel}). The original branch is kept \u2014 compare with /tree.`)}`, 1, 0));
3793
+ this.ui.requestRender();
3794
+ const costBefore = this.session.getSessionStats().cost;
3795
+ await this.session.prompt(promptText);
3796
+ const costDelta = this.session.getSessionStats().cost - costBefore;
3797
+ const costNote = costDelta > 0 ? ` Replay cost: $${costDelta.toFixed(4)}.` : "";
3798
+ this.showStatus(`Replay with ${target.name || target.id} complete.${costNote} Use /tree to compare branches.`);
3799
+ }
3800
+ async handleLearnCommand(nameHint) {
3801
+ const model = this.session.model;
3802
+ if (!model) {
3803
+ this.showError("No model selected. Pick one with /model first.");
3804
+ return;
3805
+ }
3806
+ const messages = this.session.state.messages;
3807
+ if (messages.length < 4) {
3808
+ this.showStatus("Not enough conversation yet to extract a skill from.");
3809
+ return;
3810
+ }
3811
+ this.chatContainer.addChild(new Spacer(1));
3812
+ const statusText = new Text(theme.fg("dim", "Analyzing session for a reusable skill..."), 1, 0);
3813
+ this.chatContainer.addChild(statusText);
3814
+ this.ui.requestRender();
3815
+ const auth = await this.session.modelRegistry.getApiKeyAndHeaders(model);
3816
+ if (!auth.ok || !auth.apiKey) {
3817
+ statusText.setText(theme.fg("error", "No API key available for the current model."));
3818
+ this.ui.requestRender();
3819
+ return;
3820
+ }
3821
+ const result = await extractSkillFromSession({
3822
+ messages,
3823
+ model,
3824
+ apiKey: auth.apiKey,
3825
+ headers: auth.headers,
3826
+ nameHint: nameHint || undefined,
3827
+ });
3828
+ if (result.error) {
3829
+ statusText.setText(theme.fg("error", `Skill extraction failed: ${result.error}`));
3830
+ }
3831
+ else if (result.skipped || !result.skill) {
3832
+ statusText.setText(theme.fg("dim", "No reusable procedure found in this session. Teach the agent a workflow first, then /learn."));
3833
+ }
3834
+ else {
3835
+ statusText.setText(`${theme.bold(theme.fg("accent", `Skill learned: ${result.skill.name}`))}\n${theme.fg("muted", `Saved to ${result.skill.filePath}`)}\n${theme.fg("dim", "Review/edit the file, then /reload to activate it. It will load in all future sessions.")}`);
3836
+ }
3684
3837
  this.ui.requestRender();
3685
3838
  }
3686
3839
  async checkGoalCondition() {
@@ -3705,6 +3858,19 @@ export class InteractiveMode {
3705
3858
  this.ui.requestRender();
3706
3859
  }
3707
3860
  else {
3861
+ // Budget guard: stop the loop (but keep the work) once the cap is hit.
3862
+ if (this._activeGoal.budgetUsd !== undefined) {
3863
+ const spent = this.session.getSessionStats().cost - (this._activeGoal.startCostUsd ?? 0);
3864
+ if (spent >= this._activeGoal.budgetUsd) {
3865
+ const goalText = this._activeGoal.condition;
3866
+ const budget = this._activeGoal.budgetUsd;
3867
+ this._activeGoal = undefined;
3868
+ this.chatContainer.addChild(new Spacer(1));
3869
+ this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Goal budget reached:"))} spent $${spent.toFixed(2)} of $${budget.toFixed(2)} without meeting the goal.\n${theme.fg("muted", `Goal was: ${goalText}. The work so far is kept. Re-run /goal to continue with a fresh budget.`)}`, 1, 0));
3870
+ this.ui.requestRender();
3871
+ return;
3872
+ }
3873
+ }
3708
3874
  // Goal not met — send the output back to the agent to continue
3709
3875
  const output = (result.stdout || "").trim();
3710
3876
  const stderr = (result.stderr || "").trim();