indusagi-coding-agent 0.2.5 → 0.2.9
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 +9 -0
- package/README.md +9 -5
- package/dist/entry.js +14267 -24282
- package/dist/guardrails.js +924 -7867
- package/dist/index.js +13706 -23775
- package/package.json +3 -2
- package/src/_decls/entry.ts +18 -0
- package/src/_decls/guardrails.ts +35 -0
- package/src/_decls/index.ts +26 -0
- package/src/addons/contract.ts +236 -0
- package/src/addons/dispatch/event-dispatcher.ts +164 -0
- package/src/addons/dispatch/index.ts +25 -0
- package/src/addons/dispatch/tool-interceptor.ts +208 -0
- package/src/addons/host.ts +225 -0
- package/src/addons/index.ts +112 -0
- package/src/addons/manifest.ts +158 -0
- package/src/addons/sandbox.ts +170 -0
- package/src/addons/surface.ts +78 -0
- package/src/boot/auth-vault.ts +195 -0
- package/src/boot/boot.ts +138 -0
- package/src/boot/contract.ts +238 -0
- package/src/boot/heap.ts +59 -0
- package/src/boot/index.ts +28 -0
- package/src/boot/invocation.ts +93 -0
- package/src/boot/runners/addon-wiring.ts +153 -0
- package/src/boot/runners/checkpoint.ts +169 -0
- package/src/boot/runners/delegate-runner.ts +294 -0
- package/src/boot/runners/index.ts +13 -0
- package/src/boot/runners/link-runner.ts +45 -0
- package/src/boot/runners/memdir.ts +168 -0
- package/src/boot/runners/oneshot-runner.ts +58 -0
- package/src/boot/runners/read-state.ts +90 -0
- package/src/boot/runners/registry.ts +42 -0
- package/src/boot/runners/repl-runner.ts +143 -0
- package/src/boot/runners/server-mode.ts +121 -0
- package/src/boot/runners/session.ts +641 -0
- package/src/boot/server-token.ts +148 -0
- package/src/boot/stages.ts +167 -0
- package/src/boot/upgrade/apply.ts +94 -0
- package/src/boot/upgrade/index.ts +13 -0
- package/src/boot/upgrade/upgrades.ts +289 -0
- package/src/briefing/compose.ts +150 -0
- package/src/briefing/context-docs.ts +19 -0
- package/src/briefing/contract.ts +717 -0
- package/src/briefing/index.ts +31 -0
- package/src/briefing/macros.ts +97 -0
- package/src/briefing/skills.ts +47 -0
- package/src/capability-deck/bridge-ledger/index.ts +27 -0
- package/src/capability-deck/bridge-ledger/key.ts +67 -0
- package/src/capability-deck/bridge-ledger/ledger.ts +131 -0
- package/src/capability-deck/bridge-ledger/network.ts +117 -0
- package/src/capability-deck/builtin-bridge.ts +312 -0
- package/src/capability-deck/cards/bg-process-card.ts +335 -0
- package/src/capability-deck/cards/index.ts +115 -0
- package/src/capability-deck/cards/memory-card.ts +146 -0
- package/src/capability-deck/cards/plan-file.ts +97 -0
- package/src/capability-deck/cards/plan-tools.ts +185 -0
- package/src/capability-deck/cards/saas-card.ts +183 -0
- package/src/capability-deck/cards/task-card.ts +207 -0
- package/src/capability-deck/cards/todo-card.ts +168 -0
- package/src/capability-deck/cards/workflow-card.ts +247 -0
- package/src/capability-deck/contract.ts +388 -0
- package/src/capability-deck/index.ts +48 -0
- package/src/capability-deck/manifest.ts +109 -0
- package/src/capability-deck/provision.ts +169 -0
- package/src/channels/contract.ts +191 -0
- package/src/channels/framer.ts +50 -0
- package/src/channels/index.ts +101 -0
- package/src/channels/link/dialog.ts +129 -0
- package/src/channels/link/driver.ts +190 -0
- package/src/channels/link/index.ts +34 -0
- package/src/channels/link/server.ts +134 -0
- package/src/channels/oneshot.ts +90 -0
- package/src/channels/ops.ts +65 -0
- package/src/channels/session-ops.ts +81 -0
- package/src/conductor/bash-guard.ts +599 -0
- package/src/conductor/catalog/catalog.ts +116 -0
- package/src/conductor/catalog/index.ts +8 -0
- package/src/conductor/catalog/matcher.ts +134 -0
- package/src/conductor/conductor.ts +234 -0
- package/src/conductor/contract.ts +842 -0
- package/src/conductor/diagnostics.ts +227 -0
- package/src/conductor/index.ts +33 -0
- package/src/conductor/permissions.ts +588 -0
- package/src/conductor/quota-error.ts +49 -0
- package/src/conductor/signal-hub/hub.ts +46 -0
- package/src/conductor/signal-hub/index.ts +2 -0
- package/src/conductor/signal-hub/translate.test.ts +81 -0
- package/src/conductor/signal-hub/translate.ts +74 -0
- package/src/conductor/skill-parse/index.ts +2 -0
- package/src/conductor/skill-parse/parse.ts +108 -0
- package/src/conductor/transcript-store/index.ts +22 -0
- package/src/conductor/transcript-store/serialize.ts +116 -0
- package/src/conductor/transcript-store/store.ts +205 -0
- package/src/console/auth-status.ts +56 -0
- package/src/console/components/AgentsView.ts +165 -0
- package/src/console/components/BackgroundAgents.ts +155 -0
- package/src/console/components/Banner.ts +334 -0
- package/src/console/components/Composer.ts +94 -0
- package/src/console/components/StatusBar.ts +49 -0
- package/src/console/components/TerminalConsole.ts +1090 -0
- package/src/console/components/WorkingIndicator.ts +98 -0
- package/src/console/components/banner-sweep.ts +24 -0
- package/src/console/components/welcome.ts +74 -0
- package/src/console/contract.ts +630 -0
- package/src/console/index.ts +34 -0
- package/src/console/input/complete.ts +127 -0
- package/src/console/input/dir-reader.ts +34 -0
- package/src/console/input/index.ts +23 -0
- package/src/console/input/keymap.ts +159 -0
- package/src/console/input/paste.ts +104 -0
- package/src/console/mount.ts +56 -0
- package/src/console/overlays/approval-queue.ts +57 -0
- package/src/console/overlays/approval.ts +130 -0
- package/src/console/overlays/auth.ts +342 -0
- package/src/console/overlays/boards.ts +308 -0
- package/src/console/overlays/host.ts +36 -0
- package/src/console/overlays/index.ts +26 -0
- package/src/console/overlays/pickers.ts +258 -0
- package/src/console/overlays/sessions.ts +190 -0
- package/src/console/reducer.ts +182 -0
- package/src/console/slash/builtins.ts +81 -0
- package/src/console/slash/commands/dynamic.ts +83 -0
- package/src/console/slash/commands/integrations.ts +695 -0
- package/src/console/slash/commands/shared.ts +75 -0
- package/src/console/slash/commands/transcript.ts +263 -0
- package/src/console/slash/commands/workbench.ts +246 -0
- package/src/console/slash/index.ts +15 -0
- package/src/console/slash/registry.ts +70 -0
- package/src/console/slash/resolve.ts +63 -0
- package/src/console/startup.ts +209 -0
- package/src/console/theme/adapter.ts +45 -0
- package/src/console/theme/index.ts +7 -0
- package/src/console/theme/palette.ts +68 -0
- package/src/console/theme/resolve.ts +39 -0
- package/src/console/theme/tokens.ts +71 -0
- package/src/entry.ts +55 -0
- package/src/guardrails.ts +37 -0
- package/src/index.ts +18 -0
- package/src/insight/channel.ts +88 -0
- package/src/insight/contract.ts +185 -0
- package/src/insight/index.ts +110 -0
- package/src/insight/recorder.ts +213 -0
- package/src/insight/redaction.ts +157 -0
- package/src/insight/replay.ts +158 -0
- package/src/insight/sampling.ts +70 -0
- package/src/insight/serialize.ts +50 -0
- package/src/insight/sinks/console.ts +64 -0
- package/src/insight/sinks/file.ts +40 -0
- package/src/insight/sinks/index.ts +24 -0
- package/src/insight/sinks/stream.ts +54 -0
- package/src/integrations/sarvam/attach.ts +239 -0
- package/src/integrations/sarvam/config.ts +156 -0
- package/src/integrations/sarvam/index.ts +25 -0
- package/src/integrations/sarvam/sarvam.test.ts +60 -0
- package/src/integrations/sarvam/types.ts +27 -0
- package/src/integrations/zoho/attach.ts +342 -0
- package/src/integrations/zoho/config.ts +125 -0
- package/src/integrations/zoho/index.ts +27 -0
- package/src/integrations/zoho/types.ts +21 -0
- package/src/integrations/zoho/zoho.test.ts +50 -0
- package/src/kit/clipboard-image.ts +107 -0
- package/src/kit/external-editor.ts +48 -0
- package/src/kit/image.ts +59 -0
- package/src/kit/index.ts +51 -0
- package/src/kit/shell.ts +19 -0
- package/src/kit/tool-fetch.ts +85 -0
- package/src/launch/catalog.ts +148 -0
- package/src/launch/contract.ts +187 -0
- package/src/launch/credentials.ts +625 -0
- package/src/launch/index.ts +98 -0
- package/src/launch/invocation/attachments.ts +179 -0
- package/src/launch/invocation/flags.ts +196 -0
- package/src/launch/invocation/index.ts +25 -0
- package/src/launch/invocation/read.ts +260 -0
- package/src/launch/invocation/usage.ts +67 -0
- package/src/launch/login.ts +324 -0
- package/src/launch/oauth.test.ts +18 -0
- package/src/launch/oauth.ts +203 -0
- package/src/launch/packages.ts +194 -0
- package/src/launch/pickers.ts +189 -0
- package/src/runtime-bridge/bridges/_drive.ts +96 -0
- package/src/runtime-bridge/bridges/builtins.ts +68 -0
- package/src/runtime-bridge/bridges/claude-cli.ts +123 -0
- package/src/runtime-bridge/bridges/codex-cli.ts +142 -0
- package/src/runtime-bridge/bridges/index.ts +33 -0
- package/src/runtime-bridge/bridges/indusagi-cli.ts +155 -0
- package/src/runtime-bridge/broker.ts +227 -0
- package/src/runtime-bridge/contract.ts +122 -0
- package/src/runtime-bridge/index.ts +79 -0
- package/src/runtime-bridge/sink.ts +180 -0
- package/src/sessions/contract.ts +81 -0
- package/src/sessions/index.ts +13 -0
- package/src/sessions/library.ts +229 -0
- package/src/settings/contract.ts +114 -0
- package/src/settings/index.ts +32 -0
- package/src/settings/manager.ts +117 -0
- package/src/transcript-export/index.ts +45 -0
- package/src/transcript-export/publish.ts +260 -0
- package/src/transcript-export/sgr.ts +315 -0
- package/src/transcript-export/template.ts +272 -0
- package/src/transcript-export/theme-bridge.ts +150 -0
- package/src/window-budget/budget/estimate.ts +135 -0
- package/src/window-budget/budget/gate.ts +33 -0
- package/src/window-budget/budget/index.ts +16 -0
- package/src/window-budget/budget/slice.ts +56 -0
- package/src/window-budget/condenser.ts +58 -0
- package/src/window-budget/contract.ts +184 -0
- package/src/window-budget/index.ts +19 -0
- package/src/window-budget/microcompact.ts +95 -0
- package/src/window-budget/rehydrate.ts +136 -0
- package/src/window-budget/summarize/condense.ts +103 -0
- package/src/window-budget/summarize/index.ts +14 -0
- package/src/window-budget/summarize/prompt.ts +149 -0
- package/src/workflow-engine/agent-runner.ts +181 -0
- package/src/workflow-engine/display.ts +224 -0
- package/src/workflow-engine/engine.ts +294 -0
- package/src/workflow-engine/index.ts +23 -0
- package/src/workflow-engine/parse.ts +172 -0
- package/src/workflow-engine/structured-output.ts +35 -0
- package/src/workspace/brand.ts +29 -0
- package/src/workspace/index.ts +18 -0
- package/src/workspace/locator.ts +103 -0
- package/src/workspace/runtime-detect.ts +64 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared slash-command helpers — the small, pure toolkit every command group is written against.
|
|
3
|
+
*/
|
|
4
|
+
import type { SlashContext, SlashOutcome, StatusMessage } from "../../contract.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The family labels the catalog groups commands under.
|
|
8
|
+
*/
|
|
9
|
+
export const FAMILY = {
|
|
10
|
+
composio: "composio",
|
|
11
|
+
memory: "memory",
|
|
12
|
+
scopedModels: "models-for",
|
|
13
|
+
theme: "theme"
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
/** The settled "command ran, nothing more to do" outcome. */
|
|
17
|
+
export const HANDLED: SlashOutcome = { kind: "handled" };
|
|
18
|
+
|
|
19
|
+
/** Mint an info-tone status toast. */
|
|
20
|
+
export function info(text: string): StatusMessage {
|
|
21
|
+
return { kind: "info", text };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Mint a warning-tone status toast (used for usage hints on bad args). */
|
|
25
|
+
export function warn(text: string): StatusMessage {
|
|
26
|
+
return { kind: "warning", text };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Split a family command's argument string into a leading sub-command verb and the remainder.
|
|
31
|
+
*/
|
|
32
|
+
export function splitVerb(args: string): { verb: string; rest: string } {
|
|
33
|
+
const trimmed = args.trim();
|
|
34
|
+
if (trimmed.length === 0) return { verb: "", rest: "" };
|
|
35
|
+
const gap = trimmed.search(/\s/);
|
|
36
|
+
if (gap === -1) return { verb: trimmed.toLowerCase(), rest: "" };
|
|
37
|
+
return {
|
|
38
|
+
verb: trimmed.slice(0, gap).toLowerCase(),
|
|
39
|
+
rest: trimmed.slice(gap + 1).trim()
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* One sub-command of a command family.
|
|
45
|
+
*/
|
|
46
|
+
export interface SubCommand {
|
|
47
|
+
readonly verb: string;
|
|
48
|
+
readonly describe: string;
|
|
49
|
+
run(ctx: SlashContext, rest: string): SlashOutcome;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Build a family command's `run` from a sub-command table.
|
|
54
|
+
*/
|
|
55
|
+
export function familyRunner(family: string, subs: readonly SubCommand[]): (ctx: SlashContext) => SlashOutcome {
|
|
56
|
+
const table = new Map(subs.map((sub) => [sub.verb, sub]));
|
|
57
|
+
const usage = () =>
|
|
58
|
+
warn(
|
|
59
|
+
`/${family} expects: ${subs.map((sub) => `${sub.verb} (${sub.describe})`).join(", ")}`
|
|
60
|
+
);
|
|
61
|
+
return (ctx) => {
|
|
62
|
+
const { verb, rest } = splitVerb(ctx.args);
|
|
63
|
+
if (verb.length === 0) {
|
|
64
|
+
ctx.setStatus(usage());
|
|
65
|
+
return HANDLED;
|
|
66
|
+
}
|
|
67
|
+
const sub = table.get(verb);
|
|
68
|
+
if (sub === void 0) {
|
|
69
|
+
ctx.setStatus(warn(`/${family}: unrecognised action "${verb}".`));
|
|
70
|
+
ctx.setStatus(usage());
|
|
71
|
+
return HANDLED;
|
|
72
|
+
}
|
|
73
|
+
return sub.run(ctx, rest);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash commands that operate on the session transcript (clear/new, compact, resume, stats, name, reload, quit).
|
|
3
|
+
*/
|
|
4
|
+
import type { SlashCommand } from "../../contract.js";
|
|
5
|
+
import { HANDLED, info, warn } from "./shared.js";
|
|
6
|
+
|
|
7
|
+
function wipeView(ctx: { dispatch: (e: any) => void; setStatus: (s: any) => void }) {
|
|
8
|
+
ctx.dispatch({ type: "rows:set", rows: [] });
|
|
9
|
+
ctx.dispatch({ type: "blocks:clear" });
|
|
10
|
+
ctx.dispatch({ type: "status:clear" });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function startNewSession(ctx: any, toast: string) {
|
|
14
|
+
wipeView(ctx);
|
|
15
|
+
ctx.setStatus(info(toast));
|
|
16
|
+
void ctx.conductor.newSession().catch(() => {
|
|
17
|
+
ctx.setStatus(warn("Could not start a new session."));
|
|
18
|
+
});
|
|
19
|
+
return HANDLED;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function statsMarkdown(stats: any, name: string | undefined): string {
|
|
23
|
+
const dollars = `$${stats.cost.toFixed(4)}`;
|
|
24
|
+
const t = stats.tokens;
|
|
25
|
+
const rows: Array<[string, string]> = [
|
|
26
|
+
["Session", name ? `${name} (${stats.sessionId})` : stats.sessionId],
|
|
27
|
+
["Messages", String(stats.totalMessages)],
|
|
28
|
+
["User / assistant", `${stats.userMessages} / ${stats.assistantMessages}`],
|
|
29
|
+
["Tool calls / results", `${stats.toolCalls} / ${stats.toolResults}`],
|
|
30
|
+
["Tokens (in / out)", `${t.input} / ${t.output}`],
|
|
31
|
+
["Tokens (cache r/w)", `${t.cacheRead} / ${t.cacheWrite}`],
|
|
32
|
+
["Tokens (total)", String(t.total)],
|
|
33
|
+
["Cost", dollars]
|
|
34
|
+
];
|
|
35
|
+
const body = rows.map(([k, v]) => `| ${k} | ${v} |`).join("\n");
|
|
36
|
+
return `| Field | Value |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
${body}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const clearCommand: SlashCommand = {
|
|
42
|
+
name: "clear",
|
|
43
|
+
summary: "Clear the conversation and start a new session",
|
|
44
|
+
aliases: ["reset"],
|
|
45
|
+
run(ctx) {
|
|
46
|
+
return startNewSession(ctx, "Started a new session.");
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const newCommand: SlashCommand = {
|
|
51
|
+
name: "new",
|
|
52
|
+
summary: "Start a fresh session",
|
|
53
|
+
run(ctx) {
|
|
54
|
+
return startNewSession(ctx, "Started a new session.");
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const compactCommand: SlashCommand = {
|
|
59
|
+
name: "compact",
|
|
60
|
+
summary: "Condense the conversation context",
|
|
61
|
+
aliases: ["condense", "summarize-context"],
|
|
62
|
+
takesArgs: true,
|
|
63
|
+
run(ctx) {
|
|
64
|
+
const instructions = ctx.args.trim();
|
|
65
|
+
const busyText =
|
|
66
|
+
instructions.length > 0
|
|
67
|
+
? `Compacting with guidance: ${instructions} — Esc to cancel`
|
|
68
|
+
: "Compacting conversation context... Press Esc to cancel.";
|
|
69
|
+
ctx.setStatus({ kind: "busy", text: busyText });
|
|
70
|
+
void (async () => {
|
|
71
|
+
try {
|
|
72
|
+
if (instructions.length > 0) {
|
|
73
|
+
await ctx.conductor.executeBash(
|
|
74
|
+
`printf '%s' ${JSON.stringify(`Compaction guidance: ${instructions}`)}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
switch (await ctx.conductor.condense()) {
|
|
78
|
+
case "condensed":
|
|
79
|
+
ctx.setStatus(
|
|
80
|
+
info(
|
|
81
|
+
instructions.length > 0
|
|
82
|
+
? "Context condensed (guidance recorded for the agent)."
|
|
83
|
+
: "Context condensed."
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
break;
|
|
87
|
+
case "nothing":
|
|
88
|
+
ctx.setStatus(warn("Nothing to compact yet."));
|
|
89
|
+
break;
|
|
90
|
+
case "cancelled":
|
|
91
|
+
ctx.setStatus(warn("Compaction cancelled."));
|
|
92
|
+
break;
|
|
93
|
+
case "busy":
|
|
94
|
+
ctx.setStatus(warn("A turn is running — press Esc to stop it first, then /compact."));
|
|
95
|
+
break;
|
|
96
|
+
case "failed":
|
|
97
|
+
ctx.setStatus(warn("Compaction failed; the transcript is unchanged."));
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
ctx.setStatus(warn("Compaction failed; the transcript is unchanged."));
|
|
102
|
+
}
|
|
103
|
+
})();
|
|
104
|
+
return HANDLED;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const resumeCommand: SlashCommand = {
|
|
109
|
+
name: "resume",
|
|
110
|
+
summary: "Resume a persisted session",
|
|
111
|
+
aliases: ["sessions"],
|
|
112
|
+
run(ctx) {
|
|
113
|
+
ctx.openModal("sessions");
|
|
114
|
+
return HANDLED;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const sessionCommand: SlashCommand = {
|
|
119
|
+
name: "session",
|
|
120
|
+
summary: "Show live session statistics (ids, counts, tokens, cost)",
|
|
121
|
+
run(ctx) {
|
|
122
|
+
const stats = ctx.conductor.stats();
|
|
123
|
+
const name = ctx.conductor.sessionName();
|
|
124
|
+
const block = {
|
|
125
|
+
id: `session-stats-${Date.now().toString(36)}`,
|
|
126
|
+
kind: "changelog",
|
|
127
|
+
title: name ? `Session: ${name}` : "Session statistics",
|
|
128
|
+
markdown: statsMarkdown(stats, name),
|
|
129
|
+
timestamp: Date.now()
|
|
130
|
+
};
|
|
131
|
+
ctx.appendBlock(block);
|
|
132
|
+
ctx.setStatus(
|
|
133
|
+
info(
|
|
134
|
+
`${stats.totalMessages} messages, ${stats.tokens.total} tokens, $${stats.cost.toFixed(4)}.`
|
|
135
|
+
)
|
|
136
|
+
);
|
|
137
|
+
return HANDLED;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const costCommand: SlashCommand = {
|
|
142
|
+
name: "cost",
|
|
143
|
+
summary: "Show session cost and token usage",
|
|
144
|
+
run(ctx) {
|
|
145
|
+
const stats = ctx.conductor.stats();
|
|
146
|
+
const t = stats.tokens;
|
|
147
|
+
const rows: Array<[string, string]> = [
|
|
148
|
+
["Total cost", `$${stats.cost.toFixed(4)}`],
|
|
149
|
+
["Total tokens", String(t.total)],
|
|
150
|
+
["Input / output", `${t.input} / ${t.output}`],
|
|
151
|
+
["Cache read / write", `${t.cacheRead} / ${t.cacheWrite}`]
|
|
152
|
+
];
|
|
153
|
+
const markdown = `| Field | Value |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
${rows.map(([k, v]) => `| ${k} | ${v} |`).join("\n")}`;
|
|
156
|
+
const block = {
|
|
157
|
+
id: `session-cost-${Date.now().toString(36)}`,
|
|
158
|
+
kind: "changelog",
|
|
159
|
+
title: "Session cost",
|
|
160
|
+
markdown,
|
|
161
|
+
timestamp: Date.now()
|
|
162
|
+
};
|
|
163
|
+
ctx.appendBlock(block);
|
|
164
|
+
ctx.setStatus(info(`$${stats.cost.toFixed(4)} · ${t.total} tokens`));
|
|
165
|
+
return HANDLED;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const forkCommand: SlashCommand = {
|
|
170
|
+
name: "branch",
|
|
171
|
+
summary: "Branch the transcript from a prior turn",
|
|
172
|
+
aliases: ["fork"],
|
|
173
|
+
run(ctx) {
|
|
174
|
+
ctx.openModal("userTurns");
|
|
175
|
+
return HANDLED;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const treeCommand: SlashCommand = {
|
|
180
|
+
name: "timeline",
|
|
181
|
+
summary: "Navigate the transcript tree",
|
|
182
|
+
aliases: ["tree"],
|
|
183
|
+
run(ctx) {
|
|
184
|
+
ctx.openModal("tree");
|
|
185
|
+
return HANDLED;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const nameCommand: SlashCommand = {
|
|
190
|
+
name: "name",
|
|
191
|
+
summary: "Name the session, or show the current name",
|
|
192
|
+
takesArgs: true,
|
|
193
|
+
run(ctx) {
|
|
194
|
+
const label = ctx.args.trim();
|
|
195
|
+
if (label.length === 0) {
|
|
196
|
+
const current = ctx.conductor.sessionName();
|
|
197
|
+
ctx.setStatus(
|
|
198
|
+
current === void 0 || current.length === 0
|
|
199
|
+
? info("This session has no name. Set one with /name <label>.")
|
|
200
|
+
: info(`Current session name: ${current}`)
|
|
201
|
+
);
|
|
202
|
+
return HANDLED;
|
|
203
|
+
}
|
|
204
|
+
ctx.conductor.setSessionName(label);
|
|
205
|
+
ctx.setStatus(info(`Session named "${label}".`));
|
|
206
|
+
return HANDLED;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const reloadCommand: SlashCommand = {
|
|
211
|
+
name: "reload",
|
|
212
|
+
summary: "Reload session resources",
|
|
213
|
+
run(ctx) {
|
|
214
|
+
const leaf = ctx.conductor.snapshot().head.leaf;
|
|
215
|
+
if (leaf === null) {
|
|
216
|
+
ctx.setStatus(info("Session is empty; nothing to reload."));
|
|
217
|
+
return HANDLED;
|
|
218
|
+
}
|
|
219
|
+
ctx.setStatus({ kind: "busy", text: "Reloading session resources..." });
|
|
220
|
+
void (async () => {
|
|
221
|
+
try {
|
|
222
|
+
await ctx.conductor.navigateTree(leaf);
|
|
223
|
+
ctx.setStatus(info("Reloaded session resources."));
|
|
224
|
+
} catch {
|
|
225
|
+
ctx.setStatus(warn("Could not reload session resources."));
|
|
226
|
+
}
|
|
227
|
+
})();
|
|
228
|
+
return HANDLED;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const quitCommand: SlashCommand = {
|
|
233
|
+
name: "quit",
|
|
234
|
+
summary: "Leave the interactive console",
|
|
235
|
+
run(ctx) {
|
|
236
|
+
ctx.requestExit();
|
|
237
|
+
return HANDLED;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const exitCommand: SlashCommand = {
|
|
242
|
+
name: "exit",
|
|
243
|
+
summary: "Leave the interactive console",
|
|
244
|
+
run(ctx) {
|
|
245
|
+
ctx.requestExit();
|
|
246
|
+
return HANDLED;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export const transcriptCommands: SlashCommand[] = [
|
|
251
|
+
clearCommand,
|
|
252
|
+
newCommand,
|
|
253
|
+
compactCommand,
|
|
254
|
+
resumeCommand,
|
|
255
|
+
sessionCommand,
|
|
256
|
+
costCommand,
|
|
257
|
+
forkCommand,
|
|
258
|
+
treeCommand,
|
|
259
|
+
nameCommand,
|
|
260
|
+
reloadCommand,
|
|
261
|
+
quitCommand,
|
|
262
|
+
exitCommand
|
|
263
|
+
];
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workbench slash commands (model pickers, settings, help, keys, changelog, debug, plan, thinking).
|
|
3
|
+
*/
|
|
4
|
+
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import type { SlashCommand } from "../../contract.js";
|
|
8
|
+
import { SLASH_COMMANDS } from "../builtins.js";
|
|
9
|
+
import { FAMILY, HANDLED, familyRunner, info, warn } from "./shared.js";
|
|
10
|
+
|
|
11
|
+
function block(slug: string, title: string, markdown: string) {
|
|
12
|
+
return {
|
|
13
|
+
id: `${slug}-${Date.now().toString(36)}`,
|
|
14
|
+
kind: "changelog",
|
|
15
|
+
title,
|
|
16
|
+
markdown,
|
|
17
|
+
timestamp: Date.now()
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function helpBlock() {
|
|
22
|
+
const lines = SLASH_COMMANDS.map((c) => {
|
|
23
|
+
const alts = c.aliases && c.aliases.length > 0 ? ` _(/${c.aliases.join(", /")})_` : "";
|
|
24
|
+
return `- **/${c.name}**${alts} — ${c.summary}`;
|
|
25
|
+
});
|
|
26
|
+
const body = [
|
|
27
|
+
`${SLASH_COMMANDS.length} commands available. Type \`/\` to complete.`,
|
|
28
|
+
"",
|
|
29
|
+
...lines
|
|
30
|
+
].join("\n");
|
|
31
|
+
return block("help", "Commands", body);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function hotkeysBlock() {
|
|
35
|
+
const rows = HOTKEYS.map(([chord, effect]) => `| \`${chord}\` | ${effect} |`);
|
|
36
|
+
const body = [
|
|
37
|
+
"| Shortcut | Action |",
|
|
38
|
+
"| --- | --- |",
|
|
39
|
+
...rows
|
|
40
|
+
].join("\n");
|
|
41
|
+
return block("hotkeys", "Keyboard shortcuts", body);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readChangelog(cwd: string) {
|
|
45
|
+
for (const name of CHANGELOG_NAMES) {
|
|
46
|
+
try {
|
|
47
|
+
const text = readFileSync(join(cwd, name), "utf8");
|
|
48
|
+
if (text.trim().length > 0) return text;
|
|
49
|
+
} catch {
|
|
50
|
+
// ignore
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function runChangelog(ctx: any) {
|
|
57
|
+
const markdown = readChangelog(process.cwd());
|
|
58
|
+
ctx.appendBlock(
|
|
59
|
+
markdown !== null
|
|
60
|
+
? block("changelog", "Changelog", markdown)
|
|
61
|
+
: block("changelog", "What's new", CHANGELOG_FALLBACK)
|
|
62
|
+
);
|
|
63
|
+
return HANDLED;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function runDebug(ctx: any) {
|
|
67
|
+
ctx.dispatch({ type: "toggle:reasoning" });
|
|
68
|
+
const stats = ctx.conductor.stats();
|
|
69
|
+
const messages = ctx.conductor.messages();
|
|
70
|
+
const snapshot = ctx.conductor.snapshot();
|
|
71
|
+
const records: any[] = [
|
|
72
|
+
{
|
|
73
|
+
type: "session",
|
|
74
|
+
at: new Date().toISOString(),
|
|
75
|
+
sessionId: stats.sessionId,
|
|
76
|
+
modelId: snapshot.modelId,
|
|
77
|
+
phase: snapshot.phase,
|
|
78
|
+
stats
|
|
79
|
+
},
|
|
80
|
+
...messages.map((message: any, index: number) => ({
|
|
81
|
+
type: "message",
|
|
82
|
+
index,
|
|
83
|
+
role: message.role ?? "unknown",
|
|
84
|
+
content: message.content
|
|
85
|
+
}))
|
|
86
|
+
];
|
|
87
|
+
const jsonl = records.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
88
|
+
try {
|
|
89
|
+
const dir = mkdtempSync(join(tmpdir(), "indus-debug-"));
|
|
90
|
+
const path = join(dir, `session-${stats.sessionId || "unknown"}.jsonl`);
|
|
91
|
+
writeFileSync(path, jsonl, "utf8");
|
|
92
|
+
ctx.setStatus(info(`Diagnostics written to ${path}`));
|
|
93
|
+
} catch (cause) {
|
|
94
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
95
|
+
ctx.setStatus(warn(`Could not write the diagnostics log (${detail}).`));
|
|
96
|
+
}
|
|
97
|
+
return HANDLED;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function runPlan(ctx: any) {
|
|
101
|
+
const wasPlan = ctx.conductor.permissionMode() === "plan";
|
|
102
|
+
const now = ctx.conductor.togglePlanMode(!wasPlan);
|
|
103
|
+
ctx.setStatus(
|
|
104
|
+
now === "plan"
|
|
105
|
+
? info("Plan mode ON — read-only research; mutating tools are blocked. Run /plan again to leave.")
|
|
106
|
+
: info(`Plan mode OFF — permission mode is now ${now}.`)
|
|
107
|
+
);
|
|
108
|
+
return HANDLED;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const SCOPED_MODEL_VERBS = [
|
|
112
|
+
{
|
|
113
|
+
verb: "edit",
|
|
114
|
+
describe: "open the per-scope routing editor",
|
|
115
|
+
run: (ctx: any) => {
|
|
116
|
+
ctx.openModal("scopedModels");
|
|
117
|
+
return HANDLED;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
verb: "show",
|
|
122
|
+
describe: "review the active per-scope assignments",
|
|
123
|
+
run: (ctx: any) => {
|
|
124
|
+
ctx.openModal("scopedModels", { focus: "summary" });
|
|
125
|
+
return HANDLED;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
verb: "reset",
|
|
130
|
+
describe: "clear every per-scope override",
|
|
131
|
+
run: (ctx: any) => {
|
|
132
|
+
ctx.openModal("scopedModels", { intent: "reset" });
|
|
133
|
+
ctx.setStatus(info("Per-scope model overrides cleared."));
|
|
134
|
+
return HANDLED;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
const runScopedModels = familyRunner(FAMILY.scopedModels, SCOPED_MODEL_VERBS);
|
|
140
|
+
|
|
141
|
+
const HOTKEYS: Array<[string, string]> = [
|
|
142
|
+
["Enter", "Submit the current turn"],
|
|
143
|
+
["Shift+Enter", "Insert a soft newline in the composer"],
|
|
144
|
+
["Esc", "Dismiss an overlay / abort the in-flight turn"],
|
|
145
|
+
["Esc Esc", "Open the transcript-tree navigator"],
|
|
146
|
+
["Shift+Tab", "Cycle permission mode (default → accept-edits → plan → bypass)"],
|
|
147
|
+
["Tab", "Accept the highlighted completion"],
|
|
148
|
+
["Ctrl+C", "Interrupt the running turn"],
|
|
149
|
+
["Ctrl+T", "Toggle reasoning / thinking rows"],
|
|
150
|
+
["/thinking", "Cycle thinking effort level (off → low → medium)"],
|
|
151
|
+
["Ctrl+V", "Attach an image from the clipboard"],
|
|
152
|
+
["Ctrl+G", "Edit the composer in your external editor"],
|
|
153
|
+
["Ctrl+O", "Expand or collapse full tool output"],
|
|
154
|
+
["Ctrl+L", "Open the model picker"],
|
|
155
|
+
["Ctrl+R", "Open the session resume list"],
|
|
156
|
+
["Ctrl+N / Ctrl+P", "Cycle the active model"],
|
|
157
|
+
["Ctrl+A / Ctrl+E", "Jump to start / end of the line"],
|
|
158
|
+
["Ctrl+U", "Clear the composer line (twice: exit)"],
|
|
159
|
+
["Ctrl+J", "Insert a newline"],
|
|
160
|
+
["Ctrl+Z", "Suspend the process"],
|
|
161
|
+
["Alt+Up", "Pull the newest queued input back into the composer"],
|
|
162
|
+
["Ctrl+X", "Run the newest queued message NOW (stops the current run)"],
|
|
163
|
+
["! <cmd>", "Run a shell command (kept in context)"],
|
|
164
|
+
["!! <cmd>", "Run a shell command (excluded from context)"]
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const CHANGELOG_NAMES = ["CHANGELOG.md", "CHANGELOG", "changelog.md"];
|
|
168
|
+
|
|
169
|
+
const CHANGELOG_FALLBACK = [
|
|
170
|
+
"No `CHANGELOG.md` was found at the workspace root.",
|
|
171
|
+
"",
|
|
172
|
+
"Recent highlights in this build:",
|
|
173
|
+
"- Model and per-scope routing pickers reachable from the composer.",
|
|
174
|
+
"- `/help` and `/keys` render live command and shortcut references.",
|
|
175
|
+
"- Colour scheme switches inline between **saffron**, **midnight**, and **daylight**."
|
|
176
|
+
].join("\n");
|
|
177
|
+
|
|
178
|
+
export const workbenchCommands: SlashCommand[] = [
|
|
179
|
+
{
|
|
180
|
+
name: "model",
|
|
181
|
+
summary: "Switch the model bound to this session",
|
|
182
|
+
aliases: ["models"],
|
|
183
|
+
run: (ctx) => {
|
|
184
|
+
ctx.openModal("models");
|
|
185
|
+
return HANDLED;
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "models-for",
|
|
190
|
+
summary: "Edit, show, or reset per-scope model routing",
|
|
191
|
+
aliases: ["scoped-models"],
|
|
192
|
+
family: FAMILY.scopedModels,
|
|
193
|
+
takesArgs: true,
|
|
194
|
+
run: runScopedModels
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "settings",
|
|
198
|
+
summary: "Open the settings overlay",
|
|
199
|
+
run: (ctx) => {
|
|
200
|
+
ctx.openModal("settings");
|
|
201
|
+
return HANDLED;
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "help",
|
|
206
|
+
summary: "List the available commands",
|
|
207
|
+
aliases: ["?"],
|
|
208
|
+
run: async (ctx) => {
|
|
209
|
+
ctx.appendBlock(await helpBlock());
|
|
210
|
+
return HANDLED;
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: "keys",
|
|
215
|
+
summary: "Show the keyboard shortcut map",
|
|
216
|
+
aliases: ["hotkeys"],
|
|
217
|
+
run: (ctx) => {
|
|
218
|
+
ctx.appendBlock(hotkeysBlock());
|
|
219
|
+
return HANDLED;
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: "whats-new",
|
|
224
|
+
summary: "Show what changed in this build",
|
|
225
|
+
aliases: ["changelog"],
|
|
226
|
+
run: runChangelog
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "plan",
|
|
230
|
+
summary: "Toggle plan mode (read-only research; mutating tools are blocked)",
|
|
231
|
+
run: runPlan
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: "thinking",
|
|
235
|
+
summary: "Cycle thinking effort level (off → low → medium → off)",
|
|
236
|
+
run: (ctx) => {
|
|
237
|
+
ctx.conductor.cycleThinkingLevel();
|
|
238
|
+
return HANDLED;
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: "debug",
|
|
243
|
+
summary: "Write a session diagnostics log and toggle verbose view",
|
|
244
|
+
run: runDebug
|
|
245
|
+
}
|
|
246
|
+
];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash subsystem — public barrel.
|
|
3
|
+
*/
|
|
4
|
+
export { SLASH_COMMANDS, FAMILY } from "./builtins.js";
|
|
5
|
+
export { buildRegistry, tokensOf, findCommand, matchPrefix, listFamilies, commandsInFamily } from "./registry.js";
|
|
6
|
+
export { SLASH_PREFIX, looksLikeSlash, parseSlash, resolveSlash } from "./resolve.js";
|
|
7
|
+
export type { SlashLine, SlashResolution } from "./resolve.js";
|
|
8
|
+
import { buildRegistry } from "./registry.js";
|
|
9
|
+
import { SLASH_COMMANDS } from "./builtins.js";
|
|
10
|
+
import type { SlashRegistry } from "../contract.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The built-in catalog pre-folded into a resolved {@link SlashRegistry}.
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_SLASH_REGISTRY: SlashRegistry = buildRegistry(SLASH_COMMANDS);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash registry assembly — fold a command list into the resolved registry.
|
|
3
|
+
*/
|
|
4
|
+
import type { SlashCommand, SlashRegistry } from "../contract.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Every token a command answers to: its canonical name plus any aliases.
|
|
8
|
+
*/
|
|
9
|
+
export function tokensOf(command: SlashCommand): string[] {
|
|
10
|
+
return [command.name, ...(command.aliases ?? [])];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fold an ordered command list into a resolved {@link SlashRegistry}.
|
|
15
|
+
*/
|
|
16
|
+
export function buildRegistry(commands: readonly SlashCommand[]): SlashRegistry {
|
|
17
|
+
const index = new Map<string, SlashCommand>();
|
|
18
|
+
for (const command of commands) {
|
|
19
|
+
for (const token of tokensOf(command)) {
|
|
20
|
+
const key = token.toLowerCase();
|
|
21
|
+
const existing = index.get(key);
|
|
22
|
+
if (existing !== void 0 && existing !== command) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`slash registry: token "${key}" is claimed by both "${existing.name}" and "${command.name}"`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
index.set(key, command);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { commands, index };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a single token (canonical or alias) to its command, or `undefined`.
|
|
35
|
+
*/
|
|
36
|
+
export function findCommand(registry: SlashRegistry, token: string): SlashCommand | undefined {
|
|
37
|
+
const bare = token.startsWith("/") ? token.slice(1) : token;
|
|
38
|
+
return registry.index.get(bare.toLowerCase());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The commands whose name or alias begins with a partial token, in registry order.
|
|
43
|
+
*/
|
|
44
|
+
export function matchPrefix(registry: SlashRegistry, partial: string): SlashCommand[] {
|
|
45
|
+
const needle = partial.toLowerCase();
|
|
46
|
+
if (needle.length === 0) return [...registry.commands];
|
|
47
|
+
return registry.commands.filter(
|
|
48
|
+
(command) => tokensOf(command).some((token) => token.toLowerCase().startsWith(needle))
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The distinct family labels present in the registry, in first-seen order.
|
|
54
|
+
*/
|
|
55
|
+
export function listFamilies(registry: SlashRegistry): string[] {
|
|
56
|
+
const seen: string[] = [];
|
|
57
|
+
for (const command of registry.commands) {
|
|
58
|
+
if (command.family !== void 0 && !seen.includes(command.family)) {
|
|
59
|
+
seen.push(command.family);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return seen;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The commands belonging to a family, in registry order.
|
|
67
|
+
*/
|
|
68
|
+
export function commandsInFamily(registry: SlashRegistry, family: string): SlashCommand[] {
|
|
69
|
+
return registry.commands.filter((command) => command.family === family);
|
|
70
|
+
}
|