aiden-runtime 4.5.0 → 4.6.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/README.md +17 -2
- package/dist/cli/v4/aidenCLI.js +207 -100
- package/dist/cli/v4/chatSession.js +120 -0
- package/dist/cli/v4/commands/_runtimeToggleHelpers.js +2 -0
- package/dist/cli/v4/commands/fanout.js +42 -59
- package/dist/cli/v4/commands/help.js +8 -0
- package/dist/cli/v4/commands/index.js +21 -1
- package/dist/cli/v4/commands/mcp.js +80 -54
- package/dist/cli/v4/commands/plannerGuard.js +53 -0
- package/dist/cli/v4/commands/recovery.js +122 -0
- package/dist/cli/v4/commands/runs.js +22 -2
- package/dist/cli/v4/commands/spawnPause.js +93 -0
- package/dist/cli/v4/commands/walkthrough.js +140 -0
- package/dist/cli/v4/daemonAgentBuilder.js +4 -1
- package/dist/cli/v4/defaultSoul.js +1 -1
- package/dist/cli/v4/onboarding/disclaimer.js +162 -0
- package/dist/cli/v4/onboarding/loading.js +208 -0
- package/dist/cli/v4/onboarding/providerPicker.js +126 -0
- package/dist/cli/v4/onboarding/successScreen.js +68 -0
- package/dist/cli/v4/repl/firstRunHint.js +107 -0
- package/dist/cli/v4/setupWizard.js +201 -31
- package/dist/core/v4/aidenAgent.js +219 -1
- package/dist/core/v4/daemon/bootstrap.js +47 -0
- package/dist/core/v4/daemon/db/migrations.js +66 -0
- package/dist/core/v4/daemon/runStore.js +33 -3
- package/dist/core/v4/providerFallback.js +35 -2
- package/dist/core/v4/providers/modelFetch.js +179 -0
- package/dist/core/v4/providers/probe.js +275 -0
- package/dist/core/v4/runtimeToggles.js +30 -3
- package/dist/core/v4/selfimprovement/recoveryStore.js +307 -0
- package/dist/core/v4/selfimprovement/signatureBuilder.js +158 -0
- package/dist/core/v4/subagent/childBuilder.js +391 -0
- package/dist/core/v4/subagent/fanout.js +75 -51
- package/dist/core/v4/subagent/spawnPause.js +191 -0
- package/dist/core/v4/subagent/spawnSubAgent.js +310 -0
- package/dist/core/v4/toolRegistry.js +19 -3
- package/dist/core/v4/ui/banner.js +133 -0
- package/dist/core/v4/ui/theme.js +164 -0
- package/dist/core/version.js +1 -1
- package/dist/moat/plannerGuard.js +29 -0
- package/dist/providers/v4/anthropicAdapter.js +31 -3
- package/dist/providers/v4/chatCompletionsAdapter.js +26 -3
- package/dist/providers/v4/codexResponsesAdapter.js +25 -2
- package/dist/providers/v4/ollamaPromptToolsAdapter.js +57 -2
- package/dist/tools/v4/index.js +17 -3
- package/dist/tools/v4/skills/lookupToolSchema.js +6 -1
- package/dist/tools/v4/subagent/spawnSubAgentTool.js +334 -0
- package/dist/tools/v4/subagent/subagentFanout.js +53 -1
- package/dist/tools/v4/ui/_uiSmokeTool.js +60 -0
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -10,14 +10,15 @@
|
|
|
10
10
|
|
|
11
11
|
Autonomous AI Engine — local-first, Windows-native, yours to own
|
|
12
12
|
|
|
13
|
-
74 skills ·
|
|
13
|
+
74 skills · 60 tools · 19 providers · 9 channels · AGPL-3.0
|
|
14
14
|
|
|
15
15
|
Windows · Linux · WSL · macOS (API Mode)
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
|
-
<a href="https://github.com/taracodlabs/aiden/releases/latest"><img src="https://img.shields.io/badge/version-v4.
|
|
19
|
+
<a href="https://github.com/taracodlabs/aiden/releases/latest"><img src="https://img.shields.io/badge/version-v4.6.0-f97316?style=for-the-badge" alt="v4.6.0" /></a>
|
|
20
20
|
<a href="https://www.npmjs.com/package/aiden-runtime"><img src="https://img.shields.io/npm/v/aiden-runtime?color=f97316&label=npm&style=for-the-badge" alt="npm" /></a>
|
|
21
|
+
<a href="https://www.npmjs.com/package/aiden-runtime"><img src="https://img.shields.io/npm/dm/aiden-runtime?color=f97316&label=downloads&style=for-the-badge" alt="npm downloads" /></a>
|
|
21
22
|
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0-orange?style=for-the-badge" alt="License: AGPL-3.0" /></a>
|
|
22
23
|
<a href="https://discord.gg/gMZ3hUnQTm"><img src="https://img.shields.io/badge/chat-discord-7289da?logo=discord&logoColor=white&style=for-the-badge" alt="Discord" /></a>
|
|
23
24
|
<a href="https://github.com/taracodlabs/aiden/stargazers"><img src="https://img.shields.io/github/stars/taracodlabs/aiden?style=for-the-badge&color=f9d71c" alt="Stars" /></a>
|
|
@@ -33,6 +34,20 @@ Windows · Linux · WSL · macOS (API Mode)
|
|
|
33
34
|
|
|
34
35
|
---
|
|
35
36
|
|
|
37
|
+
## What's new in v4.6
|
|
38
|
+
|
|
39
|
+
Aiden now spawns workers and learns from itself.
|
|
40
|
+
|
|
41
|
+
- **Sub-agents.** `spawn_sub_agent` runs a focused child with an isolated context + intersected toolset; `subagent_fanout` runs N children in parallel (ensemble or partition) with merge strategies (`all` / `vote` / `pick-best` / `combine`) and provider rotation across configured fallback slots.
|
|
42
|
+
- **Operator kill-switch.** `/spawn-pause on|off|status` blocks new sub-agent spawning while in-flight children continue. Marker file at `~/.aiden/spawn.paused` so the state survives restart and is shared across REPL, daemon, and MCP runtimes. Optional reason field captured in the typed `SUBAGENT_SPAWN_PAUSED` error envelope.
|
|
43
|
+
- **Self-improvement loop foundation.** TCE classifications + recoveries persist to two new SQLite tables (`failure_signatures`, `recovery_reports`); `/recovery list|show|clear` surfaces recurring failure patterns across sessions.
|
|
44
|
+
- **REPL parent-run lineage.** Each REPL turn writes its own `runs` row; sub-agent children link back via `spawned_from_run_id`. `aiden runs list` hides children by default and shows a `(N children, M OK)` badge per parent; `--include-children` flips to flat view.
|
|
45
|
+
- **PlannerGuard opt-in.** The keyword-based per-turn tool narrower is OFF by default in v4.6 (modern models pick well from the full catalog). Enable via `/planner-guard on` or `AIDEN_PLANNER_GUARD=1` for smaller local models.
|
|
46
|
+
|
|
47
|
+
Phase 2 also fixed an MCP-mode `subagent_fanout` regression that had silently broken in the v4.5 refactor.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
36
51
|
## What's new in v4.5
|
|
37
52
|
|
|
38
53
|
Aiden now wakes up by itself.
|
package/dist/cli/v4/aidenCLI.js
CHANGED
|
@@ -97,6 +97,13 @@ const sessionManager_1 = require("../../core/v4/sessionManager");
|
|
|
97
97
|
const toolRegistry_1 = require("../../core/v4/toolRegistry");
|
|
98
98
|
const skillLoader_1 = require("../../core/v4/skillLoader");
|
|
99
99
|
const index_1 = require("../../tools/v4/index");
|
|
100
|
+
// v4.6 Phase 1 — spawn_sub_agent: always-on runStore + LLM-callable tool.
|
|
101
|
+
const node_crypto_1 = require("node:crypto");
|
|
102
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
103
|
+
const daemonConfig_1 = require("../../core/v4/daemon/daemonConfig");
|
|
104
|
+
const connection_1 = require("../../core/v4/daemon/db/connection");
|
|
105
|
+
const runStore_1 = require("../../core/v4/daemon/runStore");
|
|
106
|
+
const spawnSubAgentTool_1 = require("../../tools/v4/subagent/spawnSubAgentTool");
|
|
100
107
|
const skillCommands_1 = require("../../core/v4/skillCommands");
|
|
101
108
|
const aidenAgent_1 = require("../../core/v4/aidenAgent");
|
|
102
109
|
const promptBuilder_1 = require("../../core/v4/promptBuilder");
|
|
@@ -502,6 +509,7 @@ async function main(argv, opts = {}) {
|
|
|
502
509
|
.option('--source <src>', 'list: filter by trigger source (file/webhook/email/schedule/manual)')
|
|
503
510
|
.option('--status <s>', 'list: filter by status (queued/running/completed/failed/cancelled/interrupted)')
|
|
504
511
|
.option('--trigger <prefix>', 'list: sessionId prefix (e.g. "trigger:file:<id>:")')
|
|
512
|
+
.option('--include-children', 'list: include sub-agent children (default: top-level only, with per-parent badge)')
|
|
505
513
|
.action(async (action, posArgs, cmdObj) => {
|
|
506
514
|
const { runRunsSubcommand } = await Promise.resolve().then(() => __importStar(require('./commands/runs')));
|
|
507
515
|
const code = await runRunsSubcommand(action, posArgs ?? [], cmdObj, {
|
|
@@ -623,6 +631,60 @@ async function main(argv, opts = {}) {
|
|
|
623
631
|
async function buildAgentRuntime(cliOpts, opts) {
|
|
624
632
|
const paths = opts.pathsOverride ?? (0, paths_1.resolveAidenPaths)();
|
|
625
633
|
await (0, paths_1.ensureAidenDirsExist)(paths);
|
|
634
|
+
// ── v4.6 Phase 1 — always-on runStore for spawn_sub_agent ──────────────
|
|
635
|
+
//
|
|
636
|
+
// The spawn_sub_agent primitive persists each child run to the runs
|
|
637
|
+
// table via spawned_from_run_id FK. The REPL needs a runStore handle
|
|
638
|
+
// regardless of whether AIDEN_DAEMON=1 or not. SQLite WAL mode
|
|
639
|
+
// (enabled in openDaemonDb) allows REPL + daemon to coexist on the
|
|
640
|
+
// same file without lock contention. The connection.ts module caches
|
|
641
|
+
// per-path, so when daemon foundation has already opened the DB this
|
|
642
|
+
// call returns the same handle. Migration runner is idempotent on
|
|
643
|
+
// already-current schemas.
|
|
644
|
+
const replInstanceId = `repl-${(0, node_crypto_1.randomUUID)().slice(0, 8)}`;
|
|
645
|
+
const replDb = (0, connection_1.openDaemonDb)((0, daemonConfig_1.daemonDbPath)(paths.root));
|
|
646
|
+
// Seed the REPL's daemon_instances row so the FK on runs.instance_id
|
|
647
|
+
// is satisfied. Idempotent under INSERT OR IGNORE — multiple REPL
|
|
648
|
+
// launches reusing the same instance_id (rare with random UUID
|
|
649
|
+
// suffix) silently no-op.
|
|
650
|
+
replDb.prepare(`INSERT OR IGNORE INTO daemon_instances
|
|
651
|
+
(instance_id, pid, hostname, started_at, last_heartbeat, version)
|
|
652
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(replInstanceId, process.pid, node_os_1.default.hostname(), Date.now(), Date.now(), version_1.VERSION);
|
|
653
|
+
const replRunStore = (0, runStore_1.createRunStore)({ db: replDb });
|
|
654
|
+
// v4.6 Phase 3A — operator kill-switch for sub-agent spawning.
|
|
655
|
+
// Initialised as early as possible so any subsequent tool wiring
|
|
656
|
+
// sees the singleton. The marker file lives at
|
|
657
|
+
// `<paths.root>/spawn.paused` and is shared across REPL + daemon
|
|
658
|
+
// + MCP for cross-process coordination. The startup probe (warn
|
|
659
|
+
// operator that pause is active from a prior session) fires
|
|
660
|
+
// later, once `bootLogger` is available — see the
|
|
661
|
+
// `spawnPauseBootStatus` block below.
|
|
662
|
+
const { initSpawnPause } = await Promise.resolve().then(() => __importStar(require('../../core/v4/subagent/spawnPause')));
|
|
663
|
+
const spawnPauseState = initSpawnPause({ aidenHome: paths.root });
|
|
664
|
+
// v4.6 Phase 3b — self-improvement loop. Initialise the durable
|
|
665
|
+
// failure-ledger / recovery-report store against the same
|
|
666
|
+
// daemon.db handle the runStore uses. WAL coexistence: REPL +
|
|
667
|
+
// daemon + MCP all share the same connection-cached handle, so
|
|
668
|
+
// any writes from one runtime are visible to the others. The
|
|
669
|
+
// TCE write-through path inside the agent loop reads through the
|
|
670
|
+
// module-level singleton; initialising here makes spawnSubAgent
|
|
671
|
+
// and daemon-fired turns observe the same persistence.
|
|
672
|
+
const { initRecoveryStore } = await Promise.resolve().then(() => __importStar(require('../../core/v4/selfimprovement/recoveryStore')));
|
|
673
|
+
initRecoveryStore({ db: replDb });
|
|
674
|
+
const spawnPauseBootStatus = spawnPauseState.isPaused()
|
|
675
|
+
? spawnPauseState.status()
|
|
676
|
+
: null;
|
|
677
|
+
// v4.6 Phase 2Q-B — mutable holder for the REPL's current parent
|
|
678
|
+
// run id. ChatSession's `runAgentTurn` writes a row before each
|
|
679
|
+
// turn dispatches and stores the id here (and clears it on
|
|
680
|
+
// completion). The spawn / fanout tool factories below read it
|
|
681
|
+
// through `resolveParentRunId` / `resolveParentSessionId`
|
|
682
|
+
// callbacks so any child spawned mid-turn is linked back to the
|
|
683
|
+
// live REPL parent row via `runs.spawned_from_run_id`.
|
|
684
|
+
const replParentRunRef = {
|
|
685
|
+
runId: null,
|
|
686
|
+
sessionId: null,
|
|
687
|
+
};
|
|
626
688
|
// Phase 16c.2: load `paths.envFile` (the aiden-managed `.env` that
|
|
627
689
|
// `setupWizard.ts::upsertEnvVar` writes to) into `process.env` BEFORE
|
|
628
690
|
// any provider resolution. The bug: setup wrote keys to this file but
|
|
@@ -747,7 +809,28 @@ async function buildAgentRuntime(cliOpts, opts) {
|
|
|
747
809
|
process.stdout.write(`\n${(0, providerDetection_1.summarizeDetection)(detection)}\n`);
|
|
748
810
|
process.stdout.write('config.yaml is empty — let\'s pick a provider that matches.\n');
|
|
749
811
|
}
|
|
750
|
-
|
|
812
|
+
// ONB1-WIRE — disclaimer + loading screens land BEFORE the wizard.
|
|
813
|
+
// Only inside this TTY-guarded branch (the non-TTY branch at the
|
|
814
|
+
// outer else already bails into explore mode). The detection
|
|
815
|
+
// summary above gives the user context for what was found; the
|
|
816
|
+
// disclaimer then asks for explicit consent before we walk them
|
|
817
|
+
// through setup. Declining exits 0 — the user chose not to
|
|
818
|
+
// continue, that's not an error.
|
|
819
|
+
//
|
|
820
|
+
// Lazy-required so the test harness paths that stub the wizard
|
|
821
|
+
// don't pay the load cost.
|
|
822
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
823
|
+
const { showDisclaimer } = require('./onboarding/disclaimer');
|
|
824
|
+
const disc = await showDisclaimer();
|
|
825
|
+
if (!disc.ok) {
|
|
826
|
+
// User typed 'n' / 'no'. The disclaimer already printed the
|
|
827
|
+
// friendly goodbye line; just exit cleanly.
|
|
828
|
+
process.exit(0);
|
|
829
|
+
}
|
|
830
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
831
|
+
const { runLoadingSequence, defaultLoadingSteps } = require('./onboarding/loading');
|
|
832
|
+
await runLoadingSequence(defaultLoadingSteps(paths));
|
|
833
|
+
process.stdout.write('\n');
|
|
751
834
|
const result = await (0, setupWizard_1.runSetupWizard)({ paths });
|
|
752
835
|
// Phase 30.2.1: three exit states.
|
|
753
836
|
if (result.status === 'exited') {
|
|
@@ -1238,7 +1321,11 @@ async function buildAgentRuntime(cliOpts, opts) {
|
|
|
1238
1321
|
// ── Build agent with all moat layers attached ────────────────────────
|
|
1239
1322
|
const agent = new aidenAgent_1.AidenAgent({
|
|
1240
1323
|
provider: adapter,
|
|
1241
|
-
tools
|
|
1324
|
+
// v4.6 Phase 1 — 'repl' context filter excludes tools tagged
|
|
1325
|
+
// daemon-only (none today) and INCLUDES tools tagged repl-only
|
|
1326
|
+
// (e.g. spawn_sub_agent, registered after this line). Tools with
|
|
1327
|
+
// no `contexts` field default to visible in both contexts.
|
|
1328
|
+
tools: toolRegistry.getSchemas(undefined, 'repl'),
|
|
1242
1329
|
toolExecutor,
|
|
1243
1330
|
maxTurns: config.getValue('agent.max_turns', 90),
|
|
1244
1331
|
auxiliaryClient,
|
|
@@ -1426,6 +1513,21 @@ async function buildAgentRuntime(cliOpts, opts) {
|
|
|
1426
1513
|
// Wire the gateway singleton's logger BEFORE registering its processor
|
|
1427
1514
|
// so register / unregister channel events are scoped correctly.
|
|
1428
1515
|
gateway_1.gateway.attachLogger(bootLogger.child('gateway'));
|
|
1516
|
+
// v4.6 Phase 3A — startup probe for the spawn-pause kill-switch.
|
|
1517
|
+
// The state was initialised early (line ~740) before tool wiring.
|
|
1518
|
+
// Now that bootLogger exists, emit a visible warning so an
|
|
1519
|
+
// operator who forgot they paused in a prior session learns
|
|
1520
|
+
// immediately rather than puzzling at silent rejected fanouts.
|
|
1521
|
+
if (spawnPauseBootStatus) {
|
|
1522
|
+
const s = spawnPauseBootStatus;
|
|
1523
|
+
const reasonSuffix = s.reason ? ` (reason: ${s.reason})` : '';
|
|
1524
|
+
bootLogger.warn(`spawn_sub_agent / subagent_fanout are PAUSED${reasonSuffix}. ` +
|
|
1525
|
+
'Run /spawn-pause off to resume.', {
|
|
1526
|
+
pausedAt: s.pausedAt ?? null,
|
|
1527
|
+
pausedBy: s.pausedBy ?? null,
|
|
1528
|
+
durationMs: s.durationMs ?? null,
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1429
1531
|
// ── Phase v4.1-subagent.1 — replace subagent_fanout stub with wired version
|
|
1430
1532
|
//
|
|
1431
1533
|
// tools/v4/index.ts registers a stub at boot so the schema is visible
|
|
@@ -1442,10 +1544,45 @@ async function buildAgentRuntime(cliOpts, opts) {
|
|
|
1442
1544
|
// shared subsystems (registry, skillLoader, paths, memoryManager,
|
|
1443
1545
|
// promptBuilder, promptBuilderOptions) are read-only and pass by
|
|
1444
1546
|
// reference.
|
|
1547
|
+
// v4.6 Phase 2Q-A — `runFanout` routes each child through
|
|
1548
|
+
// `spawnSubAgent`. `spawnDeps` mirrors the deps the
|
|
1549
|
+
// `makeSpawnSubAgentTool` factory accepts (see registration below
|
|
1550
|
+
// this block). The legacy per-call `runChild` closure that lived
|
|
1551
|
+
// here pre-2R has been deleted; the primitive owns child
|
|
1552
|
+
// construction now.
|
|
1445
1553
|
toolRegistry.register((0, index_1.makeSubagentFanoutTool)({
|
|
1446
1554
|
logger: bootLogger.child('subagent'),
|
|
1447
1555
|
resolveActiveModel: () => ({ providerId, modelId }),
|
|
1448
1556
|
aggregatorAdapter: adapter,
|
|
1557
|
+
spawnDeps: {
|
|
1558
|
+
toolRegistry,
|
|
1559
|
+
parentToolContext: {
|
|
1560
|
+
cwd: process.cwd(),
|
|
1561
|
+
paths,
|
|
1562
|
+
sessions: sessionManager,
|
|
1563
|
+
memory: memoryManager,
|
|
1564
|
+
memoryGuard,
|
|
1565
|
+
ssrfProtection,
|
|
1566
|
+
tirithScanner,
|
|
1567
|
+
skillLoader,
|
|
1568
|
+
},
|
|
1569
|
+
parentProvider: adapter,
|
|
1570
|
+
parentProviderId: providerId,
|
|
1571
|
+
parentModelId: modelId,
|
|
1572
|
+
resolveVerifiedFlag,
|
|
1573
|
+
resolveToolset,
|
|
1574
|
+
resolveMutates,
|
|
1575
|
+
runStore: replRunStore,
|
|
1576
|
+
instanceId: replInstanceId,
|
|
1577
|
+
logger: bootLogger.child('subagent'),
|
|
1578
|
+
},
|
|
1579
|
+
// v4.6 Phase 2Q-B — REPL parent-run wiring. Reads the shared
|
|
1580
|
+
// `replParentRunRef` mutated by `ChatSession.runAgentTurn` so
|
|
1581
|
+
// fanout children get `spawned_from_run_id` populated. Returns
|
|
1582
|
+
// undefined between turns (ref cleared post-turn), matching the
|
|
1583
|
+
// pre-2Q-B behaviour for slash-command-triggered spawns.
|
|
1584
|
+
resolveParentRunId: () => replParentRunRef.runId ?? undefined,
|
|
1585
|
+
resolveParentSessionId: () => replParentRunRef.sessionId ?? undefined,
|
|
1449
1586
|
resolveProviders: () => {
|
|
1450
1587
|
// When the parent uses FallbackAdapter, expose every key-present
|
|
1451
1588
|
// slot's (providerId, modelId) so rotation can spread children
|
|
@@ -1466,110 +1603,68 @@ async function buildAgentRuntime(cliOpts, opts) {
|
|
|
1466
1603
|
}
|
|
1467
1604
|
return [{ providerId, modelId }];
|
|
1468
1605
|
},
|
|
1469
|
-
runChild: async (childOpts) => {
|
|
1470
|
-
// Per-child context: paths / skillLoader / memoryManager / processes
|
|
1471
|
-
// are SAFE to share (read-only or per-call by design). The approval
|
|
1472
|
-
// engine is intentionally OMITTED — N children competing for one
|
|
1473
|
-
// stdin REPL would deadlock.
|
|
1474
|
-
const childCtx = {
|
|
1475
|
-
cwd: process.cwd(),
|
|
1476
|
-
paths,
|
|
1477
|
-
sessions: sessionManager,
|
|
1478
|
-
memory: memoryManager,
|
|
1479
|
-
skillLoader,
|
|
1480
|
-
// approvalEngine, ssrfProtection, tirithScanner, memoryGuard:
|
|
1481
|
-
// SSRF + Tirith would be safe to share but adding them now
|
|
1482
|
-
// expands the per-child surface; keep lean for v4.1-subagent.1
|
|
1483
|
-
// and revisit when fanout actually exercises network or shell
|
|
1484
|
-
// tools (gated by ALLOW_DESTRUCTIVE).
|
|
1485
|
-
};
|
|
1486
|
-
// Filter the tool surface. Default-safe: read-only tools only.
|
|
1487
|
-
// AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE=1 mirrors the MCP env from
|
|
1488
|
-
// v4.1-mcp — predictable, env-driven.
|
|
1489
|
-
const allowDestructive = process.env.AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE === '1' ||
|
|
1490
|
-
process.env.AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE === 'true';
|
|
1491
|
-
const childToolNames = [];
|
|
1492
|
-
for (const name of toolRegistry.list()) {
|
|
1493
|
-
const h = toolRegistry.get(name);
|
|
1494
|
-
if (!h)
|
|
1495
|
-
continue;
|
|
1496
|
-
if (h.mutates && !allowDestructive)
|
|
1497
|
-
continue;
|
|
1498
|
-
// Avoid recursive fanout this phase — children cannot spawn
|
|
1499
|
-
// their own children. Recursion was capped at depth 1 by
|
|
1500
|
-
// default in prior multi-agent systems for the same reason;
|
|
1501
|
-
// v3 starved nested spawns.
|
|
1502
|
-
if (name === 'subagent_fanout')
|
|
1503
|
-
continue;
|
|
1504
|
-
childToolNames.push(name);
|
|
1505
|
-
}
|
|
1506
|
-
const childExecutor = toolRegistry.buildExecutor(childCtx);
|
|
1507
|
-
const childTools = childToolNames
|
|
1508
|
-
.map((n) => toolRegistry.get(n)?.schema)
|
|
1509
|
-
.filter((s) => !!s);
|
|
1510
|
-
// Provider isolation: clone the FallbackAdapter so per-child
|
|
1511
|
-
// rate-limit state doesn't pollute the parent or siblings.
|
|
1512
|
-
// Non-Fallback adapters are stateless by spec (providers/v4/
|
|
1513
|
-
// types.ts:190) so direct reuse is safe.
|
|
1514
|
-
const childProvider = adapter instanceof providerFallback_1.FallbackAdapter
|
|
1515
|
-
? adapter.clone()
|
|
1516
|
-
: adapter;
|
|
1517
|
-
// Build per-child AidenAgent. Skip the moat layers (PlannerGuard,
|
|
1518
|
-
// HonestyEnforcement, SkillTeacher, SkillEnforcementTracker) —
|
|
1519
|
-
// they're parent-loop concerns and add cost without value at the
|
|
1520
|
-
// child scale. Skip promptBuilder too: children get a SHORT
|
|
1521
|
-
// system prompt (brief identity + role) instead of the parent's
|
|
1522
|
-
// full SOUL.md + 72-skills inventory + memory snapshot. The
|
|
1523
|
-
// tradeoff is deliberate — children answer the GOAL, not "be
|
|
1524
|
-
// Aiden". With the full prompt, trivial queries take 30s+ for
|
|
1525
|
-
// children to generate verbose self-introductions; the lean
|
|
1526
|
-
// child prompt brings n=2 trivial fanouts under 12s. Parent
|
|
1527
|
-
// should pass any context children genuinely need via the
|
|
1528
|
-
// `query` / `tasks[].context` argument.
|
|
1529
|
-
const child = new aidenAgent_1.AidenAgent({
|
|
1530
|
-
provider: childProvider,
|
|
1531
|
-
tools: childTools,
|
|
1532
|
-
toolExecutor: childExecutor,
|
|
1533
|
-
maxTurns: childOpts.maxIterations,
|
|
1534
|
-
providerId: childOpts.provider.providerId,
|
|
1535
|
-
modelId: childOpts.provider.modelId,
|
|
1536
|
-
// No promptBuilder — childSystemPrompt prepended manually below.
|
|
1537
|
-
// No fallback strategy — child failures bubble up to the
|
|
1538
|
-
// orchestrator, which surfaces them in the result envelope.
|
|
1539
|
-
});
|
|
1540
|
-
// Honour the abort signal — if the parent aborts mid-call (or the
|
|
1541
|
-
// per-child timeout fires), short-circuit before dispatching to
|
|
1542
|
-
// the provider. AidenAgent doesn't take an AbortSignal directly;
|
|
1543
|
-
// the AbortController plumbing through fetch is the
|
|
1544
|
-
// v4.1-subagent.2 / v4.2 hardening pass. Pre-check here for the
|
|
1545
|
-
// synchronous path.
|
|
1546
|
-
if (childOpts.signal.aborted) {
|
|
1547
|
-
throw new Error('aborted before dispatch');
|
|
1548
|
-
}
|
|
1549
|
-
// Brief, role-aware system prompt — drops 5KB+ of Aiden identity
|
|
1550
|
-
// boilerplate that would otherwise inflate every child to 30s+
|
|
1551
|
-
// wall-clock for a trivial query. The parent agent retains the
|
|
1552
|
-
// full prompt when it's the orchestrator; children answer the
|
|
1553
|
-
// goal directly.
|
|
1554
|
-
const roleLine = childOpts.role
|
|
1555
|
-
? `Role: ${childOpts.role}. `
|
|
1556
|
-
: '';
|
|
1557
|
-
const childSystemPrompt = `You are one of ${childOpts.index >= 0 ? 'N' : '?'} parallel subagents. ` +
|
|
1558
|
-
`${roleLine}Answer the user's request concisely. Use available tools when ` +
|
|
1559
|
-
`the answer requires real-world information you don't have memorized.`;
|
|
1560
|
-
const history = [
|
|
1561
|
-
{ role: 'system', content: childSystemPrompt },
|
|
1562
|
-
{ role: 'user', content: childOpts.prompt },
|
|
1563
|
-
];
|
|
1564
|
-
const result = await child.runConversation(history);
|
|
1565
|
-
return result.finalContent;
|
|
1566
|
-
},
|
|
1567
1606
|
}));
|
|
1568
1607
|
bootLogger.child('subagent').info('subagent_fanout: wired (replaces stub)', {
|
|
1569
1608
|
providerId,
|
|
1570
1609
|
modelId,
|
|
1571
1610
|
fallback: adapter instanceof providerFallback_1.FallbackAdapter ? 'FallbackAdapter' : 'direct',
|
|
1572
1611
|
});
|
|
1612
|
+
// ── v4.6 Phase 1 — register spawn_sub_agent (REPL only) ────────────────
|
|
1613
|
+
//
|
|
1614
|
+
// The new single-child synchronous primitive. Coexists with
|
|
1615
|
+
// subagent_fanout (Q9 — additive in Phase 1; Phase 2 will refactor
|
|
1616
|
+
// fanout to call this primitive N times).
|
|
1617
|
+
//
|
|
1618
|
+
// Wired here, AFTER `agent` and `toolExecutor` are in scope, because
|
|
1619
|
+
// the child builder needs the parent agent's reference (to read
|
|
1620
|
+
// `getCurrentSignal()` at dispatch time per the agent-instance signal
|
|
1621
|
+
// pattern) and the parent's tool context for ssrf/tirith/memory/etc.
|
|
1622
|
+
//
|
|
1623
|
+
// Deliberately NOT registered in cli/v4/daemonAgentBuilder.ts —
|
|
1624
|
+
// daemon-fired agents don't expose spawn_sub_agent in their tool
|
|
1625
|
+
// catalog (Q6 lock).
|
|
1626
|
+
toolRegistry.register((0, spawnSubAgentTool_1.makeSpawnSubAgentTool)({
|
|
1627
|
+
parentAgent: agent,
|
|
1628
|
+
toolRegistry,
|
|
1629
|
+
parentToolContext: {
|
|
1630
|
+
cwd: process.cwd(),
|
|
1631
|
+
paths,
|
|
1632
|
+
sessions: sessionManager,
|
|
1633
|
+
memory: memoryManager,
|
|
1634
|
+
memoryGuard,
|
|
1635
|
+
// approvalEngine intentionally OMITTED — the child builder
|
|
1636
|
+
// constructs its own auto-deny ApprovalEngine. Listing it here
|
|
1637
|
+
// would be ignored (childBuilder overrides via spread), but
|
|
1638
|
+
// keeping it out makes the intent explicit.
|
|
1639
|
+
ssrfProtection,
|
|
1640
|
+
tirithScanner,
|
|
1641
|
+
skillLoader,
|
|
1642
|
+
},
|
|
1643
|
+
parentProvider: adapter,
|
|
1644
|
+
parentProviderId: providerId,
|
|
1645
|
+
parentModelId: modelId,
|
|
1646
|
+
resolveVerifiedFlag,
|
|
1647
|
+
resolveToolset,
|
|
1648
|
+
resolveMutates,
|
|
1649
|
+
runStore: replRunStore,
|
|
1650
|
+
instanceId: replInstanceId,
|
|
1651
|
+
// v4.6 Phase 2Q-B — REPL parent-run wiring. Reads the same
|
|
1652
|
+
// shared `replParentRunRef` the fanout factory above uses;
|
|
1653
|
+
// ChatSession.runAgentTurn populates it before each turn
|
|
1654
|
+
// dispatches. Returns undefined between turns so spawns from
|
|
1655
|
+
// slash-command handlers stay top-level (consistent with
|
|
1656
|
+
// pre-2Q-B observable behaviour).
|
|
1657
|
+
resolveParentRunId: () => replParentRunRef.runId ?? undefined,
|
|
1658
|
+
resolveParentSessionId: () => replParentRunRef.sessionId ?? undefined,
|
|
1659
|
+
// v4.6 Phase 1 observability — info-level traces for spec at
|
|
1660
|
+
// invocation, child-tools count, completion, and per-tool-call
|
|
1661
|
+
// run_events on the child's runs row.
|
|
1662
|
+
logger: bootLogger.child('subagent'),
|
|
1663
|
+
}));
|
|
1664
|
+
bootLogger.child('subagent').info('spawn_sub_agent: wired (REPL only)', {
|
|
1665
|
+
instanceId: replInstanceId,
|
|
1666
|
+
dbPath: (0, daemonConfig_1.daemonDbPath)(paths.root),
|
|
1667
|
+
});
|
|
1573
1668
|
// ── Phase v4.1-2.1: gateway message processor ────────────────────
|
|
1574
1669
|
//
|
|
1575
1670
|
// Channel adapters call `gateway.routeMessage(...)` for every inbound
|
|
@@ -1762,6 +1857,10 @@ async function buildAgentRuntime(cliOpts, opts) {
|
|
|
1762
1857
|
exploreMode,
|
|
1763
1858
|
channelManager,
|
|
1764
1859
|
daemonAgentBuilder,
|
|
1860
|
+
// v4.6 Phase 2Q-B — REPL parent-run wiring.
|
|
1861
|
+
replRunStore,
|
|
1862
|
+
replInstanceId,
|
|
1863
|
+
replParentRunRef,
|
|
1765
1864
|
};
|
|
1766
1865
|
}
|
|
1767
1866
|
async function runInteractiveChat(cliOpts, opts) {
|
|
@@ -1828,6 +1927,14 @@ async function runInteractiveChat(cliOpts, opts) {
|
|
|
1828
1927
|
// when /quit fires the auto-summary path.
|
|
1829
1928
|
memoryManager: runtime.memoryManager,
|
|
1830
1929
|
memoryGuard: runtime.memoryGuard,
|
|
1930
|
+
// v4.6 Phase 2Q-B — REPL parent-run wiring. ChatSession.runAgentTurn
|
|
1931
|
+
// writes a runs row per turn and stores its id into
|
|
1932
|
+
// `replParentRunRef`; the spawn / fanout factories above read the
|
|
1933
|
+
// ref via their `resolveParentRunId` / `resolveParentSessionId`
|
|
1934
|
+
// callbacks so children get `spawned_from_run_id` populated.
|
|
1935
|
+
replRunStore: runtime.replRunStore,
|
|
1936
|
+
replInstanceId: runtime.replInstanceId,
|
|
1937
|
+
replParentRunRef: runtime.replParentRunRef,
|
|
1831
1938
|
};
|
|
1832
1939
|
if (cliOpts.tui) {
|
|
1833
1940
|
await (0, aidenTUI_1.runTuiMode)({
|
|
@@ -922,6 +922,43 @@ class ChatSession {
|
|
|
922
922
|
const baseHistory = newHistory.length > 0
|
|
923
923
|
? [...this.history, ...newHistory, userMsg]
|
|
924
924
|
: [...this.history, userMsg];
|
|
925
|
+
// v4.6 Phase 2Q-B — REPL parent-run row (best-effort).
|
|
926
|
+
//
|
|
927
|
+
// Insert a `runs` row tagged with this REPL session BEFORE the
|
|
928
|
+
// agent loop dispatches. Capture the row id into the shared
|
|
929
|
+
// `replParentRunRef` so any `spawn_sub_agent` / `subagent_fanout`
|
|
930
|
+
// child this turn produces can link back via
|
|
931
|
+
// `spawned_from_run_id`. The ref is cleared in the catch /
|
|
932
|
+
// success paths below regardless of outcome.
|
|
933
|
+
//
|
|
934
|
+
// Defensive: a runStore write failure (locked DB, schema drift,
|
|
935
|
+
// etc.) must NOT crash the REPL — every persistence call here is
|
|
936
|
+
// wrapped in try/catch and reduces to a logged warning. The
|
|
937
|
+
// user-facing turn still runs.
|
|
938
|
+
let replRunId = null;
|
|
939
|
+
const replRunStore = this.opts.replRunStore;
|
|
940
|
+
const replInstanceId = this.opts.replInstanceId;
|
|
941
|
+
const replParentRunRef = this.opts.replParentRunRef;
|
|
942
|
+
if (replRunStore && replInstanceId && this.sessionId) {
|
|
943
|
+
try {
|
|
944
|
+
replRunId = replRunStore.create({
|
|
945
|
+
sessionId: this.sessionId,
|
|
946
|
+
instanceId: replInstanceId,
|
|
947
|
+
status: 'running',
|
|
948
|
+
startedAt: turnStartedAt,
|
|
949
|
+
});
|
|
950
|
+
if (replParentRunRef) {
|
|
951
|
+
replParentRunRef.runId = replRunId;
|
|
952
|
+
replParentRunRef.sessionId = this.sessionId;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
catch (err) {
|
|
956
|
+
// Logged once per turn; the user's chat is not interrupted.
|
|
957
|
+
// eslint-disable-next-line no-console
|
|
958
|
+
console.warn('[runs] failed to write REPL parent-run row:', err instanceof Error ? err.message : String(err));
|
|
959
|
+
replRunId = null;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
925
962
|
// Phase 16c: streaming gated on display.streaming config.
|
|
926
963
|
// v4.1.4 Part 1.6: PRODUCTION DEFAULT FLIPPED FROM FALSE TO TRUE.
|
|
927
964
|
// Streaming delivers the activity indicator, tool-row live tick,
|
|
@@ -1179,6 +1216,35 @@ class ChatSession {
|
|
|
1179
1216
|
this.history = result.messages;
|
|
1180
1217
|
this.totalUsage.inputTokens += result.totalUsage.inputTokens;
|
|
1181
1218
|
this.totalUsage.outputTokens += result.totalUsage.outputTokens;
|
|
1219
|
+
// v4.6 Phase 2Q-B — finalize the REPL parent-run row on success.
|
|
1220
|
+
// `finishReason` from the agent loop maps directly into our DB
|
|
1221
|
+
// status: `stop` → completed; `interrupted` / `tool_loop` →
|
|
1222
|
+
// surface as 'interrupted' so it's visible in `runs list`;
|
|
1223
|
+
// `budget_exhausted` / `error` → failed. Wrapped in try/catch
|
|
1224
|
+
// so even a runStore write failure here can't crash the REPL.
|
|
1225
|
+
if (replRunStore && replRunId !== null) {
|
|
1226
|
+
try {
|
|
1227
|
+
const dbStatus = result.finishReason === 'stop' ? 'completed' :
|
|
1228
|
+
result.finishReason === 'interrupted' ? 'interrupted' :
|
|
1229
|
+
result.finishReason === 'tool_loop' ? 'interrupted' :
|
|
1230
|
+
'failed';
|
|
1231
|
+
replRunStore.setStatus(replRunId, dbStatus, {
|
|
1232
|
+
finishReason: result.finishReason,
|
|
1233
|
+
completedAt: Date.now(),
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
catch (err) {
|
|
1237
|
+
// eslint-disable-next-line no-console
|
|
1238
|
+
console.warn('[runs] failed to finalize REPL parent-run row:', err instanceof Error ? err.message : String(err));
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
// Clear the shared ref so a subsequent turn (or stray
|
|
1242
|
+
// spawn/fanout dispatched between turns from a slash command
|
|
1243
|
+
// handler) doesn't see a stale parent id.
|
|
1244
|
+
if (replParentRunRef) {
|
|
1245
|
+
replParentRunRef.runId = null;
|
|
1246
|
+
replParentRunRef.sessionId = null;
|
|
1247
|
+
}
|
|
1182
1248
|
// Phase 16d: surface inline confirmations for verified memory writes.
|
|
1183
1249
|
// We MUST gate on verified=true (the post-write read flag from
|
|
1184
1250
|
// MemoryGuard) — HonestyEnforcement uses the same flag to catch
|
|
@@ -1289,6 +1355,26 @@ class ChatSession {
|
|
|
1289
1355
|
progressBar?.hide();
|
|
1290
1356
|
if (streamingActive)
|
|
1291
1357
|
this.opts.display.streamComplete();
|
|
1358
|
+
// v4.6 Phase 2Q-B — finalize REPL parent-run row on error.
|
|
1359
|
+
// Visible in `aiden runs list` as a failed top-level row so
|
|
1360
|
+
// operators can correlate a chat error with whatever children
|
|
1361
|
+
// it had already kicked off this turn.
|
|
1362
|
+
if (replRunStore && replRunId !== null) {
|
|
1363
|
+
try {
|
|
1364
|
+
replRunStore.setStatus(replRunId, 'failed', {
|
|
1365
|
+
finishReason: 'error',
|
|
1366
|
+
completedAt: Date.now(),
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
catch (e2) {
|
|
1370
|
+
// eslint-disable-next-line no-console
|
|
1371
|
+
console.warn('[runs] failed to mark REPL parent-run failed:', e2 instanceof Error ? e2.message : String(e2));
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
if (replParentRunRef) {
|
|
1375
|
+
replParentRunRef.runId = null;
|
|
1376
|
+
replParentRunRef.sessionId = null;
|
|
1377
|
+
}
|
|
1292
1378
|
const msg = err?.message ?? String(err);
|
|
1293
1379
|
// v4.1.3-prebump: classify the error so the suggestion below
|
|
1294
1380
|
// points at the actual fix instead of the generic "/model or
|
|
@@ -1418,6 +1504,27 @@ class ChatSession {
|
|
|
1418
1504
|
providerOk: !this.opts.unconfigured,
|
|
1419
1505
|
version: version_1.VERSION,
|
|
1420
1506
|
}) + '\n');
|
|
1507
|
+
// v4.6 Phase 3A — operator kill-switch indicator. Lands ABOVE
|
|
1508
|
+
// the blank-line + provider-source annotation so an operator
|
|
1509
|
+
// who paused in a prior session sees the state immediately on
|
|
1510
|
+
// boot, alongside the standard status pills. Single dim
|
|
1511
|
+
// warning line; no special chrome — the message itself is the
|
|
1512
|
+
// visual signal.
|
|
1513
|
+
try {
|
|
1514
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1515
|
+
const { getSpawnPause } = require('../../core/v4/subagent/spawnPause');
|
|
1516
|
+
const s = getSpawnPause().status();
|
|
1517
|
+
if (s.paused) {
|
|
1518
|
+
const reasonSuffix = s.reason ? ` · ${s.reason}` : '';
|
|
1519
|
+
const durationSuffix = typeof s.durationMs === 'number'
|
|
1520
|
+
? ` · ${formatDuration(s.durationMs)}`
|
|
1521
|
+
: '';
|
|
1522
|
+
display.warn(`spawn-pause: ON${reasonSuffix}${durationSuffix} — use /spawn-pause off to resume`);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
catch {
|
|
1526
|
+
// Singleton not initialised (test stubs, etc.) — silently skip.
|
|
1527
|
+
}
|
|
1421
1528
|
// v4.5 TUI polish — blank line so the status pills row doesn't
|
|
1422
1529
|
// crowd the muted source annotation right beneath it.
|
|
1423
1530
|
display.write('\n');
|
|
@@ -1493,6 +1600,19 @@ class ChatSession {
|
|
|
1493
1600
|
await this.maybeShowBootUpdatePrompt();
|
|
1494
1601
|
}
|
|
1495
1602
|
catch { /* never let the update prompt crash boot */ }
|
|
1603
|
+
// ONB1 slice 9 — one-time first-run hint banner. Renders below
|
|
1604
|
+
// the boot card on the very first session after a successful
|
|
1605
|
+
// setup; dismissed when the user sends their first message or
|
|
1606
|
+
// runs /dismiss. Lazy-required so test-harness sessions that
|
|
1607
|
+
// omit `paths` don't pay the fs cost.
|
|
1608
|
+
try {
|
|
1609
|
+
if (this.opts.paths) {
|
|
1610
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1611
|
+
const { renderFirstRunHint } = require('./repl/firstRunHint');
|
|
1612
|
+
await renderFirstRunHint({ paths: this.opts.paths, out: process.stdout });
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
catch { /* never let a missing marker crash boot */ }
|
|
1496
1616
|
// Bottom prompt hint — final line of the boot card.
|
|
1497
1617
|
display.write('\n');
|
|
1498
1618
|
display.write(display.bottomPromptHint() + '\n');
|
|
@@ -31,12 +31,14 @@ const LABEL = {
|
|
|
31
31
|
tce: 'TCE',
|
|
32
32
|
browser_depth: 'Browser depth',
|
|
33
33
|
suggestions: 'Suggestions',
|
|
34
|
+
planner_guard: 'Planner-Guard',
|
|
34
35
|
};
|
|
35
36
|
const CONFIG_DOTTED = {
|
|
36
37
|
sandbox: 'runtime_toggles.sandbox',
|
|
37
38
|
tce: 'runtime_toggles.tce',
|
|
38
39
|
browser_depth: 'runtime_toggles.browser_depth',
|
|
39
40
|
suggestions: 'runtime_toggles.suggestions',
|
|
41
|
+
planner_guard: 'runtime_toggles.planner_guard',
|
|
40
42
|
};
|
|
41
43
|
/**
|
|
42
44
|
* Apply a toggle change. When `ctx.config` is wired, persists to
|