@sns-myagent/cli 0.2.0 → 0.3.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 +12 -1
- package/README.md +7 -8
- package/bin/.gitkeep +0 -0
- package/bin/snscoder.js +7 -96
- package/dist/cli.js +22026 -0
- package/package.json +4 -3
- package/scripts/apply-pi-natives-patch.js +82 -56
- package/scripts/fetch-binary.mjs +106 -217
- package/scripts/smoke-test.sh +92 -0
- package/src/adapters/telegram/bot.ts +41 -1
- package/src/adapters/telegram/bridge.ts +186 -0
- package/src/adapters/telegram/handler.ts +138 -13
- package/src/adapters/telegram/index.ts +12 -2
- package/src/agents/__tests__/config.test.ts +79 -0
- package/src/agents/__tests__/resilience.test.ts +119 -0
- package/src/agents/__tests__/strategies.test.ts +83 -0
- package/src/agents/config.ts +367 -0
- package/src/agents/ensemble.ts +230 -0
- package/src/agents/resilience.ts +332 -0
- package/src/agents/strategies/best-of-n.ts +108 -0
- package/src/agents/strategies/consensus.ts +104 -0
- package/src/agents/strategies/critic.ts +131 -0
- package/src/agents/strategies/index.ts +12 -0
- package/src/agents/strategies/types.ts +68 -0
- package/src/async/__tests__/task-runner.test.ts +162 -0
- package/src/async/__tests__/task-store.test.ts +146 -0
- package/src/async/index.ts +28 -1
- package/src/async/notifier.ts +133 -0
- package/src/async/task-runner.ts +175 -0
- package/src/async/task-store.ts +170 -0
- package/src/async/types.ts +70 -0
- package/src/cli/entry.ts +3 -1
- package/src/cli/index.ts +74 -55
- package/src/config/index.ts +1 -1
- package/src/debug/index.ts +1 -1
- package/src/internal-urls/docs-index.generated.txt +0 -2
- package/src/modes/components/welcome.ts +13 -6
- package/src/modes/controllers/event-controller.ts +1 -1
- package/src/modes/setup-wizard/scenes/splash.ts +1 -1
- package/src/session/agent-session.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +63 -0
- package/src/slash-commands/helpers/task.ts +181 -0
- package/src/tbm/__tests__/tbm.test.ts +660 -0
- package/src/tbm/comm-modes.ts +165 -0
- package/src/tbm/config.ts +136 -0
- package/src/tbm/context-delta.ts +146 -0
- package/src/tbm/context-pyramid.ts +202 -0
- package/src/tbm/dashboard.ts +131 -0
- package/src/tbm/index.ts +247 -0
- package/src/tbm/lazy-skills.ts +182 -0
- package/src/tbm/response-cache.ts +220 -0
- package/src/tbm/tombstone.ts +189 -0
- package/src/tbm/tool-compress.ts +230 -0
- package/src/tools/ask.ts +1 -1
- package/src/tui/chat-blocks.ts +205 -0
- package/src/tui/chat-ui.ts +270 -0
- package/src/tui/code-cell.ts +90 -1
- package/src/tui/command-palette.ts +189 -0
- package/src/tui/index.ts +4 -0
- package/src/tui/splash.ts +130 -0
- package/src/ui/banner.ts +69 -29
- package/src/ui/colors.ts +24 -0
- package/src/ui/error-display.ts +130 -0
- package/src/ui/gradient.ts +104 -0
- package/src/ui/index.ts +15 -0
- package/src/ui/memory-toast.ts +102 -0
- package/src/ui/status-bar.ts +36 -30
|
@@ -453,15 +453,22 @@ export class WelcomeComponent implements Component {
|
|
|
453
453
|
}
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
-
export const PI_LOGO = [
|
|
456
|
+
export const PI_LOGO = [
|
|
457
|
+
"███╗ ██╗███████╗██╗ ██╗██╗ ██╗███████╗",
|
|
458
|
+
"████╗ ██║██╔════╝╚██╗██╔╝██║ ██║██╔════╝",
|
|
459
|
+
"██╔██╗ ██║█████╗ ╚███╔╝ ██║ ██║███████╗",
|
|
460
|
+
"██║╚██╗██║██╔══╝ ██╔██╗ ██║ ██║╚════██║",
|
|
461
|
+
"██║ ╚████║███████╗██╔╝ ╚██╗╚██████╔╝███████║",
|
|
462
|
+
"╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝",
|
|
463
|
+
];
|
|
457
464
|
|
|
458
465
|
/** Multi-stop palette for the diagonal gradient. */
|
|
459
466
|
const GRADIENT_STOPS: ReadonlyArray<readonly [number, number, number]> = [
|
|
460
|
-
[
|
|
461
|
-
[
|
|
462
|
-
[
|
|
463
|
-
[
|
|
464
|
-
[
|
|
467
|
+
[0, 210, 255], // #00d2ff — SNS cyan
|
|
468
|
+
[80, 150, 255], // transitional blue
|
|
469
|
+
[123, 47, 247], // #7b2ff7 — SNS purple
|
|
470
|
+
[200, 70, 180], // transitional pink-purple
|
|
471
|
+
[255, 107, 157], // #ff6b9d — SNS pink
|
|
465
472
|
];
|
|
466
473
|
|
|
467
474
|
/** 256-color ramp fallback when truecolor isn't available. */
|
|
@@ -1182,7 +1182,7 @@ export class EventController {
|
|
|
1182
1182
|
|
|
1183
1183
|
const sessionName = this.ctx.sessionManager.getSessionName();
|
|
1184
1184
|
TERMINAL.sendNotification({
|
|
1185
|
-
title: sessionName || "
|
|
1185
|
+
title: sessionName || "SnsCoder",
|
|
1186
1186
|
body: "Complete",
|
|
1187
1187
|
type: "completion",
|
|
1188
1188
|
actions: "focus",
|
|
@@ -189,7 +189,7 @@ export function renderSetupSplash(width: number, height: number, elapsedMs: numb
|
|
|
189
189
|
/** Centered fallback for windows too small to hold the full scene. */
|
|
190
190
|
function renderCompactSplash(width: number, height: number, phase: number, shine: ShineConfig): string[] {
|
|
191
191
|
const art = height >= 14 ? LARGE_LOGO : PI_LOGO;
|
|
192
|
-
const content = [...gradientLogo(art, phase, shine), "", theme.bold("
|
|
192
|
+
const content = [...gradientLogo(art, phase, shine), "", theme.bold("S n s C o d e r")];
|
|
193
193
|
const start = Math.max(0, Math.floor((height - content.length) / 2));
|
|
194
194
|
const lines: string[] = [];
|
|
195
195
|
for (let y = 0; y < height; y++) {
|
|
@@ -1376,7 +1376,7 @@ export class AgentSession {
|
|
|
1376
1376
|
if (mode === "off") return;
|
|
1377
1377
|
try {
|
|
1378
1378
|
this.#powerAssertion = MacOSPowerAssertion.start({
|
|
1379
|
-
reason: "
|
|
1379
|
+
reason: "SnsCoder agent session",
|
|
1380
1380
|
idle: true,
|
|
1381
1381
|
display: mode === "display" || mode === "system",
|
|
1382
1382
|
system: mode === "system",
|
|
@@ -32,6 +32,7 @@ import { urlHyperlinkAlways } from "../tui";
|
|
|
32
32
|
import { getChangelogPath, parseChangelog } from "../utils/changelog";
|
|
33
33
|
import { CollabQrCodeComponent } from "./helpers/collab-qrcode";
|
|
34
34
|
import { handleCronCommand } from "./helpers/cron";
|
|
35
|
+
import { handleTaskCommand } from "./helpers/task";
|
|
35
36
|
import { buildContextReportText } from "./helpers/context-report";
|
|
36
37
|
import { formatDuration } from "./helpers/format";
|
|
37
38
|
import { createMarketplaceManager } from "./helpers/marketplace-manager";
|
|
@@ -2233,11 +2234,73 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
2233
2234
|
],
|
|
2234
2235
|
handle: handleCronCommand,
|
|
2235
2236
|
},
|
|
2237
|
+
{
|
|
2238
|
+
name: "task",
|
|
2239
|
+
description: "Manage async background tasks",
|
|
2240
|
+
allowArgs: true,
|
|
2241
|
+
subcommands: [
|
|
2242
|
+
{ name: "run", description: "Spawn async task in background", usage: "<description>" },
|
|
2243
|
+
{ name: "list", description: "Show all tasks" },
|
|
2244
|
+
{ name: "status", description: "Check specific task", usage: "<id>" },
|
|
2245
|
+
{ name: "cancel", description: "Cancel running task", usage: "<id>" },
|
|
2246
|
+
{ name: "result", description: "Get task result", usage: "<id>" },
|
|
2247
|
+
],
|
|
2248
|
+
handle: handleTaskCommand,
|
|
2249
|
+
},
|
|
2236
2250
|
{
|
|
2237
2251
|
name: "quit",
|
|
2238
2252
|
description: "Quit the application",
|
|
2239
2253
|
handleTui: shutdownHandlerTui,
|
|
2240
2254
|
},
|
|
2255
|
+
{
|
|
2256
|
+
name: "tokens",
|
|
2257
|
+
description: "Show Token Budget Manager dashboard — session stats, cache hits, compression, pyramid level",
|
|
2258
|
+
inlineHint: "[compact]",
|
|
2259
|
+
allowArgs: true,
|
|
2260
|
+
handle: async (command, runtime) => {
|
|
2261
|
+
try {
|
|
2262
|
+
// TBM is accessed via session.tbm if available
|
|
2263
|
+
const session = runtime.session as unknown as Record<string, unknown>;
|
|
2264
|
+
const tbm = session.tbm as { renderDashboard(): string; renderCompactDashboard(): string } | undefined;
|
|
2265
|
+
if (!tbm) {
|
|
2266
|
+
await runtime.output("TBM not initialized. Check config tbm.enabled.");
|
|
2267
|
+
return commandConsumed();
|
|
2268
|
+
}
|
|
2269
|
+
const compact = command.args.trim() === "compact";
|
|
2270
|
+
await runtime.output(compact ? tbm.renderCompactDashboard() : tbm.renderDashboard());
|
|
2271
|
+
return commandConsumed();
|
|
2272
|
+
} catch (err) {
|
|
2273
|
+
await runtime.output(`Failed to render TBM dashboard: ${errorMessage(err)}`);
|
|
2274
|
+
return commandConsumed();
|
|
2275
|
+
}
|
|
2276
|
+
},
|
|
2277
|
+
},
|
|
2278
|
+
{
|
|
2279
|
+
name: "mode",
|
|
2280
|
+
description: "Set communication mode: caveman (terse), normal, verbose, or auto",
|
|
2281
|
+
inlineHint: "[caveman|normal|verbose|auto|status]",
|
|
2282
|
+
allowArgs: true,
|
|
2283
|
+
handle: async (command, runtime) => {
|
|
2284
|
+
const arg = command.args.trim().toLowerCase();
|
|
2285
|
+
const session = runtime.session as unknown as Record<string, unknown>;
|
|
2286
|
+
const tbm = session.tbm as { setMode(mode: string): void; renderCompactDashboard(): string } | undefined;
|
|
2287
|
+
if (!tbm) {
|
|
2288
|
+
await runtime.output("TBM not initialized.");
|
|
2289
|
+
return commandConsumed();
|
|
2290
|
+
}
|
|
2291
|
+
if (!arg || arg === "status") {
|
|
2292
|
+
await runtime.output(tbm.renderCompactDashboard());
|
|
2293
|
+
return commandConsumed();
|
|
2294
|
+
}
|
|
2295
|
+
if (!["caveman", "normal", "verbose", "auto"].includes(arg)) {
|
|
2296
|
+
await runtime.output("Usage: /mode [caveman|normal|verbose|auto|status]");
|
|
2297
|
+
return commandConsumed();
|
|
2298
|
+
}
|
|
2299
|
+
tbm.setMode(arg);
|
|
2300
|
+
await runtime.output(`Communication mode set to: ${arg}`);
|
|
2301
|
+
return commandConsumed();
|
|
2302
|
+
},
|
|
2303
|
+
},
|
|
2241
2304
|
];
|
|
2242
2305
|
|
|
2243
2306
|
const BUILTIN_SLASH_COMMAND_LOOKUP = new Map<string, SlashCommandSpec>();
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// /task slash command handler
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
import { getTaskStore } from "../../async/task-store";
|
|
6
|
+
import { getTaskRunner } from "../../async/task-runner";
|
|
7
|
+
import { formatTaskList, formatTaskStatus, cliNotify } from "../../async/notifier";
|
|
8
|
+
import type { ParsedSlashCommand, SlashCommandResult, SlashCommandRuntime } from "../types";
|
|
9
|
+
import { parseSubcommand } from "./parse";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Handle /task subcommands: run, list, status, cancel, result.
|
|
13
|
+
*/
|
|
14
|
+
export async function handleTaskCommand(
|
|
15
|
+
command: ParsedSlashCommand,
|
|
16
|
+
runtime: SlashCommandRuntime,
|
|
17
|
+
): Promise<SlashCommandResult> {
|
|
18
|
+
const { verb, rest } = parseSubcommand(command.args);
|
|
19
|
+
const store = getTaskStore();
|
|
20
|
+
|
|
21
|
+
switch (verb) {
|
|
22
|
+
case "run":
|
|
23
|
+
return taskRun(rest, store, runtime);
|
|
24
|
+
case "list":
|
|
25
|
+
case "ls":
|
|
26
|
+
return taskList(store, runtime);
|
|
27
|
+
case "status":
|
|
28
|
+
return taskStatus(rest, store, runtime);
|
|
29
|
+
case "cancel":
|
|
30
|
+
case "stop":
|
|
31
|
+
return taskCancel(rest, store, runtime);
|
|
32
|
+
case "result":
|
|
33
|
+
case "out":
|
|
34
|
+
return taskResult(rest, store, runtime);
|
|
35
|
+
default:
|
|
36
|
+
await runtime.output(
|
|
37
|
+
`Usage: /task <subcommand>\n` +
|
|
38
|
+
` run <description> Spawn async task in background\n` +
|
|
39
|
+
` list Show all tasks (pending + running)\n` +
|
|
40
|
+
` status <id> Check specific task status\n` +
|
|
41
|
+
` cancel <id> Cancel running/pending task\n` +
|
|
42
|
+
` result <id> Get full task result\n`,
|
|
43
|
+
);
|
|
44
|
+
return { consumed: true };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function taskRun(
|
|
49
|
+
description: string,
|
|
50
|
+
store: ReturnType<typeof getTaskStore>,
|
|
51
|
+
runtime: SlashCommandRuntime,
|
|
52
|
+
): Promise<SlashCommandResult> {
|
|
53
|
+
if (!description.trim()) {
|
|
54
|
+
await runtime.output("Usage: /task run <description>");
|
|
55
|
+
return { consumed: true };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const task = store.create({ description: description.trim() });
|
|
59
|
+
const runner = getTaskRunner(store);
|
|
60
|
+
|
|
61
|
+
// Register a default prompt executor (idempotent — overwrites if exists)
|
|
62
|
+
runner.registerExecutor("prompt", async (t) => {
|
|
63
|
+
// Default executor: simulate work (real executor wired by agent session)
|
|
64
|
+
return { success: true, result: `Task "${t.description}" completed by default executor.` };
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
runner.start();
|
|
68
|
+
await runner.submit(task);
|
|
69
|
+
|
|
70
|
+
await runtime.output(
|
|
71
|
+
`✅ Task created: ${task.id.slice(0, 8)}\n` +
|
|
72
|
+
` Description: ${task.description}\n` +
|
|
73
|
+
` Status: pending → running in background\n` +
|
|
74
|
+
` Check: /task status ${task.id.slice(0, 8)}`,
|
|
75
|
+
);
|
|
76
|
+
return { consumed: true };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function taskList(
|
|
80
|
+
store: ReturnType<typeof getTaskStore>,
|
|
81
|
+
runtime: SlashCommandRuntime,
|
|
82
|
+
): Promise<SlashCommandResult> {
|
|
83
|
+
const active = store.list("running");
|
|
84
|
+
const pending = store.list("pending", 10);
|
|
85
|
+
const recent = store.list("completed", 5);
|
|
86
|
+
|
|
87
|
+
const tasks = [...active, ...pending, ...recent];
|
|
88
|
+
await runtime.output(formatTaskList(tasks));
|
|
89
|
+
|
|
90
|
+
const counts = store.count();
|
|
91
|
+
await runtime.output(
|
|
92
|
+
`Total: ${counts.total} | Pending: ${counts.pending} | Running: ${counts.running} | Done: ${counts.completed} | Failed: ${counts.failed}`,
|
|
93
|
+
);
|
|
94
|
+
return { consumed: true };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function taskStatus(
|
|
98
|
+
idPrefix: string,
|
|
99
|
+
store: ReturnType<typeof getTaskStore>,
|
|
100
|
+
runtime: SlashCommandRuntime,
|
|
101
|
+
): Promise<SlashCommandResult> {
|
|
102
|
+
const id = idPrefix.trim();
|
|
103
|
+
if (!id) {
|
|
104
|
+
await runtime.output("Usage: /task status <id>");
|
|
105
|
+
return { consumed: true };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const task = findTaskByIdPrefix(store, id);
|
|
109
|
+
if (!task) {
|
|
110
|
+
await runtime.output(`Task not found: ${id}`);
|
|
111
|
+
return { consumed: true };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await runtime.output(formatTaskStatus(task));
|
|
115
|
+
return { consumed: true };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function taskCancel(
|
|
119
|
+
idPrefix: string,
|
|
120
|
+
store: ReturnType<typeof getTaskStore>,
|
|
121
|
+
runtime: SlashCommandRuntime,
|
|
122
|
+
): Promise<SlashCommandResult> {
|
|
123
|
+
const id = idPrefix.trim();
|
|
124
|
+
if (!id) {
|
|
125
|
+
await runtime.output("Usage: /task cancel <id>");
|
|
126
|
+
return { consumed: true };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const task = findTaskByIdPrefix(store, id);
|
|
130
|
+
if (!task) {
|
|
131
|
+
await runtime.output(`Task not found: ${id}`);
|
|
132
|
+
return { consumed: true };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const runner = getTaskRunner(store);
|
|
136
|
+
const cancelled = runner.cancel(task.id);
|
|
137
|
+
if (cancelled) {
|
|
138
|
+
await runtime.output(`🚫 Task ${task.id.slice(0, 8)} cancelled.`);
|
|
139
|
+
} else {
|
|
140
|
+
await runtime.output(`Task ${task.id.slice(0, 8)} is already ${task.status}, cannot cancel.`);
|
|
141
|
+
}
|
|
142
|
+
return { consumed: true };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function taskResult(
|
|
146
|
+
idPrefix: string,
|
|
147
|
+
store: ReturnType<typeof getTaskStore>,
|
|
148
|
+
runtime: SlashCommandRuntime,
|
|
149
|
+
): Promise<SlashCommandResult> {
|
|
150
|
+
const id = idPrefix.trim();
|
|
151
|
+
if (!id) {
|
|
152
|
+
await runtime.output("Usage: /task result <id>");
|
|
153
|
+
return { consumed: true };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const task = findTaskByIdPrefix(store, id);
|
|
157
|
+
if (!task) {
|
|
158
|
+
await runtime.output(`Task not found: ${id}`);
|
|
159
|
+
return { consumed: true };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (task.status === "completed" && task.result) {
|
|
163
|
+
await runtime.output(`Result for ${task.id.slice(0, 8)}:\n${task.result}`);
|
|
164
|
+
} else if (task.status === "failed" && task.error) {
|
|
165
|
+
await runtime.output(`Error for ${task.id.slice(0, 8)}:\n${task.error}`);
|
|
166
|
+
} else {
|
|
167
|
+
await runtime.output(`Task ${task.id.slice(0, 8)} is ${task.status} — no result yet.`);
|
|
168
|
+
}
|
|
169
|
+
return { consumed: true };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Find task by full ID or prefix (first 8 chars) */
|
|
173
|
+
function findTaskByIdPrefix(store: ReturnType<typeof getTaskStore>, prefix: string) {
|
|
174
|
+
// Try full ID first
|
|
175
|
+
const full = store.getById(prefix);
|
|
176
|
+
if (full) return full;
|
|
177
|
+
|
|
178
|
+
// Try prefix match — search all recent tasks
|
|
179
|
+
const all = store.list(undefined, 100);
|
|
180
|
+
return all.find((t) => t.id.startsWith(prefix)) ?? null;
|
|
181
|
+
}
|