indusagi-coding-agent 0.2.13 → 0.2.14
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/README.md +19 -0
- package/dist/entry.js +1762 -958
- package/dist/guardrails.js +145 -35
- package/dist/index.js +1810 -1006
- package/dist/review-agent.js +344 -60
- package/dist/review-server.js +346 -62
- package/package.json +12 -3
- package/src/boot/auth-vault.ts +4 -5
- package/src/boot/runners/addon-wiring.ts +111 -3
- package/src/boot/runners/benchmark-integrity.ts +107 -0
- package/src/boot/runners/repl-runner.ts +2 -1
- package/src/boot/runners/session.ts +18 -8
- package/src/boot/server-token.ts +20 -11
- package/src/briefing/context-docs.ts +144 -12
- package/src/channels/oneshot.ts +6 -0
- package/src/conductor/conductor.ts +67 -12
- package/src/conductor/contract.ts +4 -3
- package/src/conductor/transcript-store/store.ts +4 -8
- package/src/console/auth-status.ts +9 -4
- package/src/console/components/AgentsView.ts +2 -2
- package/src/console/components/BackgroundAgents.ts +15 -2
- package/src/console/components/Banner.ts +20 -9
- package/src/console/components/Composer.ts +57 -82
- package/src/console/components/ScrollViewport.ts +46 -0
- package/src/console/components/TerminalConsole.ts +165 -162
- package/src/console/components/WorkingIndicator.ts +6 -5
- package/src/console/components/banner-sweep.ts +5 -1
- package/src/console/components/welcome.ts +3 -3
- package/src/console/contract.ts +10 -5
- package/src/console/index.ts +7 -7
- package/src/console/input/complete.ts +2 -2
- package/src/console/input/dir-reader.ts +3 -1
- package/src/console/input/graphemes.ts +22 -0
- package/src/console/input/keymap.ts +6 -4
- package/src/console/mount.ts +45 -3
- package/src/console/overlays/auth.ts +28 -8
- package/src/console/overlays/boards.ts +13 -40
- package/src/console/overlays/host.ts +10 -0
- package/src/console/overlays/pickers.ts +35 -30
- package/src/console/reducer.ts +12 -5
- package/src/console/slash/builtins.ts +4 -3
- package/src/console/slash/commands/integrations.ts +13 -14
- package/src/console/slash/commands/transcript.ts +3 -8
- package/src/console/slash/commands/workbench.ts +2 -2
- package/src/console/test-support.ts +48 -0
- package/src/integrations/sarvam/config.ts +15 -13
- package/src/kit/external-editor.ts +17 -1
- package/src/kit/run-shell.ts +31 -0
- package/src/launch/invocation/flags.ts +12 -0
- package/src/launch/login.ts +23 -10
- package/src/launch/oauth.ts +3 -0
- package/src/models.ts +78 -2
- package/src/security/private-state.ts +122 -0
- package/src/window-budget/summarize/condense.ts +4 -1
- package/src/workspace/brand.ts +1 -1
- package/src/workspace/locator.ts +2 -2
package/dist/review-agent.js
CHANGED
|
@@ -442,6 +442,56 @@ import { Agent } from "indusagi/agent";
|
|
|
442
442
|
import { createZeroUsage as createZeroUsage2 } from "indusagi/ai";
|
|
443
443
|
import * as path2 from "node:path";
|
|
444
444
|
|
|
445
|
+
// src/kit/run-shell.ts
|
|
446
|
+
import { spawn } from "node:child_process";
|
|
447
|
+
function runShell(command, cwd, signal) {
|
|
448
|
+
if (signal?.aborted) return Promise.resolve({ output: "Command cancelled.", exitCode: 130 });
|
|
449
|
+
return new Promise((resolve3) => {
|
|
450
|
+
let output = "", truncated = false, settled = false;
|
|
451
|
+
let forceKill;
|
|
452
|
+
const child = spawn(command, { cwd, shell: true, detached: process.platform !== "win32" });
|
|
453
|
+
const capture = (chunk) => {
|
|
454
|
+
output += String(chunk);
|
|
455
|
+
if (output.length > 1024 * 1024) {
|
|
456
|
+
output = output.slice(-1024 * 1024);
|
|
457
|
+
truncated = true;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
const kill = (signalName) => {
|
|
461
|
+
try {
|
|
462
|
+
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, signalName);
|
|
463
|
+
else child.kill(signalName);
|
|
464
|
+
} catch {
|
|
465
|
+
try {
|
|
466
|
+
child.kill(signalName);
|
|
467
|
+
} catch {
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
const abort = () => {
|
|
472
|
+
kill("SIGTERM");
|
|
473
|
+
forceKill = setTimeout(() => kill("SIGKILL"), 1e3);
|
|
474
|
+
forceKill.unref();
|
|
475
|
+
};
|
|
476
|
+
const finish = (exitCode) => {
|
|
477
|
+
if (settled) return;
|
|
478
|
+
settled = true;
|
|
479
|
+
signal?.removeEventListener("abort", abort);
|
|
480
|
+
if (forceKill) clearTimeout(forceKill);
|
|
481
|
+
resolve3({ output: (truncated ? "[older output truncated]\n" : "") + output, exitCode: signal?.aborted ? 130 : exitCode });
|
|
482
|
+
};
|
|
483
|
+
child.stdout?.on("data", capture);
|
|
484
|
+
child.stderr?.on("data", capture);
|
|
485
|
+
child.once("error", (error) => {
|
|
486
|
+
capture(error.message);
|
|
487
|
+
finish(1);
|
|
488
|
+
});
|
|
489
|
+
child.once("close", (code) => finish(code ?? 1));
|
|
490
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
491
|
+
if (signal?.aborted) abort();
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
445
495
|
// src/conductor/catalog/catalog.ts
|
|
446
496
|
import { z } from "zod";
|
|
447
497
|
import { getModels, getProviders, modelRegistry } from "indusagi/ai";
|
|
@@ -473,7 +523,74 @@ var GPT_6_ASTRA_BASE = {
|
|
|
473
523
|
}
|
|
474
524
|
}
|
|
475
525
|
};
|
|
526
|
+
var GPT_6_SOL_BASE = {
|
|
527
|
+
id: "gpt-6-sol",
|
|
528
|
+
name: "GPT-6 Sol",
|
|
529
|
+
api: "openai-responses",
|
|
530
|
+
reasoning: true,
|
|
531
|
+
reasoningEfforts: ["high"],
|
|
532
|
+
input: ["text", "image"],
|
|
533
|
+
cost: {
|
|
534
|
+
input: 10,
|
|
535
|
+
cacheRead: 1,
|
|
536
|
+
cacheWrite: 12.5,
|
|
537
|
+
output: 50
|
|
538
|
+
},
|
|
539
|
+
contextWindow: 105e4,
|
|
540
|
+
maxTokens: 128e3,
|
|
541
|
+
longContextPricing: {
|
|
542
|
+
threshold: 272e3,
|
|
543
|
+
multipliers: {
|
|
544
|
+
input: 2,
|
|
545
|
+
cacheRead: 2,
|
|
546
|
+
cacheWrite: 2,
|
|
547
|
+
output: 1.5
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
var GPT_6_LUNA_BASE = {
|
|
552
|
+
id: "gpt-6-luna",
|
|
553
|
+
name: "GPT-6 Luna",
|
|
554
|
+
api: "openai-responses",
|
|
555
|
+
reasoning: true,
|
|
556
|
+
reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
557
|
+
input: ["text", "image"],
|
|
558
|
+
cost: {
|
|
559
|
+
input: 0.1,
|
|
560
|
+
cacheRead: 0.01,
|
|
561
|
+
cacheWrite: 0.125,
|
|
562
|
+
output: 0.5
|
|
563
|
+
},
|
|
564
|
+
contextWindow: 105e4,
|
|
565
|
+
maxTokens: 128e3,
|
|
566
|
+
longContextPricing: {
|
|
567
|
+
threshold: 272e3,
|
|
568
|
+
multipliers: { input: 2, cacheRead: 2, cacheWrite: 2, output: 1.5 }
|
|
569
|
+
}
|
|
570
|
+
};
|
|
476
571
|
var CODING_AGENT_MODELS = [
|
|
572
|
+
{
|
|
573
|
+
...GPT_6_SOL_BASE,
|
|
574
|
+
provider: "openai",
|
|
575
|
+
baseUrl: "https://api.openai.com/v1"
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
...GPT_6_SOL_BASE,
|
|
579
|
+
api: "openai-codex-responses",
|
|
580
|
+
provider: "openai-codex",
|
|
581
|
+
baseUrl: "https://chatgpt.com/backend-api"
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
...GPT_6_LUNA_BASE,
|
|
585
|
+
provider: "openai",
|
|
586
|
+
baseUrl: "https://api.openai.com/v1"
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
...GPT_6_LUNA_BASE,
|
|
590
|
+
api: "openai-codex-responses",
|
|
591
|
+
provider: "openai-codex",
|
|
592
|
+
baseUrl: "https://chatgpt.com/backend-api"
|
|
593
|
+
},
|
|
477
594
|
{
|
|
478
595
|
...GPT_6_ASTRA_BASE,
|
|
479
596
|
provider: "openai",
|
|
@@ -509,8 +626,15 @@ function applyCodingAgentModelOverlay(registry) {
|
|
|
509
626
|
return registry;
|
|
510
627
|
}
|
|
511
628
|
function isReasoningEffortSupported(modelId, effort) {
|
|
512
|
-
|
|
513
|
-
|
|
629
|
+
const id = modelId?.split("/").at(-1);
|
|
630
|
+
if (id === "gpt-6-astra") {
|
|
631
|
+
return GPT_6_ASTRA_BASE.reasoningEfforts.includes(effort);
|
|
632
|
+
}
|
|
633
|
+
if (id === "gpt-6-sol") return effort === "high";
|
|
634
|
+
if (id === "gpt-6-luna") {
|
|
635
|
+
return GPT_6_LUNA_BASE.reasoningEfforts.includes(effort);
|
|
636
|
+
}
|
|
637
|
+
return true;
|
|
514
638
|
}
|
|
515
639
|
|
|
516
640
|
// src/conductor/catalog/catalog.ts
|
|
@@ -917,6 +1041,125 @@ function parseSessionText(sessionId, text) {
|
|
|
917
1041
|
};
|
|
918
1042
|
}
|
|
919
1043
|
|
|
1044
|
+
// src/security/private-state.ts
|
|
1045
|
+
import {
|
|
1046
|
+
chmodSync,
|
|
1047
|
+
closeSync,
|
|
1048
|
+
constants,
|
|
1049
|
+
fchmodSync,
|
|
1050
|
+
fstatSync,
|
|
1051
|
+
lstatSync,
|
|
1052
|
+
mkdirSync,
|
|
1053
|
+
openSync,
|
|
1054
|
+
readFileSync,
|
|
1055
|
+
renameSync,
|
|
1056
|
+
rmSync,
|
|
1057
|
+
writeFileSync
|
|
1058
|
+
} from "node:fs";
|
|
1059
|
+
import { randomUUID } from "node:crypto";
|
|
1060
|
+
import { dirname } from "node:path";
|
|
1061
|
+
var PRIVATE_DIRECTORY_MODE = 448;
|
|
1062
|
+
var PRIVATE_FILE_MODE = 384;
|
|
1063
|
+
var NO_FOLLOW = constants.O_NOFOLLOW ?? 0;
|
|
1064
|
+
function isPosix() {
|
|
1065
|
+
return process.platform !== "win32";
|
|
1066
|
+
}
|
|
1067
|
+
function assertOwned(uid, path3) {
|
|
1068
|
+
if (!isPosix() || process.getuid === void 0) return;
|
|
1069
|
+
if (uid !== process.getuid()) {
|
|
1070
|
+
throw new Error(`Refusing sensitive path not owned by the current user: ${path3}`);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function ensurePrivateDirectorySync(path3) {
|
|
1074
|
+
try {
|
|
1075
|
+
const current = lstatSync(path3);
|
|
1076
|
+
if (current.isSymbolicLink() || !current.isDirectory()) {
|
|
1077
|
+
throw new Error(`Refusing non-directory sensitive state path: ${path3}`);
|
|
1078
|
+
}
|
|
1079
|
+
assertOwned(current.uid, path3);
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
if (error.code !== "ENOENT") throw error;
|
|
1082
|
+
mkdirSync(path3, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
|
|
1083
|
+
}
|
|
1084
|
+
if (isPosix()) chmodSync(path3, PRIVATE_DIRECTORY_MODE);
|
|
1085
|
+
}
|
|
1086
|
+
function validateExistingFile(path3) {
|
|
1087
|
+
try {
|
|
1088
|
+
const current = lstatSync(path3);
|
|
1089
|
+
if (current.isSymbolicLink() || !current.isFile()) {
|
|
1090
|
+
throw new Error(`Refusing non-regular sensitive file: ${path3}`);
|
|
1091
|
+
}
|
|
1092
|
+
assertOwned(current.uid, path3);
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
if (error.code === "ENOENT") return;
|
|
1095
|
+
throw error;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function readPrivateFileSync(path3) {
|
|
1099
|
+
ensurePrivateDirectorySync(dirname(path3));
|
|
1100
|
+
try {
|
|
1101
|
+
validateExistingFile(path3);
|
|
1102
|
+
const fd = openSync(path3, constants.O_RDONLY | NO_FOLLOW);
|
|
1103
|
+
try {
|
|
1104
|
+
const current = fstatSync(fd);
|
|
1105
|
+
if (!current.isFile()) throw new Error(`Refusing non-regular sensitive file: ${path3}`);
|
|
1106
|
+
assertOwned(current.uid, path3);
|
|
1107
|
+
if (isPosix()) fchmodSync(fd, PRIVATE_FILE_MODE);
|
|
1108
|
+
return readFileSync(fd, "utf8");
|
|
1109
|
+
} finally {
|
|
1110
|
+
closeSync(fd);
|
|
1111
|
+
}
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
if (error.code === "ENOENT") return null;
|
|
1114
|
+
throw error;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
function atomicWritePrivateFileSync(path3, content) {
|
|
1118
|
+
const parent = dirname(path3);
|
|
1119
|
+
ensurePrivateDirectorySync(parent);
|
|
1120
|
+
validateExistingFile(path3);
|
|
1121
|
+
const temporary = `${path3}.tmp-${process.pid}-${randomUUID()}`;
|
|
1122
|
+
let fd;
|
|
1123
|
+
try {
|
|
1124
|
+
fd = openSync(
|
|
1125
|
+
temporary,
|
|
1126
|
+
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | NO_FOLLOW,
|
|
1127
|
+
PRIVATE_FILE_MODE
|
|
1128
|
+
);
|
|
1129
|
+
writeFileSync(fd, content, "utf8");
|
|
1130
|
+
if (isPosix()) fchmodSync(fd, PRIVATE_FILE_MODE);
|
|
1131
|
+
closeSync(fd);
|
|
1132
|
+
fd = void 0;
|
|
1133
|
+
renameSync(temporary, path3);
|
|
1134
|
+
if (isPosix()) chmodSync(path3, PRIVATE_FILE_MODE);
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
if (fd !== void 0) closeSync(fd);
|
|
1137
|
+
try {
|
|
1138
|
+
rmSync(temporary, { force: true });
|
|
1139
|
+
} catch {
|
|
1140
|
+
}
|
|
1141
|
+
throw error;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
function appendPrivateFileSync(path3, content) {
|
|
1145
|
+
ensurePrivateDirectorySync(dirname(path3));
|
|
1146
|
+
validateExistingFile(path3);
|
|
1147
|
+
const fd = openSync(
|
|
1148
|
+
path3,
|
|
1149
|
+
constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | NO_FOLLOW,
|
|
1150
|
+
PRIVATE_FILE_MODE
|
|
1151
|
+
);
|
|
1152
|
+
try {
|
|
1153
|
+
const current = fstatSync(fd);
|
|
1154
|
+
if (!current.isFile()) throw new Error(`Refusing non-regular sensitive file: ${path3}`);
|
|
1155
|
+
assertOwned(current.uid, path3);
|
|
1156
|
+
if (isPosix()) fchmodSync(fd, PRIVATE_FILE_MODE);
|
|
1157
|
+
writeFileSync(fd, content, "utf8");
|
|
1158
|
+
} finally {
|
|
1159
|
+
closeSync(fd);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
920
1163
|
// src/conductor/transcript-store/store.ts
|
|
921
1164
|
function memoryBackend() {
|
|
922
1165
|
const files = /* @__PURE__ */ new Map();
|
|
@@ -944,24 +1187,15 @@ function fsBackend(dir) {
|
|
|
944
1187
|
return {
|
|
945
1188
|
locate: (sessionId) => `${dir}/${sessionId}.ndjson`,
|
|
946
1189
|
read: async (sessionId) => {
|
|
947
|
-
|
|
948
|
-
try {
|
|
949
|
-
return await fs.readFile(await fileOf(sessionId), "utf8");
|
|
950
|
-
} catch {
|
|
951
|
-
return null;
|
|
952
|
-
}
|
|
1190
|
+
return readPrivateFileSync(await fileOf(sessionId));
|
|
953
1191
|
},
|
|
954
1192
|
append: async (sessionId, line) => {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
await fs.appendFile(await fileOf(sessionId), `${line}
|
|
958
|
-
`, "utf8");
|
|
1193
|
+
appendPrivateFileSync(await fileOf(sessionId), `${line}
|
|
1194
|
+
`);
|
|
959
1195
|
},
|
|
960
1196
|
rewrite: async (sessionId, text) => {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
await fs.writeFile(await fileOf(sessionId), text.length === 0 ? "" : `${text}
|
|
964
|
-
`, "utf8");
|
|
1197
|
+
atomicWritePrivateFileSync(await fileOf(sessionId), text.length === 0 ? "" : `${text}
|
|
1198
|
+
`);
|
|
965
1199
|
}
|
|
966
1200
|
};
|
|
967
1201
|
}
|
|
@@ -1479,7 +1713,7 @@ async function loadSpawn() {
|
|
|
1479
1713
|
return void 0;
|
|
1480
1714
|
}
|
|
1481
1715
|
}
|
|
1482
|
-
function captureOutput(
|
|
1716
|
+
function captureOutput(spawn3, bin, args, cwd, signal) {
|
|
1483
1717
|
return new Promise((resolve3) => {
|
|
1484
1718
|
let settled = false;
|
|
1485
1719
|
const finish = (value) => {
|
|
@@ -1490,7 +1724,7 @@ function captureOutput(spawn2, bin, args, cwd, signal) {
|
|
|
1490
1724
|
};
|
|
1491
1725
|
let child;
|
|
1492
1726
|
try {
|
|
1493
|
-
child =
|
|
1727
|
+
child = spawn3(bin, args, { cwd, shell: false });
|
|
1494
1728
|
} catch {
|
|
1495
1729
|
finish();
|
|
1496
1730
|
return;
|
|
@@ -1519,9 +1753,9 @@ function captureOutput(spawn2, bin, args, cwd, signal) {
|
|
|
1519
1753
|
});
|
|
1520
1754
|
}
|
|
1521
1755
|
async function runTscDiagnostics(cwd, signal) {
|
|
1522
|
-
const
|
|
1523
|
-
if (!
|
|
1524
|
-
const output = await captureOutput(
|
|
1756
|
+
const spawn3 = await loadSpawn();
|
|
1757
|
+
if (!spawn3 || signal?.aborted) return [];
|
|
1758
|
+
const output = await captureOutput(spawn3, path.join(cwd, "node_modules", ".bin", "tsc"), ["--noEmit", "--pretty", "false"], cwd, signal);
|
|
1525
1759
|
return output === void 0 ? [] : parseTscOutput(output, cwd);
|
|
1526
1760
|
}
|
|
1527
1761
|
function parseTscOutput(output, cwd) {
|
|
@@ -1541,9 +1775,9 @@ function parseTscOutput(output, cwd) {
|
|
|
1541
1775
|
}
|
|
1542
1776
|
async function runEslintDiagnostics(files, cwd, signal) {
|
|
1543
1777
|
if (files.length === 0) return [];
|
|
1544
|
-
const
|
|
1545
|
-
if (!
|
|
1546
|
-
const output = await captureOutput(
|
|
1778
|
+
const spawn3 = await loadSpawn();
|
|
1779
|
+
if (!spawn3 || signal?.aborted) return [];
|
|
1780
|
+
const output = await captureOutput(spawn3, path.join(cwd, "node_modules", ".bin", "eslint"), ["--format", "json", ...files], cwd, signal);
|
|
1547
1781
|
return output === void 0 ? [] : parseEslintOutput(output, cwd);
|
|
1548
1782
|
}
|
|
1549
1783
|
function parseEslintOutput(output, cwd) {
|
|
@@ -1808,6 +2042,8 @@ var SessionConductorImpl = class {
|
|
|
1808
2042
|
#matcher;
|
|
1809
2043
|
#state;
|
|
1810
2044
|
#busy = false;
|
|
2045
|
+
#activeWork;
|
|
2046
|
+
#shellAbort;
|
|
1811
2047
|
#draining = false;
|
|
1812
2048
|
#queue = [];
|
|
1813
2049
|
#boundModel;
|
|
@@ -1867,6 +2103,7 @@ var SessionConductorImpl = class {
|
|
|
1867
2103
|
abort() {
|
|
1868
2104
|
this.clearQueue();
|
|
1869
2105
|
this.#condenseAbort?.abort();
|
|
2106
|
+
this.#shellAbort?.abort();
|
|
1870
2107
|
this.agent.abort();
|
|
1871
2108
|
const fault = conductorFault("aborted", "turn aborted by caller");
|
|
1872
2109
|
this.#dispatch({ type: "fault", fault });
|
|
@@ -1879,13 +2116,22 @@ var SessionConductorImpl = class {
|
|
|
1879
2116
|
return this.#state;
|
|
1880
2117
|
}
|
|
1881
2118
|
this.#busy = true;
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2119
|
+
const work = (async () => {
|
|
2120
|
+
try {
|
|
2121
|
+
await this.#runTurn(input);
|
|
2122
|
+
await this.#drainQueue();
|
|
2123
|
+
return this.#state;
|
|
2124
|
+
} finally {
|
|
2125
|
+
this.#busy = false;
|
|
2126
|
+
}
|
|
2127
|
+
})();
|
|
2128
|
+
this.#activeWork = work;
|
|
2129
|
+
return work;
|
|
2130
|
+
}
|
|
2131
|
+
async #stopWork() {
|
|
2132
|
+
if (!this.#busy) return;
|
|
2133
|
+
this.abort();
|
|
2134
|
+
await this.#activeWork;
|
|
1889
2135
|
}
|
|
1890
2136
|
async #drainQueue() {
|
|
1891
2137
|
if (this.#draining) return;
|
|
@@ -1974,8 +2220,16 @@ var SessionConductorImpl = class {
|
|
|
1974
2220
|
}
|
|
1975
2221
|
this.#applyModel(card);
|
|
1976
2222
|
}
|
|
1977
|
-
cycleModel(id) {
|
|
1978
|
-
|
|
2223
|
+
cycleModel(id, direction = 1) {
|
|
2224
|
+
if (id.trim()) {
|
|
2225
|
+
this.selectModel(id);
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2228
|
+
const cards = this.availableModels();
|
|
2229
|
+
if (!cards.length) return;
|
|
2230
|
+
const at = cards.findIndex((card) => card.id === this.#state.modelId);
|
|
2231
|
+
const index = at < 0 ? 0 : (at + direction + cards.length) % cards.length;
|
|
2232
|
+
this.selectModel(cards[index].id);
|
|
1979
2233
|
}
|
|
1980
2234
|
#applyModel(card) {
|
|
1981
2235
|
const model = withGatewayBaseUrl(card.model, this.#gatewayBaseUrls);
|
|
@@ -1990,28 +2244,36 @@ var SessionConductorImpl = class {
|
|
|
1990
2244
|
registerTools(tools) {
|
|
1991
2245
|
this.agent.setTools([...this.#deckTools, ...tools]);
|
|
1992
2246
|
}
|
|
1993
|
-
async condense() {
|
|
2247
|
+
async condense(instructions) {
|
|
1994
2248
|
if (this.#busy) return "busy";
|
|
1995
2249
|
this.#busy = true;
|
|
1996
2250
|
this.#condenseAbort = new AbortController();
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2251
|
+
const work = (async () => {
|
|
2252
|
+
try {
|
|
2253
|
+
const result = await this.#runCondense(true, this.#condenseAbort.signal, instructions);
|
|
2254
|
+
await this.#drainQueue();
|
|
2255
|
+
return result;
|
|
2256
|
+
} finally {
|
|
2257
|
+
this.#busy = false;
|
|
2258
|
+
this.#condenseAbort = void 0;
|
|
2259
|
+
}
|
|
2260
|
+
})();
|
|
2261
|
+
this.#activeWork = work;
|
|
2262
|
+
return work;
|
|
2005
2263
|
}
|
|
2006
2264
|
cancelCondense() {
|
|
2007
2265
|
this.#condenseAbort?.abort();
|
|
2008
2266
|
}
|
|
2009
|
-
async #runCondense(force, signal) {
|
|
2267
|
+
async #runCondense(force, signal, instructions) {
|
|
2010
2268
|
const before = [...this.agent.state.messages];
|
|
2011
2269
|
this.#dispatch({ type: "phase", phase: "condensing" });
|
|
2012
2270
|
try {
|
|
2013
|
-
const after = await (this.deps.condense ?? noopCondense)(before, { force, model: this.#boundModel, contextTokens: this.#state.contextTokens, signal });
|
|
2014
|
-
if (signal?.aborted)
|
|
2271
|
+
const after = await (this.deps.condense ?? noopCondense)(before, { force, model: this.#boundModel, contextTokens: this.#state.contextTokens, signal, instructions });
|
|
2272
|
+
if (signal?.aborted) {
|
|
2273
|
+
this.#dispatch({ type: "settled" });
|
|
2274
|
+
this.#hub.emit({ kind: "idle" });
|
|
2275
|
+
return "cancelled";
|
|
2276
|
+
}
|
|
2015
2277
|
if (after.length >= before.length) {
|
|
2016
2278
|
this.#dispatch({ type: "settled" });
|
|
2017
2279
|
return "nothing";
|
|
@@ -2038,10 +2300,12 @@ var SessionConductorImpl = class {
|
|
|
2038
2300
|
}
|
|
2039
2301
|
async #move(id, message) {
|
|
2040
2302
|
try {
|
|
2303
|
+
await this.#stopWork();
|
|
2041
2304
|
await this.store.branchAt(id);
|
|
2042
2305
|
this.agent.replaceMessages(this.store.messages());
|
|
2043
2306
|
this.#dispatch({ type: "head", head: this.store.head });
|
|
2044
2307
|
this.#dispatch({ type: "settled" });
|
|
2308
|
+
this.#hub.emit({ kind: "reset" });
|
|
2045
2309
|
this.#hub.emit({ kind: "idle" });
|
|
2046
2310
|
} catch (error) {
|
|
2047
2311
|
const fault = conductorFault("persistence", message, error);
|
|
@@ -2056,18 +2320,33 @@ var SessionConductorImpl = class {
|
|
|
2056
2320
|
return { canRestore: this.options.checkpoint?.hasSnapshot(id) ?? false };
|
|
2057
2321
|
}
|
|
2058
2322
|
async executeBash(command, opts = {}) {
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
${outcome.output}`.trimEnd(), timestamp: Date.now() }
|
|
2070
|
-
|
|
2323
|
+
if (this.#busy) throw new Error("A turn is running \u2014 stop it before running a shell command.");
|
|
2324
|
+
this.#busy = true;
|
|
2325
|
+
this.#shellAbort = new AbortController();
|
|
2326
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, this.#shellAbort.signal]) : this.#shellAbort.signal;
|
|
2327
|
+
const work = (async () => {
|
|
2328
|
+
try {
|
|
2329
|
+
this.#dispatch({ type: "phase", phase: "tooling" });
|
|
2330
|
+
const outcome = await runShell(command, this.#workspace, signal);
|
|
2331
|
+
if (!opts.excludeFromContext) {
|
|
2332
|
+
const message = { role: "user", content: `$ ${command}
|
|
2333
|
+
${outcome.output}`.trimEnd(), timestamp: Date.now() };
|
|
2334
|
+
const entry = await this.store.append(message, "note");
|
|
2335
|
+
this.agent.replaceMessages([...this.agent.state.messages, message]);
|
|
2336
|
+
this.#dispatch({ type: "head", head: this.store.head });
|
|
2337
|
+
this.#hub.emit({ kind: "persisted", entryId: entry.id });
|
|
2338
|
+
}
|
|
2339
|
+
this.#dispatch({ type: "settled" });
|
|
2340
|
+
this.#hub.emit({ kind: "idle" });
|
|
2341
|
+
await this.#drainQueue();
|
|
2342
|
+
return outcome;
|
|
2343
|
+
} finally {
|
|
2344
|
+
this.#busy = false;
|
|
2345
|
+
this.#shellAbort = void 0;
|
|
2346
|
+
}
|
|
2347
|
+
})();
|
|
2348
|
+
this.#activeWork = work;
|
|
2349
|
+
return work;
|
|
2071
2350
|
}
|
|
2072
2351
|
stats() {
|
|
2073
2352
|
const messages = this.agent.state.messages;
|
|
@@ -2137,6 +2416,7 @@ ${outcome.output}`.trimEnd(), timestamp: Date.now() }, "note");
|
|
|
2137
2416
|
this.#sessionName = name;
|
|
2138
2417
|
}
|
|
2139
2418
|
async resume(id) {
|
|
2419
|
+
await this.#stopWork();
|
|
2140
2420
|
if (!await this.store.load(id)) {
|
|
2141
2421
|
const fault = conductorFault("persistence", `no session "${id}" to resume`);
|
|
2142
2422
|
this.#dispatch({ type: "fault", fault });
|
|
@@ -2145,19 +2425,23 @@ ${outcome.output}`.trimEnd(), timestamp: Date.now() }, "note");
|
|
|
2145
2425
|
}
|
|
2146
2426
|
this.agent.replaceMessages(this.store.messages());
|
|
2147
2427
|
this.#state = { ...this.#state, head: this.store.head, usage: this.store.usage ?? createZeroUsage2(), phase: "idle" };
|
|
2428
|
+
this.#hub.emit({ kind: "reset" });
|
|
2148
2429
|
this.#hub.emit({ kind: "idle" });
|
|
2149
2430
|
}
|
|
2150
2431
|
async newSession() {
|
|
2151
|
-
this
|
|
2432
|
+
await this.#stopWork();
|
|
2152
2433
|
this.agent.replaceMessages([]);
|
|
2153
2434
|
this.store.startNewSession(newSessionId());
|
|
2154
2435
|
this.#queue.length = 0;
|
|
2155
2436
|
this.#sessionName = void 0;
|
|
2156
2437
|
this.#state = { phase: "idle", head: this.store.head, usage: createZeroUsage2(), contextTokens: 0, modelId: this.#state.modelId };
|
|
2438
|
+
this.#hub.emit({ kind: "reset" });
|
|
2157
2439
|
this.#hub.emit({ kind: "idle" });
|
|
2158
2440
|
}
|
|
2159
2441
|
#dispatch(action) {
|
|
2442
|
+
const phase = this.#state.phase;
|
|
2160
2443
|
this.#state = reduceState(this.#state, action);
|
|
2444
|
+
if (phase !== this.#state.phase) this.#hub.emit({ kind: "phase", phase: this.#state.phase });
|
|
2161
2445
|
}
|
|
2162
2446
|
};
|
|
2163
2447
|
|
|
@@ -2260,7 +2544,7 @@ var todoCard = {
|
|
|
2260
2544
|
};
|
|
2261
2545
|
|
|
2262
2546
|
// src/capability-deck/cards/bg-process-card.ts
|
|
2263
|
-
import { spawn } from "node:child_process";
|
|
2547
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2264
2548
|
import { Type as Type2 } from "@sinclair/typebox";
|
|
2265
2549
|
var MAX_BUFFERED_LINES = 2e3;
|
|
2266
2550
|
var DaemonTable = class {
|
|
@@ -2272,7 +2556,7 @@ var DaemonTable = class {
|
|
|
2272
2556
|
}
|
|
2273
2557
|
start(command) {
|
|
2274
2558
|
const id = `bg-${this.nextSeq++}`;
|
|
2275
|
-
const child =
|
|
2559
|
+
const child = spawn2(command, {
|
|
2276
2560
|
cwd: this.cwd,
|
|
2277
2561
|
shell: true,
|
|
2278
2562
|
stdio: ["ignore", "pipe", "pipe"],
|