aiden-runtime 4.1.5 → 4.6.0
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 +265 -847
- package/dist/api/server.js +32 -5
- package/dist/cli/v4/aidenCLI.js +536 -152
- package/dist/cli/v4/callbacks.js +170 -0
- package/dist/cli/v4/chatSession.js +245 -3
- package/dist/cli/v4/commands/_runtimeToggleHelpers.js +94 -0
- package/dist/cli/v4/commands/browserDepth.js +45 -0
- package/dist/cli/v4/commands/cron.js +264 -0
- package/dist/cli/v4/commands/daemon.js +541 -0
- package/dist/cli/v4/commands/daemonStatus.js +253 -0
- package/dist/cli/v4/commands/fanout.js +42 -59
- package/dist/cli/v4/commands/help.js +13 -0
- package/dist/cli/v4/commands/index.js +35 -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 +223 -0
- package/dist/cli/v4/commands/sandbox.js +48 -0
- package/dist/cli/v4/commands/spawnPause.js +93 -0
- package/dist/cli/v4/commands/suggestions.js +68 -0
- package/dist/cli/v4/commands/tce.js +41 -0
- package/dist/cli/v4/commands/trigger.js +378 -0
- package/dist/cli/v4/commands/update.js +95 -3
- package/dist/cli/v4/daemonAgentBuilder.js +145 -0
- package/dist/cli/v4/defaultSoul.js +1 -1
- package/dist/cli/v4/display/capabilityCard.js +26 -0
- package/dist/cli/v4/display.js +18 -8
- package/dist/cli/v4/replyRenderer.js +31 -23
- package/dist/cli/v4/updateBootPrompt.js +170 -0
- package/dist/core/playwrightBridge.js +129 -0
- package/dist/core/v4/aidenAgent.js +527 -5
- package/dist/core/v4/browserState.js +436 -0
- package/dist/core/v4/checkpoint.js +79 -0
- package/dist/core/v4/daemon/bootstrap.js +651 -0
- package/dist/core/v4/daemon/cleanShutdown.js +154 -0
- package/dist/core/v4/daemon/cron/cronBridge.js +126 -0
- package/dist/core/v4/daemon/cron/cronEmitter.js +173 -0
- package/dist/core/v4/daemon/cron/migration.js +199 -0
- package/dist/core/v4/daemon/cron/misfirePolicy.js +115 -0
- package/dist/core/v4/daemon/daemonConfig.js +90 -0
- package/dist/core/v4/daemon/db/connection.js +106 -0
- package/dist/core/v4/daemon/db/migrations.js +362 -0
- package/dist/core/v4/daemon/db/schema/v1.spec.js +18 -0
- package/dist/core/v4/daemon/dispatcher/agentRunner.js +98 -0
- package/dist/core/v4/daemon/dispatcher/budgetGate.js +127 -0
- package/dist/core/v4/daemon/dispatcher/daemonApproval.js +113 -0
- package/dist/core/v4/daemon/dispatcher/dailyBudgetTracker.js +120 -0
- package/dist/core/v4/daemon/dispatcher/dispatcher.js +389 -0
- package/dist/core/v4/daemon/dispatcher/fireRateLimiter.js +113 -0
- package/dist/core/v4/daemon/dispatcher/index.js +53 -0
- package/dist/core/v4/daemon/dispatcher/promptTemplate.js +95 -0
- package/dist/core/v4/daemon/dispatcher/realAgentRunner.js +356 -0
- package/dist/core/v4/daemon/dispatcher/resolveModel.js +93 -0
- package/dist/core/v4/daemon/dispatcher/sessionId.js +93 -0
- package/dist/core/v4/daemon/drain.js +156 -0
- package/dist/core/v4/daemon/eventLoopLag.js +73 -0
- package/dist/core/v4/daemon/health.js +159 -0
- package/dist/core/v4/daemon/idempotencyStore.js +204 -0
- package/dist/core/v4/daemon/index.js +179 -0
- package/dist/core/v4/daemon/instanceTracker.js +99 -0
- package/dist/core/v4/daemon/resourceRegistry.js +150 -0
- package/dist/core/v4/daemon/restartCode.js +32 -0
- package/dist/core/v4/daemon/restartFailureCounter.js +77 -0
- package/dist/core/v4/daemon/runStore.js +144 -0
- package/dist/core/v4/daemon/runtimeLock.js +167 -0
- package/dist/core/v4/daemon/signals.js +50 -0
- package/dist/core/v4/daemon/supervisor.js +272 -0
- package/dist/core/v4/daemon/triggerBus.js +279 -0
- package/dist/core/v4/daemon/triggers/email/allowlist.js +70 -0
- package/dist/core/v4/daemon/triggers/email/automatedSender.js +78 -0
- package/dist/core/v4/daemon/triggers/email/bodyExtractor.js +0 -0
- package/dist/core/v4/daemon/triggers/email/emailSeenStore.js +99 -0
- package/dist/core/v4/daemon/triggers/email/emailSpec.js +107 -0
- package/dist/core/v4/daemon/triggers/email/imapConnection.js +211 -0
- package/dist/core/v4/daemon/triggers/email/index.js +332 -0
- package/dist/core/v4/daemon/triggers/email/seenUids.js +60 -0
- package/dist/core/v4/daemon/triggers/fileObservationsStore.js +93 -0
- package/dist/core/v4/daemon/triggers/fileWatcher.js +253 -0
- package/dist/core/v4/daemon/triggers/fileWatcherSpec.js +88 -0
- package/dist/core/v4/daemon/triggers/fsIdentity.js +42 -0
- package/dist/core/v4/daemon/triggers/globMatcher.js +100 -0
- package/dist/core/v4/daemon/triggers/reconcile.js +206 -0
- package/dist/core/v4/daemon/triggers/settleStat.js +81 -0
- package/dist/core/v4/daemon/triggers/webhook.js +376 -0
- package/dist/core/v4/daemon/triggers/webhookDeliveriesStore.js +109 -0
- package/dist/core/v4/daemon/triggers/webhookIdempotency.js +72 -0
- package/dist/core/v4/daemon/triggers/webhookRateLimit.js +56 -0
- package/dist/core/v4/daemon/triggers/webhookSpec.js +76 -0
- package/dist/core/v4/daemon/triggers/webhookVerifier.js +128 -0
- package/dist/core/v4/daemon/types.js +15 -0
- package/dist/core/v4/dockerSession.js +461 -0
- package/dist/core/v4/dryRun.js +117 -0
- package/dist/core/v4/failureClassifier.js +779 -0
- package/dist/core/v4/providerFallback.js +35 -2
- package/dist/core/v4/recoveryReport.js +449 -0
- package/dist/core/v4/runtimeToggles.js +214 -0
- package/dist/core/v4/sandboxConfig.js +285 -0
- package/dist/core/v4/sandboxFs.js +316 -0
- 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/suggestionCatalog.js +41 -0
- package/dist/core/v4/suggestionEngine.js +210 -0
- package/dist/core/v4/toolRegistry.js +37 -3
- package/dist/core/v4/turnState.js +587 -0
- package/dist/core/v4/update/checkUpdate.js +63 -3
- package/dist/core/v4/update/installMethodDetect.js +115 -0
- package/dist/core/v4/update/registryClient.js +121 -0
- package/dist/core/v4/update/skipState.js +75 -0
- package/dist/core/v4/verifier.js +448 -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/browser/_observer.js +224 -0
- package/dist/tools/v4/browser/browserBlocker.js +396 -0
- package/dist/tools/v4/browser/browserClick.js +18 -1
- package/dist/tools/v4/browser/browserClose.js +18 -1
- package/dist/tools/v4/browser/browserExtract.js +5 -1
- package/dist/tools/v4/browser/browserFill.js +17 -1
- package/dist/tools/v4/browser/browserGetUrl.js +5 -1
- package/dist/tools/v4/browser/browserNavigate.js +16 -1
- package/dist/tools/v4/browser/browserScreenshot.js +5 -1
- package/dist/tools/v4/browser/browserScroll.js +18 -1
- package/dist/tools/v4/browser/browserType.js +17 -1
- package/dist/tools/v4/browser/captchaCheck.js +5 -1
- package/dist/tools/v4/executeCode.js +1 -0
- package/dist/tools/v4/files/fileCopy.js +56 -2
- package/dist/tools/v4/files/fileDelete.js +38 -1
- package/dist/tools/v4/files/fileList.js +12 -1
- package/dist/tools/v4/files/fileMove.js +59 -2
- package/dist/tools/v4/files/filePatch.js +43 -1
- package/dist/tools/v4/files/fileRead.js +12 -1
- package/dist/tools/v4/files/fileWrite.js +41 -1
- package/dist/tools/v4/index.js +88 -61
- package/dist/tools/v4/memory/memoryAdd.js +14 -0
- package/dist/tools/v4/memory/memoryRemove.js +14 -0
- package/dist/tools/v4/memory/memoryReplace.js +15 -0
- package/dist/tools/v4/memory/sessionSummary.js +12 -0
- package/dist/tools/v4/process/processKill.js +19 -0
- package/dist/tools/v4/process/processList.js +1 -0
- package/dist/tools/v4/process/processLogRead.js +1 -0
- package/dist/tools/v4/process/processSpawn.js +13 -0
- package/dist/tools/v4/process/processWait.js +1 -0
- package/dist/tools/v4/sessions/recallSession.js +1 -0
- package/dist/tools/v4/sessions/sessionList.js +1 -0
- package/dist/tools/v4/sessions/sessionSearch.js +1 -0
- package/dist/tools/v4/skills/lookupToolSchema.js +7 -0
- package/dist/tools/v4/skills/skillManage.js +13 -0
- package/dist/tools/v4/skills/skillView.js +1 -0
- package/dist/tools/v4/skills/skillsList.js +1 -0
- package/dist/tools/v4/subagent/spawnSubAgentTool.js +334 -0
- package/dist/tools/v4/subagent/subagentFanout.js +54 -1
- package/dist/tools/v4/system/aidenSelfUpdate.js +16 -0
- package/dist/tools/v4/system/appClose.js +13 -0
- package/dist/tools/v4/system/appInput.js +13 -0
- package/dist/tools/v4/system/appLaunch.js +13 -0
- package/dist/tools/v4/system/clipboardRead.js +1 -0
- package/dist/tools/v4/system/clipboardWrite.js +14 -0
- package/dist/tools/v4/system/mediaKey.js +12 -0
- package/dist/tools/v4/system/mediaSessions.js +1 -0
- package/dist/tools/v4/system/mediaTransport.js +13 -0
- package/dist/tools/v4/system/naturalEvents.js +1 -0
- package/dist/tools/v4/system/nowPlaying.js +1 -0
- package/dist/tools/v4/system/osProcessList.js +1 -0
- package/dist/tools/v4/system/screenshot.js +1 -0
- package/dist/tools/v4/system/systemInfo.js +1 -0
- package/dist/tools/v4/system/volumeSet.js +17 -0
- package/dist/tools/v4/terminal/shellExec.js +81 -9
- package/dist/tools/v4/web/deepResearch.js +1 -0
- package/dist/tools/v4/web/openUrl.js +1 -0
- package/dist/tools/v4/web/webFetch.js +1 -0
- package/dist/tools/v4/web/webPage.js +1 -0
- package/dist/tools/v4/web/webSearch.js +1 -0
- package/dist/tools/v4/web/youtubeSearch.js +1 -0
- package/package.json +13 -3
|
@@ -67,6 +67,32 @@ function renderCapabilityCard(data, colorize) {
|
|
|
67
67
|
// Compose the inner rows that boxSharp will wrap. Each row is the
|
|
68
68
|
// CONTENT (no border) — boxSharp adds the side borders + padding.
|
|
69
69
|
const rows = [];
|
|
70
|
+
// v4.2 Phase 3 — optional "what happened" one-liner above the
|
|
71
|
+
// canStill section. Rendered as a muted-tone line so it reads as
|
|
72
|
+
// context, not action. Skipped cleanly when absent → v4.1.3
|
|
73
|
+
// capability-card behaviour preserved for non-Phase-3 callers.
|
|
74
|
+
if (data.whatHappened) {
|
|
75
|
+
rows.push('');
|
|
76
|
+
rows.push(colorize(truncToContent(data.whatHappened), 'muted'));
|
|
77
|
+
}
|
|
78
|
+
// v4.2 Phase 3 — optional failure-category pill row. Each entry
|
|
79
|
+
// renders as `<category>(<count>)` separated by " · " bullets.
|
|
80
|
+
// Pre-sorted by the generator (desc count then category priority);
|
|
81
|
+
// renderer just formats. Skipped cleanly when absent.
|
|
82
|
+
if (data.failuresByCategory && data.failuresByCategory.length > 0) {
|
|
83
|
+
const pills = data.failuresByCategory
|
|
84
|
+
.map((p) => `${p.category}(${p.count})`)
|
|
85
|
+
.join(' · ');
|
|
86
|
+
const label = colorize('Failures:', 'error');
|
|
87
|
+
rows.push(`${label} ${truncToContent(pills)}`);
|
|
88
|
+
}
|
|
89
|
+
// v4.3 Phase 5 — optional one-line browser context summary
|
|
90
|
+
// (active tab, blocker kind, other-tab count, stale-ref retries).
|
|
91
|
+
// Already pre-formatted by recoveryReport's `buildBrowserContextLine`.
|
|
92
|
+
// Rendered as a muted-tone line below the Failures: row when present.
|
|
93
|
+
if (data.browserContext) {
|
|
94
|
+
rows.push(colorize(truncToContent(data.browserContext), 'muted'));
|
|
95
|
+
}
|
|
70
96
|
if (data.canStill.length > 0) {
|
|
71
97
|
rows.push('');
|
|
72
98
|
rows.push(heading('Can still:'));
|
package/dist/cli/v4/display.js
CHANGED
|
@@ -523,14 +523,21 @@ class Display {
|
|
|
523
523
|
return (0, box_1.truncateVisible)(text, INTERIOR);
|
|
524
524
|
return text + ' '.repeat(INTERIOR - v);
|
|
525
525
|
};
|
|
526
|
+
// v4.5 TUI polish — add a leading + trailing blank line so the
|
|
527
|
+
// box has visual breathing room from the lines above/below, and
|
|
528
|
+
// a trailing interior blank so contact info doesn't crowd the
|
|
529
|
+
// bottom border.
|
|
526
530
|
return [
|
|
531
|
+
'',
|
|
527
532
|
lidIndent + lid,
|
|
528
533
|
wallIndent + pipe + ' '.repeat(INTERIOR) + pipe,
|
|
529
534
|
wallIndent + pipe + padInner(` ${heart} ${val('Built solo')}`) + pipe,
|
|
530
535
|
wallIndent + pipe + padInner(` ${lab('GitHub:')} ${val('github.com/taracodlabs/aiden')}`) + pipe,
|
|
531
536
|
wallIndent + pipe + padInner(` ${lab('Web:')} ${val('aiden.taracod.com')}`) + pipe,
|
|
532
537
|
wallIndent + pipe + padInner(` ${lab('Contact:')} ${val('contact@taracod.com')}`) + pipe,
|
|
538
|
+
wallIndent + pipe + ' '.repeat(INTERIOR) + pipe,
|
|
533
539
|
wallIndent + pipe + lid + pipe,
|
|
540
|
+
'',
|
|
534
541
|
].join('\n');
|
|
535
542
|
}
|
|
536
543
|
/**
|
|
@@ -929,22 +936,25 @@ class Display {
|
|
|
929
936
|
}
|
|
930
937
|
};
|
|
931
938
|
const eraseLine = () => {
|
|
932
|
-
// Walk up to the indicator's row(s) + erase
|
|
933
|
-
//
|
|
934
|
-
//
|
|
935
|
-
//
|
|
936
|
-
//
|
|
937
|
-
//
|
|
939
|
+
// Walk up to the indicator's row(s) + erase, then drop ONE
|
|
940
|
+
// newline so the cursor lands on a blank line BELOW the
|
|
941
|
+
// indicator's old footprint. v4.1.6 polish: previous behavior
|
|
942
|
+
// left the cursor at col 0 of the just-erased row so caller
|
|
943
|
+
// writes (agentHeader, tool row, etc.) sat tight against where
|
|
944
|
+
// the indicator had been. v4.1.5 visual smoke flagged the
|
|
945
|
+
// wave-bar→`┃ Aiden` proximity as feeling cramped. The
|
|
946
|
+
// trailing `\n` gains one visible blank row of breathing space
|
|
947
|
+
// AND adds another Windows ConPTY flush trigger (Issue M).
|
|
938
948
|
//
|
|
939
949
|
// v4.1.5 Issue K — with wave bar enabled, walk up 2 rows (two
|
|
940
950
|
// up-1+erase sequences). Without the bar, walk up 1 row.
|
|
941
951
|
if (!isTty || !printed)
|
|
942
952
|
return;
|
|
943
953
|
if (waveBarEnabled) {
|
|
944
|
-
out.write(`${ANSI_UP_ERASE}${ANSI_UP_ERASE}`);
|
|
954
|
+
out.write(`${ANSI_UP_ERASE}${ANSI_UP_ERASE}\n`);
|
|
945
955
|
}
|
|
946
956
|
else {
|
|
947
|
-
out.write(ANSI_UP_ERASE);
|
|
957
|
+
out.write(`${ANSI_UP_ERASE}\n`);
|
|
948
958
|
}
|
|
949
959
|
};
|
|
950
960
|
// Initial paint — only on TTY. Indicator + `\n` so the buffer
|
|
@@ -43,32 +43,38 @@ function paint(kind) {
|
|
|
43
43
|
return (text) => (0, skinEngine_1.getSkinEngine)().applyColors(text, kind);
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* v4.1.3-essentials: bold (`**foo**`) markdown
|
|
47
|
-
*
|
|
48
|
-
* collided with
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
46
|
+
* v4.1.3-essentials → v4.5 TUI polish: bold (`**foo**`) markdown
|
|
47
|
+
* emphasis renders as plain ANSI bold. Earlier iterations tried
|
|
48
|
+
* 'brand' (orange, collided with heading hierarchy), bright-white
|
|
49
|
+
* (low contrast on dark themes), and bold+underline (made bulleted
|
|
50
|
+
* list items look like clickable hyperlinks — user feedback after
|
|
51
|
+
* v4.5 Phase 8 stabilisation).
|
|
52
52
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* styles surprisingly on terminals that batch SGR updates.
|
|
53
|
+
* Landed on bold-only: weight carries emphasis, no color slot
|
|
54
|
+
* consumed, and no underline confusion with terminal URL/path
|
|
55
|
+
* auto-highlight features.
|
|
57
56
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
57
|
+
* ANSI sequence: `\x1b[1m{text}\x1b[22m` — bold ON, bold OFF.
|
|
58
|
+
*
|
|
59
|
+
* Bypasses the skin system intentionally — emphasis is an
|
|
60
|
+
* opinionated default for this slice. Same caveat as the prior
|
|
61
|
+
* bold-as-color iteration: nested markdown loses the outer style
|
|
62
|
+
* after close (pre-existing limitation of the painter-stack
|
|
63
|
+
* architecture).
|
|
62
64
|
*
|
|
63
65
|
* Honors `NO_COLOR=1` per the standard (skips the wrap entirely).
|
|
64
|
-
* Strictly speaking `NO_COLOR` is about color
|
|
65
|
-
*
|
|
66
|
-
*
|
|
66
|
+
* Strictly speaking `NO_COLOR` is about color, but the wrap still
|
|
67
|
+
* emits ANSI escapes; honoring the env var keeps output paste-safe
|
|
68
|
+
* in scripted contexts.
|
|
69
|
+
*
|
|
70
|
+
* Function name: `paintEmphasis` rather than `paintBold` because
|
|
71
|
+
* the latter is already taken by a different (parameterised) helper
|
|
72
|
+
* below.
|
|
67
73
|
*/
|
|
68
|
-
function
|
|
74
|
+
function paintEmphasis(text) {
|
|
69
75
|
if (process.env.NO_COLOR && process.env.NO_COLOR !== '')
|
|
70
76
|
return text;
|
|
71
|
-
return `\x1b[1m
|
|
77
|
+
return `\x1b[1m${text}\x1b[22m`;
|
|
72
78
|
}
|
|
73
79
|
/**
|
|
74
80
|
* v4.1.3-essentials reply-polish: bold-on + skin paint + bold-off.
|
|
@@ -367,10 +373,12 @@ function getReplyRenderer() {
|
|
|
367
373
|
hr: () => paint('muted')('─'.repeat((0, frame_1.getBodyWidth)())) + '\n',
|
|
368
374
|
listitem: renderListItem,
|
|
369
375
|
paragraph: (text) => `${text}\n\n`,
|
|
370
|
-
// v4.1.3-essentials: bold renders as ANSI
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
|
|
376
|
+
// v4.1.3-essentials → v4.5 TUI polish: bold renders as plain ANSI
|
|
377
|
+
// bold. Earlier iterations tried orange (collision with headings),
|
|
378
|
+
// bright-white (low contrast), and bold+underline (made bulleted
|
|
379
|
+
// **labels** look like clickable links — user feedback after v4.5
|
|
380
|
+
// Phase 8 stabilisation). Weight alone carries emphasis.
|
|
381
|
+
strong: paintEmphasis,
|
|
374
382
|
em: paint('muted'),
|
|
375
383
|
// v4.1.3-essentials reply-polish: inline `` `code` `` — strip
|
|
376
384
|
// the literal backticks (used to leak into the visible output)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* cli/v4/updateBootPrompt.ts — v4.5 update system.
|
|
10
|
+
*
|
|
11
|
+
* Boxed three-option prompt rendered after the boot card / status
|
|
12
|
+
* pills, before the bottomPromptHint (Q-U5(b) position). When an
|
|
13
|
+
* update is available AND not skipped, the user sees:
|
|
14
|
+
*
|
|
15
|
+
* ┌─────────────────────────────────────────────────────────┐
|
|
16
|
+
* │ ◆ Aiden 4.5.1 available (you're on 4.5.0) │
|
|
17
|
+
* │ │
|
|
18
|
+
* │ What's new: bug fix for IMAP reconnect on Windows │
|
|
19
|
+
* │ │
|
|
20
|
+
* │ Update now? (y/n/later) │
|
|
21
|
+
* │ y — update now, restart after │
|
|
22
|
+
* │ n — skip this version (don't ask again) │
|
|
23
|
+
* │ later — remind me next session │
|
|
24
|
+
* └─────────────────────────────────────────────────────────┘
|
|
25
|
+
*
|
|
26
|
+
* Behavior (Q-U2(a)):
|
|
27
|
+
* - 5-second timeout defaults to 'later' (no state change)
|
|
28
|
+
* - 'y' triggers `executeInstall` via the method-aware dispatch
|
|
29
|
+
* - 'n' persists `skippedVersion = status.latest` to the cache
|
|
30
|
+
* - 'later' is a no-op (re-prompt next session)
|
|
31
|
+
*
|
|
32
|
+
* Display sink: writes via the supplied `display`. Keypress capture
|
|
33
|
+
* uses raw stdin mode so the user can press a single key — no Enter
|
|
34
|
+
* needed for y/n. 'later' is the timeout default; an explicit 'l'
|
|
35
|
+
* keypress also maps to it.
|
|
36
|
+
*
|
|
37
|
+
* Designed to be skippable: if stdin isn't a TTY (CI / piped /
|
|
38
|
+
* non-interactive), short-circuits to 'later' immediately so boot
|
|
39
|
+
* doesn't hang.
|
|
40
|
+
*/
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.renderBootUpdateBox = renderBootUpdateBox;
|
|
43
|
+
exports.showBootUpdatePrompt = showBootUpdatePrompt;
|
|
44
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
45
|
+
/**
|
|
46
|
+
* Pure box renderer — public so tests can assert the rendered shape
|
|
47
|
+
* without driving stdin.
|
|
48
|
+
*/
|
|
49
|
+
function renderBootUpdateBox(status, method) {
|
|
50
|
+
const innerWidth = 60;
|
|
51
|
+
const pad = (s) => {
|
|
52
|
+
const visible = s.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
53
|
+
const len = visible.length;
|
|
54
|
+
if (len >= innerWidth)
|
|
55
|
+
return s.slice(0, innerWidth);
|
|
56
|
+
return s + ' '.repeat(innerWidth - len);
|
|
57
|
+
};
|
|
58
|
+
const top = '┌' + '─'.repeat(innerWidth) + '┐';
|
|
59
|
+
const bottom = '└' + '─'.repeat(innerWidth) + '┘';
|
|
60
|
+
const blank = '│' + ' '.repeat(innerWidth) + '│';
|
|
61
|
+
const lines = [];
|
|
62
|
+
lines.push(top);
|
|
63
|
+
lines.push(blank);
|
|
64
|
+
lines.push('│' + pad(` ◆ Aiden ${status.latest} available (you're on ${status.installed})`) + '│');
|
|
65
|
+
if (status.releaseNotes && status.releaseNotes.length > 0) {
|
|
66
|
+
lines.push(blank);
|
|
67
|
+
lines.push('│' + pad(` What's new: ${status.releaseNotes}`) + '│');
|
|
68
|
+
}
|
|
69
|
+
lines.push(blank);
|
|
70
|
+
lines.push('│' + pad(` Update now? (y/n/later)`) + '│');
|
|
71
|
+
lines.push('│' + pad(` y — update via ${method.method}`) + '│');
|
|
72
|
+
lines.push('│' + pad(` n — skip ${status.latest} (don't ask again)`) + '│');
|
|
73
|
+
lines.push('│' + pad(` later — remind me next session (default in 5s)`) + '│');
|
|
74
|
+
lines.push(blank);
|
|
75
|
+
lines.push(bottom);
|
|
76
|
+
return lines;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Show the prompt and resolve with the user's choice. Never throws
|
|
80
|
+
* — returns 'later' on any error / timeout / non-TTY.
|
|
81
|
+
*/
|
|
82
|
+
async function showBootUpdatePrompt(input) {
|
|
83
|
+
// Test seam — short-circuit before any I/O.
|
|
84
|
+
if (input._testChoice)
|
|
85
|
+
return input._testChoice;
|
|
86
|
+
const isTTY = input.isTTY ?? Boolean(input.stdin?.isTTY ?? process.stdin.isTTY);
|
|
87
|
+
// Non-interactive stdin → silently default to 'later'. Boot must
|
|
88
|
+
// not hang in CI / piped contexts.
|
|
89
|
+
if (!isTTY)
|
|
90
|
+
return 'later';
|
|
91
|
+
if (!input.status.updateAvailable || !input.status.latest)
|
|
92
|
+
return 'later';
|
|
93
|
+
if (input.status.skipped)
|
|
94
|
+
return 'later';
|
|
95
|
+
// Render the box.
|
|
96
|
+
for (const line of renderBootUpdateBox(input.status, input.method)) {
|
|
97
|
+
input.display.write(line + '\n');
|
|
98
|
+
}
|
|
99
|
+
const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
100
|
+
const stdin = input.stdin ?? process.stdin;
|
|
101
|
+
return await captureSingleKey(stdin, timeoutMs);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Read ONE keypress from stdin in raw mode. Maps:
|
|
105
|
+
* - 'y' / 'Y' → 'install'
|
|
106
|
+
* - 'n' / 'N' → 'skip'
|
|
107
|
+
* - 'l' / 'L' / Enter / any other key → 'later'
|
|
108
|
+
* - Timeout → 'later'
|
|
109
|
+
*
|
|
110
|
+
* Restores stdin's prior pause/resume + rawMode state so the
|
|
111
|
+
* subsequent REPL prompt isn't broken.
|
|
112
|
+
*/
|
|
113
|
+
function captureSingleKey(stdin, timeoutMs) {
|
|
114
|
+
return new Promise((resolve) => {
|
|
115
|
+
let done = false;
|
|
116
|
+
const wasRaw = stdin.isRaw === true;
|
|
117
|
+
const wasPaused = stdin.isPaused();
|
|
118
|
+
const cleanup = () => {
|
|
119
|
+
if (done)
|
|
120
|
+
return;
|
|
121
|
+
done = true;
|
|
122
|
+
try {
|
|
123
|
+
stdin.removeListener('data', onData);
|
|
124
|
+
}
|
|
125
|
+
catch { /* noop */ }
|
|
126
|
+
try {
|
|
127
|
+
if (!wasRaw && stdin.setRawMode)
|
|
128
|
+
stdin.setRawMode(false);
|
|
129
|
+
}
|
|
130
|
+
catch { /* noop */ }
|
|
131
|
+
if (wasPaused)
|
|
132
|
+
try {
|
|
133
|
+
stdin.pause();
|
|
134
|
+
}
|
|
135
|
+
catch { /* noop */ }
|
|
136
|
+
clearTimeout(timer);
|
|
137
|
+
};
|
|
138
|
+
const onData = (chunk) => {
|
|
139
|
+
const raw = chunk.toString();
|
|
140
|
+
const ch = raw.length > 0 ? raw[0].toLowerCase() : '';
|
|
141
|
+
let choice;
|
|
142
|
+
if (ch === 'y')
|
|
143
|
+
choice = 'install';
|
|
144
|
+
else if (ch === 'n')
|
|
145
|
+
choice = 'skip';
|
|
146
|
+
else
|
|
147
|
+
choice = 'later';
|
|
148
|
+
cleanup();
|
|
149
|
+
resolve(choice);
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
if (stdin.setRawMode && !wasRaw)
|
|
153
|
+
stdin.setRawMode(true);
|
|
154
|
+
if (wasPaused)
|
|
155
|
+
stdin.resume();
|
|
156
|
+
stdin.on('data', onData);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
cleanup();
|
|
160
|
+
resolve('later');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const timer = setTimeout(() => {
|
|
164
|
+
cleanup();
|
|
165
|
+
resolve('later');
|
|
166
|
+
}, timeoutMs);
|
|
167
|
+
if (typeof timer.unref === 'function')
|
|
168
|
+
timer.unref();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
@@ -58,11 +58,14 @@ exports.pwClickFirstResult = pwClickFirstResult;
|
|
|
58
58
|
exports.pwType = pwType;
|
|
59
59
|
exports.pwScroll = pwScroll;
|
|
60
60
|
exports.pwSnapshot = pwSnapshot;
|
|
61
|
+
exports.pwSnapshotHash = pwSnapshotHash;
|
|
62
|
+
exports.pwSnapshotTabs = pwSnapshotTabs;
|
|
61
63
|
exports.pwGetUrl = pwGetUrl;
|
|
62
64
|
exports.pwClose = pwClose;
|
|
63
65
|
exports.getActiveBrowserPage = getActiveBrowserPage;
|
|
64
66
|
const path_1 = __importDefault(require("path"));
|
|
65
67
|
const fs_1 = __importDefault(require("fs"));
|
|
68
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
66
69
|
const paths_1 = require("./paths");
|
|
67
70
|
// ── Lazy-import Playwright so the server boots even if playwright
|
|
68
71
|
// is not installed (tools will return a clear error message).
|
|
@@ -410,6 +413,132 @@ async function pwSnapshot() {
|
|
|
410
413
|
return { ok: false, error: e.message };
|
|
411
414
|
}
|
|
412
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* v4.3 Phase 1 — structured page-state snapshot used by the BrowserState
|
|
418
|
+
* observer. Captures URL + title + body-text hash + recursive iframe-tree
|
|
419
|
+
* hash in a single in-page evaluate. Truncates body innerText to 5 000
|
|
420
|
+
* chars before hashing so cost stays bounded for large pages.
|
|
421
|
+
*
|
|
422
|
+
* Cross-origin iframe srcs are surfaced (URL is visible); attempting to
|
|
423
|
+
* read `iframe.contentDocument` on a cross-origin frame throws — the
|
|
424
|
+
* recursive walker catches and skips, recording only the iframe's src.
|
|
425
|
+
*
|
|
426
|
+
* Returns `ok: false` when the browser is closed or evaluate fails.
|
|
427
|
+
* Caller (BrowserState.captureState) treats `ok: false` as "snapshot
|
|
428
|
+
* unavailable, embed no sidecar this call".
|
|
429
|
+
*/
|
|
430
|
+
async function pwSnapshotHash() {
|
|
431
|
+
try {
|
|
432
|
+
const page = await ensurePage();
|
|
433
|
+
const url = page.url();
|
|
434
|
+
const title = await page.title();
|
|
435
|
+
// eslint-disable-next-line no-undef
|
|
436
|
+
const data = await page.evaluate(() => {
|
|
437
|
+
const doc = globalThis.document;
|
|
438
|
+
const text = (doc?.body?.innerText ?? '');
|
|
439
|
+
// Recursive iframe URL walk. Cross-origin iframes throw on
|
|
440
|
+
// contentDocument access — catch and record just the src.
|
|
441
|
+
const urls = [];
|
|
442
|
+
function walk(d) {
|
|
443
|
+
try {
|
|
444
|
+
const iframes = Array.from(d.querySelectorAll('iframe'));
|
|
445
|
+
for (const f of iframes) {
|
|
446
|
+
urls.push(String(f.src ?? ''));
|
|
447
|
+
try {
|
|
448
|
+
if (f.contentDocument)
|
|
449
|
+
walk(f.contentDocument);
|
|
450
|
+
}
|
|
451
|
+
catch { /* cross-origin */ }
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
catch { /* defensive */ }
|
|
455
|
+
}
|
|
456
|
+
walk(doc);
|
|
457
|
+
return { text, frame_urls: urls.join('|') };
|
|
458
|
+
});
|
|
459
|
+
const dom_text_hash = crypto_1.default.createHash('sha256').update(data.text.slice(0, 5000)).digest('hex');
|
|
460
|
+
const frame_tree_hash = crypto_1.default.createHash('sha256').update(data.frame_urls).digest('hex');
|
|
461
|
+
return { ok: true, url, title, dom_text_hash, frame_tree_hash };
|
|
462
|
+
}
|
|
463
|
+
catch (e) {
|
|
464
|
+
return { ok: false, error: e.message };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
// ── v4.3 Phase 4 — multi-tab snapshot ─────────────────────────────────────
|
|
468
|
+
//
|
|
469
|
+
// Persistent context can hold many pages (target=_blank, window.open,
|
|
470
|
+
// CDP popups). Aiden's tools still target `_activePage` only — Phase 4
|
|
471
|
+
// is DATA-ONLY: it records what tabs exist, who opened whom, and which
|
|
472
|
+
// is the active one, so v4.4+ cross-tab orchestration has a foundation.
|
|
473
|
+
//
|
|
474
|
+
// `_tabIdMap` is a WeakMap that assigns each observed Page a stable
|
|
475
|
+
// `tab_id` (`tab-1`, `tab-2`, ...). When a Page closes, Playwright drops
|
|
476
|
+
// its reference and the WeakMap entry GCs naturally — no manual cleanup.
|
|
477
|
+
const _tabIdMap = new WeakMap();
|
|
478
|
+
let _nextTabIdCounter = 0;
|
|
479
|
+
function getOrAssignTabId(page) {
|
|
480
|
+
let id = _tabIdMap.get(page);
|
|
481
|
+
if (!id) {
|
|
482
|
+
_nextTabIdCounter += 1;
|
|
483
|
+
id = `tab-${_nextTabIdCounter}`;
|
|
484
|
+
_tabIdMap.set(page, id);
|
|
485
|
+
}
|
|
486
|
+
return id;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* v4.3 Phase 4 — enumerate all pages in the persistent context and
|
|
490
|
+
* return their wire-data form. Stable `tab_id` per Page via the
|
|
491
|
+
* `_tabIdMap` WeakMap. The opener Page is looked up the same way, so
|
|
492
|
+
* `opener_id` is stable across reconciliations as long as the parent
|
|
493
|
+
* still exists.
|
|
494
|
+
*
|
|
495
|
+
* Cheap — `context.pages()` is in-process; the per-page work is one
|
|
496
|
+
* `url()` getter and one async `title()` call. Total cost scales
|
|
497
|
+
* linearly with the number of tabs; for typical sessions (1-5 tabs)
|
|
498
|
+
* it's well under 50ms.
|
|
499
|
+
*
|
|
500
|
+
* Returns `ok: false` when the browser is closed. Caller (BrowserState
|
|
501
|
+
* .reconcileTabs) treats `ok: false` as "no reconciliation this cycle".
|
|
502
|
+
*/
|
|
503
|
+
async function pwSnapshotTabs() {
|
|
504
|
+
try {
|
|
505
|
+
const ctx = await ensureContext();
|
|
506
|
+
const pages = ctx.pages();
|
|
507
|
+
const tabs = [];
|
|
508
|
+
for (const p of pages) {
|
|
509
|
+
// Skip pages already closed mid-walk (rare race).
|
|
510
|
+
if (typeof p.isClosed === 'function' && p.isClosed())
|
|
511
|
+
continue;
|
|
512
|
+
const tab_id = getOrAssignTabId(p);
|
|
513
|
+
const url = (typeof p.url === 'function') ? p.url() : '';
|
|
514
|
+
// title() can throw if the page navigated mid-walk; default to empty.
|
|
515
|
+
let title = '';
|
|
516
|
+
try {
|
|
517
|
+
title = await p.title();
|
|
518
|
+
}
|
|
519
|
+
catch { /* defensive */ }
|
|
520
|
+
// opener() returns the parent Page (or null). Same WeakMap lookup.
|
|
521
|
+
let opener_id = null;
|
|
522
|
+
try {
|
|
523
|
+
const opener = typeof p.opener === 'function' ? await p.opener() : null;
|
|
524
|
+
if (opener)
|
|
525
|
+
opener_id = _tabIdMap.get(opener) ?? null;
|
|
526
|
+
}
|
|
527
|
+
catch { /* defensive */ }
|
|
528
|
+
tabs.push({
|
|
529
|
+
tab_id,
|
|
530
|
+
url,
|
|
531
|
+
title,
|
|
532
|
+
is_active: p === _activePage,
|
|
533
|
+
opener_id,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
return { ok: true, tabs };
|
|
537
|
+
}
|
|
538
|
+
catch (e) {
|
|
539
|
+
return { ok: false, error: e.message };
|
|
540
|
+
}
|
|
541
|
+
}
|
|
413
542
|
/** Return the URL currently loaded in the active browser page. */
|
|
414
543
|
async function pwGetUrl() {
|
|
415
544
|
try {
|