@buildaharness/aielia 0.3.5 → 0.3.6
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 +96 -18
- package/dist/action-approval-service.d.ts +34 -2
- package/dist/action-record.d.ts +13 -0
- package/dist/activity-log.d.ts +4 -0
- package/dist/agent-loop.d.ts +83 -1
- package/dist/assistant-session.d.ts +20 -1
- package/dist/assistant-types.d.ts +3 -1
- package/dist/assistant.d.ts +32 -0
- package/dist/{bin-CjKpxDxl.js → bin-BYRl4xXM.js} +365 -216
- package/dist/bin-BYRl4xXM.js.map +1 -0
- package/dist/cli-config.d.ts +3 -0
- package/dist/cli-session.d.ts +5 -4
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/debug-log.d.ts +10 -1
- package/dist/email.d.ts +5 -3
- package/dist/file-tools-mcp-server.mjs +155 -30
- package/dist/file-tools.d.ts +2 -0
- package/dist/git-exclude.d.ts +9 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +79 -78
- package/dist/ink-select-prompt.d.ts +2 -0
- package/dist/ink-test-render.d.ts +2 -0
- package/dist/memory-service.d.ts +8 -0
- package/dist/next-step-proposer.d.ts +7 -0
- package/dist/plan-drafting-service.d.ts +3 -0
- package/dist/plan-store.d.ts +5 -0
- package/dist/{provider-setup-LFsaTkpf.js → provider-setup-CV3S_TAS.js} +2129 -866
- package/dist/provider-setup-CV3S_TAS.js.map +1 -0
- package/dist/reply-audit.d.ts +67 -0
- package/dist/side-call-policy.d.ts +30 -0
- package/dist/tool-step.d.ts +6 -0
- package/dist/trace-events.d.ts +1 -1
- package/dist/trust-tagging.d.ts +2 -0
- package/dist/{tui-app-BLR-PRFp.js → tui-app-CJq6YaiP.js} +127 -26
- package/dist/tui-app-CJq6YaiP.js.map +1 -0
- package/dist/tui-app.d.ts +35 -0
- package/dist/turn-abort.d.ts +18 -0
- package/dist/turn-intent-classifier.d.ts +8 -0
- package/dist/turn-interpreter.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/package.json +3 -3
- package/dist/bin-CjKpxDxl.js.map +0 -1
- package/dist/provider-setup-LFsaTkpf.js.map +0 -1
- package/dist/tui-app-BLR-PRFp.js.map +0 -1
package/dist/cli-config.d.ts
CHANGED
|
@@ -18,10 +18,13 @@ export declare function isConfigKey(key: string): key is keyof AssistantConfig;
|
|
|
18
18
|
* before this module existed (ASSISTANT_ENABLE_WEB/ASSISTANT_ENABLE_SHELL must be exactly "1").
|
|
19
19
|
*/
|
|
20
20
|
export declare function envOverridesFromProcessEnv(env: NodeJS.ProcessEnv): Partial<AssistantConfig>;
|
|
21
|
+
export declare function findInvalidNumericSettings(config: AssistantConfig): string[];
|
|
21
22
|
/** Thrown by parseConfigValue on a value that doesn't fit the target key's type — cli.ts reports .message and leaves the config unchanged. */
|
|
22
23
|
export declare class ConfigValueParseError extends Error {
|
|
23
24
|
}
|
|
24
25
|
/** Parses a raw `/config set <key> <value>` string into the type AssistantConfig[key] expects. */
|
|
25
26
|
export declare function parseConfigValue(key: keyof AssistantConfig, raw: string): unknown;
|
|
27
|
+
/** A URL with any user:password, and every query-string value, masked, so a credential embedded in proxyUrl is never printed. Text that is not a URL is returned unchanged. */
|
|
28
|
+
export declare function redactUrlCredentials(text: string): string;
|
|
26
29
|
/** Renders /config's full listing as a single multi-line string — cli.ts just console.logs the result. */
|
|
27
30
|
export declare function formatConfigListing(config: AssistantConfig, overriddenKeys: ReadonlySet<keyof AssistantConfig>): string;
|
package/dist/cli-session.d.ts
CHANGED
|
@@ -7,11 +7,12 @@ import { GoalGraphState } from './goal-graph-service.js';
|
|
|
7
7
|
import { UndoLogEntry } from './action-snapshot.js';
|
|
8
8
|
import { UserFact } from './fact-extraction.js';
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Text that came from the model, a tool result, a file or a web page must not reach the terminal raw: an
|
|
11
|
+
* embedded escape sequence can clear the screen, rewrite earlier lines (hiding what an approval prompt is
|
|
12
|
+
* really asking), set the window title or write the clipboard (OSC 52). Newlines and tabs survive; everything
|
|
13
|
+
* that could drive the terminal is removed. Plain text is returned unchanged.
|
|
14
14
|
*/
|
|
15
|
+
export declare function stripTerminalControls(text: string): string;
|
|
15
16
|
export interface CliCommandHelp {
|
|
16
17
|
command: string;
|
|
17
18
|
description: string;
|
package/dist/cli.d.ts
CHANGED
|
@@ -28,6 +28,12 @@ export interface RunCliOptions {
|
|
|
28
28
|
id: string;
|
|
29
29
|
status: TaskStatus;
|
|
30
30
|
}[]) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Called with true when a conversational turn starts being worked on and false when it has finished (in a
|
|
33
|
+
* `finally`, so also after an error) — including turns run as deferred follow-ups, which no `dispatchLine()`
|
|
34
|
+
* promise covers. The TUI's liveness indicator follows this rather than guessing from output events.
|
|
35
|
+
*/
|
|
36
|
+
onTurnBusyChange?: (busy: boolean) => void;
|
|
31
37
|
dataDir?: string;
|
|
32
38
|
configStore?: ConfigStore;
|
|
33
39
|
backend?: FsBackend;
|
|
@@ -97,6 +103,9 @@ export declare function runCli(options?: RunCliOptions): Promise<CliInstance>;
|
|
|
97
103
|
* `runTuiApp` is imported dynamically so a disabled (the default) or non-TTY run never loads Ink
|
|
98
104
|
* at all — `main()` behaves byte-for-byte as it does today in both those cases.
|
|
99
105
|
*/
|
|
106
|
+
/** A reason the assistant refuses to start (invalid configuration, unusable environment): shown to the user verbatim, exit code 1. */
|
|
107
|
+
export declare class StartupError extends Error {
|
|
108
|
+
}
|
|
100
109
|
export declare function main(argv?: readonly string[]): Promise<void>;
|
|
101
110
|
/**
|
|
102
111
|
* True when `entryArg` (a `process.argv[1]` value) refers to the same file as
|
package/dist/cli.js
CHANGED
package/dist/config.d.ts
CHANGED
|
@@ -157,6 +157,8 @@ export interface AssistantConfig {
|
|
|
157
157
|
* check, never the explicit `aielia update` command. CLI-only, like `tuiMode`.
|
|
158
158
|
*/
|
|
159
159
|
updateCheck?: UpdateCheckMode;
|
|
160
|
+
/** Colour theme of the chat-ui/desktop window: 'system' (default, follows the OS), 'dark' or 'light'. The CLI ignores it. */
|
|
161
|
+
theme?: 'system' | 'dark' | 'light';
|
|
160
162
|
/**
|
|
161
163
|
* The project label newly captured project-scoped facts are tagged with, and the one
|
|
162
164
|
* project-scoped facts are filtered against when building a turn's context (see
|
package/dist/debug-log.d.ts
CHANGED
|
@@ -5,12 +5,21 @@
|
|
|
5
5
|
* caller opts into this explicitly (see PersonalAssistantOptions.onDebugLog) knowing it
|
|
6
6
|
* carries real content, not just metadata.
|
|
7
7
|
*
|
|
8
|
+
* `approval_request` is a state-changing action (write/shell/email) staged for the user's yes/no, and
|
|
9
|
+
* `approval_decision` is how that was resolved ('approved' or 'declined'); the executed result of an
|
|
10
|
+
* approved action is the separate `tool_call` entry, so a log reader can reconstruct the whole exchange.
|
|
11
|
+
*
|
|
12
|
+
* `next_steps` is the numbered list of suggestions printed under a reply (one entry per turn that produced any).
|
|
13
|
+
*
|
|
14
|
+
* `note` records a robustness action the loop took on its own (an empty final answer retried, or replaced by a
|
|
15
|
+
* deterministic fallback), so a log reader can see why a reply is not what the model first returned.
|
|
16
|
+
*
|
|
8
17
|
* Split into its own file so both assistant.ts (user_message/assistant_reply kinds, emitted from
|
|
9
18
|
* `turn()`) and agent-loop.ts (tool_call kind, emitted from the ReAct loop) can depend on it
|
|
10
19
|
* without either owning the other.
|
|
11
20
|
*/
|
|
12
21
|
export interface DebugLogEntry {
|
|
13
|
-
kind: 'user_message' | 'assistant_reply' | 'tool_call';
|
|
22
|
+
kind: 'user_message' | 'user_message_queued' | 'assistant_reply' | 'tool_call' | 'approval_request' | 'approval_decision' | 'note' | 'next_steps';
|
|
14
23
|
sessionId: string;
|
|
15
24
|
content: string;
|
|
16
25
|
}
|
package/dist/email.d.ts
CHANGED
|
@@ -53,8 +53,10 @@ export interface ResendSenderOptions {
|
|
|
53
53
|
*/
|
|
54
54
|
export declare function createResendSender(options: ResendSenderOptions): SendEmail;
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
56
|
+
* Human-readable summary of a staged email, for the approval prompt and trace. Kept here
|
|
57
57
|
* (not in agent-loop.ts) so the MCP server (file-tools-mcp-server.mjs) and the CLI can format the
|
|
58
|
-
* same way without importing the loop.
|
|
58
|
+
* same way without importing the loop. The approver must see everything that will be sent: every
|
|
59
|
+
* recipient (Cc/Bcc included, since a hidden Bcc is a silent exfiltration channel) and the whole
|
|
60
|
+
* body, never a truncated preview.
|
|
59
61
|
*/
|
|
60
|
-
export declare function formatEmailApprovalReason(message: Pick<EmailMessage, 'to' | 'subject' | 'body'>): string;
|
|
62
|
+
export declare function formatEmailApprovalReason(message: Pick<EmailMessage, 'to' | 'subject' | 'body' | 'cc' | 'bcc'>): string;
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
* logic without a real MCP client attached over stdio): node file-tools-mcp-server.mjs --test
|
|
87
87
|
*/
|
|
88
88
|
|
|
89
|
-
import { readFile, writeFile, mkdir, readdir, realpath as fsRealpath, mkdtemp, rm } from 'node:fs/promises'
|
|
89
|
+
import { readFile, writeFile, mkdir, readdir, realpath as fsRealpath, mkdtemp, rm, rename } from 'node:fs/promises'
|
|
90
90
|
import { readFileSync } from 'node:fs'
|
|
91
91
|
import { tmpdir } from 'node:os'
|
|
92
92
|
import { randomUUID } from 'node:crypto'
|
|
@@ -111,10 +111,16 @@ export class PathOutsideWorkspaceError extends Error {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// Mirrors file-tools.ts's normalizePath: splits on both `/` and `\`, and treats a Windows drive-letter
|
|
115
|
+
// prefix as absolute — otherwise a backslash-style `..\..\x` is one opaque segment here that never
|
|
116
|
+
// pops, yet the real OS resolves it as a traversal.
|
|
114
117
|
function normalizePath(path) {
|
|
115
|
-
const
|
|
118
|
+
const driveMatch = /^([A-Za-z]:)[\\/]/.exec(path)
|
|
119
|
+
const absolute = driveMatch !== null || path.startsWith('/') || path.startsWith('\\')
|
|
120
|
+
const prefix = driveMatch ? driveMatch[1] : ''
|
|
121
|
+
const rest = driveMatch ? path.slice(driveMatch[0].length) : path
|
|
116
122
|
const segments = []
|
|
117
|
-
for (const part of
|
|
123
|
+
for (const part of rest.split(/[\\/]/)) {
|
|
118
124
|
if (part === '' || part === '.') continue
|
|
119
125
|
if (part === '..') {
|
|
120
126
|
if (segments.length > 0 && segments[segments.length - 1] !== '..') segments.pop()
|
|
@@ -123,12 +129,13 @@ function normalizePath(path) {
|
|
|
123
129
|
segments.push(part)
|
|
124
130
|
}
|
|
125
131
|
}
|
|
126
|
-
return (absolute ? '/' : '') + segments.join('/')
|
|
132
|
+
return prefix + (absolute ? '/' : '') + segments.join('/')
|
|
127
133
|
}
|
|
128
134
|
|
|
129
135
|
export function resolveInWorkspace(workspaceRoot, requestedPath) {
|
|
130
136
|
const root = normalizePath(workspaceRoot)
|
|
131
|
-
const
|
|
137
|
+
const requestedIsAbsolute = /^[A-Za-z]:[\\/]/.test(requestedPath) || requestedPath.startsWith('/') || requestedPath.startsWith('\\')
|
|
138
|
+
const combined = requestedIsAbsolute ? requestedPath : `${root}/${requestedPath}`
|
|
132
139
|
const resolved = normalizePath(combined)
|
|
133
140
|
if (resolved !== root && !resolved.startsWith(`${root}/`)) {
|
|
134
141
|
throw new PathOutsideWorkspaceError(requestedPath)
|
|
@@ -176,7 +183,18 @@ function isEnoent(err) {
|
|
|
176
183
|
// once the current one resolves, instead of dropping it.
|
|
177
184
|
let lastStagedIdThisProcess
|
|
178
185
|
|
|
179
|
-
|
|
186
|
+
// Two staging calls in flight at once (a client may issue tool calls concurrently) would both read the same
|
|
187
|
+
// `lastStagedIdThisProcess` and both link it forward, so one link overwrote the other and that action was never
|
|
188
|
+
// surfaced for approval. Staging is therefore serialized.
|
|
189
|
+
let stagingChain = Promise.resolve()
|
|
190
|
+
|
|
191
|
+
export function stagePendingAction(workspaceRoot, payload) {
|
|
192
|
+
const run = stagingChain.then(() => stagePendingActionNow(workspaceRoot, payload))
|
|
193
|
+
stagingChain = run.then(() => undefined, () => undefined)
|
|
194
|
+
return run
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function stagePendingActionNow(workspaceRoot, payload) {
|
|
180
198
|
const id = randomUUID()
|
|
181
199
|
const dir = `${workspaceRoot}/${PENDING_ACTIONS_DIR}`
|
|
182
200
|
await mkdir(dir, { recursive: true })
|
|
@@ -375,7 +393,18 @@ function doRequestToolGate(port, payload, options) {
|
|
|
375
393
|
// ── Trust boundary for fetched content — mirrors trust-tagging.ts ──────────
|
|
376
394
|
|
|
377
395
|
export function wrapUntrusted(text) {
|
|
378
|
-
|
|
396
|
+
// Same defanging as trust-tagging.ts: content cannot close (or re-open) the boundary itself.
|
|
397
|
+
// String scan, not a regex: this is a structural delimiter check, not a natural-language gate.
|
|
398
|
+
const safe = text
|
|
399
|
+
.split('<')
|
|
400
|
+
.map((part, i) => {
|
|
401
|
+
if (i === 0) return part
|
|
402
|
+
let rest = part.trimStart()
|
|
403
|
+
if (rest.startsWith('/')) rest = rest.slice(1).trimStart()
|
|
404
|
+
return rest.toLowerCase().startsWith('untrusted_external_content') ? '<' + part : '<' + part
|
|
405
|
+
})
|
|
406
|
+
.join('')
|
|
407
|
+
return `<untrusted_external_content>\n${safe}\n</untrusted_external_content>`
|
|
379
408
|
}
|
|
380
409
|
|
|
381
410
|
// Reads the same canonical JSON trust-tagging.ts's own INJECTION_PATTERNS compiles from
|
|
@@ -427,17 +456,46 @@ function isPrivateIPv4(ip) {
|
|
|
427
456
|
if (a === 172 && b >= 16 && b <= 31) return true // RFC1918
|
|
428
457
|
if (a === 192 && b === 168) return true // RFC1918
|
|
429
458
|
if (a === 169 && b === 254) return true // link-local, includes the 169.254.169.254 cloud metadata endpoint
|
|
459
|
+
if (a === 100 && b >= 64 && b <= 127) return true // carrier-grade NAT
|
|
460
|
+
if (a === 198 && (b === 18 || b === 19)) return true // benchmarking
|
|
461
|
+
if (a >= 224) return true // multicast, reserved, broadcast
|
|
430
462
|
if (a === 0) return true // "this network"
|
|
431
463
|
return false
|
|
432
464
|
}
|
|
433
465
|
|
|
466
|
+
// Same logic as web-fetch-core.ts's ipv6Groups/isPrivateIPv6 (kept in sync by hand, see file header).
|
|
467
|
+
// Notably URL parsing serialises `[::ffff:127.0.0.1]` as `::ffff:7f00:1`, which the old dotted-quad-only
|
|
468
|
+
// regex never matched — a literal-IP fetch of a mapped loopback address got through.
|
|
469
|
+
function ipv6Groups(ip) {
|
|
470
|
+
let text = ip
|
|
471
|
+
const v4 = /(\d+\.\d+\.\d+\.\d+)$/.exec(text)
|
|
472
|
+
if (v4) {
|
|
473
|
+
const octets = v4[1].split('.').map(Number)
|
|
474
|
+
if (octets.some((o) => o > 255)) return null
|
|
475
|
+
text = `${text.slice(0, -v4[1].length)}${((octets[0] << 8) | octets[1]).toString(16)}:${((octets[2] << 8) | octets[3]).toString(16)}`
|
|
476
|
+
}
|
|
477
|
+
const halves = text.split('::')
|
|
478
|
+
if (halves.length > 2) return null
|
|
479
|
+
const head = halves[0] === '' ? [] : halves[0].split(':')
|
|
480
|
+
const tail = halves.length === 2 && halves[1] !== '' ? halves[1].split(':') : []
|
|
481
|
+
const missing = 8 - head.length - tail.length
|
|
482
|
+
if (halves.length === 1 ? missing !== 0 : missing < 1) return null
|
|
483
|
+
const groups = [...head, ...Array(halves.length === 2 ? missing : 0).fill('0'), ...tail].map((g) => parseInt(g, 16))
|
|
484
|
+
if (groups.length !== 8 || groups.some((g) => Number.isNaN(g) || g < 0 || g > 0xffff)) return null
|
|
485
|
+
return groups
|
|
486
|
+
}
|
|
487
|
+
|
|
434
488
|
function isPrivateIPv6(ip) {
|
|
435
|
-
const
|
|
436
|
-
if (
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (
|
|
489
|
+
const groups = ipv6Groups(ip.toLowerCase().split('%')[0])
|
|
490
|
+
if (!groups) return true // unparseable: refuse rather than guess
|
|
491
|
+
const embeddedV4 = `${groups[6] >> 8}.${groups[6] & 0xff}.${groups[7] >> 8}.${groups[7] & 0xff}`
|
|
492
|
+
const firstFiveZero = groups.slice(0, 5).every((g) => g === 0)
|
|
493
|
+
if (firstFiveZero && groups[5] === 0 && groups[6] === 0 && (groups[7] === 0 || groups[7] === 1)) return true // :: and ::1
|
|
494
|
+
if (firstFiveZero && (groups[5] === 0xffff || groups[5] === 0)) return isPrivateIPv4(embeddedV4) // mapped / compatible
|
|
495
|
+
if (groups[0] === 0x64 && groups[1] === 0xff9b && groups.slice(2, 6).every((g) => g === 0)) return isPrivateIPv4(embeddedV4) // NAT64
|
|
496
|
+
if ((groups[0] & 0xffc0) === 0xfe80) return true // link-local fe80::/10
|
|
497
|
+
if ((groups[0] & 0xfe00) === 0xfc00) return true // unique local fc00::/7
|
|
498
|
+
if ((groups[0] & 0xff00) === 0xff00) return true // multicast
|
|
441
499
|
return false
|
|
442
500
|
}
|
|
443
501
|
|
|
@@ -457,6 +515,13 @@ export async function assertPublicHttpUrl(url) {
|
|
|
457
515
|
throw new PrivateNetworkTargetError(url, `unsupported scheme "${parsed.protocol}"`)
|
|
458
516
|
}
|
|
459
517
|
|
|
518
|
+
if (parsed.username || parsed.password) {
|
|
519
|
+
throw new PrivateNetworkTargetError(url, 'credentials in the URL are not allowed')
|
|
520
|
+
}
|
|
521
|
+
if (parsed.port && parsed.port !== '80' && parsed.port !== '443') {
|
|
522
|
+
throw new PrivateNetworkTargetError(url, `port "${parsed.port}" is not allowed (only 80/443)`)
|
|
523
|
+
}
|
|
524
|
+
|
|
460
525
|
const hostname = stripBrackets(parsed.hostname)
|
|
461
526
|
if (hostname === 'localhost') {
|
|
462
527
|
throw new PrivateNetworkTargetError(url, '"localhost" resolves to a loopback address')
|
|
@@ -495,22 +560,72 @@ function truncateFetchedText(text) {
|
|
|
495
560
|
return `${text.slice(0, MAX_FETCH_CHARS)}\n\n[... truncated at ${MAX_FETCH_CHARS} characters; the page is longer than shown here ...]`
|
|
496
561
|
}
|
|
497
562
|
|
|
563
|
+
const FETCH_TIMEOUT_MS = 10_000
|
|
564
|
+
// Streamed-read ceiling (a lying/absent Content-Length must not force an unbounded download); mirrors
|
|
565
|
+
// web-fetch-core.ts's DEFAULT_MAX_FETCH_BYTES.
|
|
566
|
+
const MAX_FETCH_BYTES = 4 * MAX_FETCH_CHARS
|
|
567
|
+
|
|
568
|
+
async function readCappedText(response, signal) {
|
|
569
|
+
const reader = response.body?.getReader()
|
|
570
|
+
if (!reader) return (await response.text()).slice(0, MAX_FETCH_BYTES)
|
|
571
|
+
const chunks = []
|
|
572
|
+
let total = 0
|
|
573
|
+
try {
|
|
574
|
+
while (total < MAX_FETCH_BYTES) {
|
|
575
|
+
if (signal.aborted) throw new Error('aborted')
|
|
576
|
+
const { done, value } = await new Promise((resolve, reject) => {
|
|
577
|
+
const onAbort = () => reject(new Error('aborted'))
|
|
578
|
+
signal.addEventListener('abort', onAbort, { once: true })
|
|
579
|
+
reader.read().then(
|
|
580
|
+
(r) => { signal.removeEventListener('abort', onAbort); resolve(r) },
|
|
581
|
+
(e) => { signal.removeEventListener('abort', onAbort); reject(e) },
|
|
582
|
+
)
|
|
583
|
+
})
|
|
584
|
+
if (done) break
|
|
585
|
+
chunks.push(value)
|
|
586
|
+
total += value.byteLength
|
|
587
|
+
}
|
|
588
|
+
} finally {
|
|
589
|
+
void reader.cancel().catch(() => {})
|
|
590
|
+
}
|
|
591
|
+
const merged = new Uint8Array(Math.min(total, MAX_FETCH_BYTES))
|
|
592
|
+
let offset = 0
|
|
593
|
+
for (const chunk of chunks) {
|
|
594
|
+
const room = merged.length - offset
|
|
595
|
+
if (room <= 0) break
|
|
596
|
+
merged.set(chunk.subarray(0, room), offset)
|
|
597
|
+
offset += Math.min(chunk.length, room)
|
|
598
|
+
}
|
|
599
|
+
return new TextDecoder('utf-8', { fatal: false }).decode(merged)
|
|
600
|
+
}
|
|
601
|
+
|
|
498
602
|
export async function fetchUrlSafely(url) {
|
|
499
603
|
let currentUrl = url
|
|
500
604
|
for (let redirect = 0; redirect <= MAX_REDIRECTS; redirect++) {
|
|
501
605
|
await assertPublicHttpUrl(currentUrl)
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
606
|
+
// One timer bounds the headers and the body read, as in web-fetch-core.ts.
|
|
607
|
+
const controller = new AbortController()
|
|
608
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)
|
|
609
|
+
try {
|
|
610
|
+
const response = await fetch(currentUrl, { redirect: 'manual', signal: controller.signal })
|
|
611
|
+
if (response.status >= 300 && response.status < 400) {
|
|
612
|
+
const location = response.headers.get('location')
|
|
613
|
+
await response.body?.cancel().catch(() => {})
|
|
614
|
+
if (!location) throw new Error(`Redirect response from "${currentUrl}" had no Location header`)
|
|
615
|
+
currentUrl = new URL(location, currentUrl).toString()
|
|
616
|
+
continue
|
|
617
|
+
}
|
|
618
|
+
const body = await readCappedText(response, controller.signal)
|
|
619
|
+
// Same rule as web-tools.ts's fetchUrlSafely: a 5xx is a server fault, not page content — an error the model and the
|
|
620
|
+
// parent's ControlState can see (a 4xx page stays content).
|
|
621
|
+
if (response.status >= 500) throw new Error(`HTTP ${response.status} from ${url}: ${body.slice(0, 200).trim()}`)
|
|
622
|
+
return truncateFetchedText(body)
|
|
623
|
+
} catch (err) {
|
|
624
|
+
if (controller.signal.aborted) throw new Error(`Timed out fetching "${currentUrl}" after ${FETCH_TIMEOUT_MS}ms`)
|
|
625
|
+
throw err
|
|
626
|
+
} finally {
|
|
627
|
+
clearTimeout(timer)
|
|
508
628
|
}
|
|
509
|
-
const body = await response.text()
|
|
510
|
-
// Same rule as web-tools.ts's fetchUrlSafely: a 5xx is a server fault, not page content — an error the model and the
|
|
511
|
-
// parent's ControlState can see (a 4xx page stays content).
|
|
512
|
-
if (response.status >= 500) throw new Error(`HTTP ${response.status} from ${url}: ${body.slice(0, 200).trim()}`)
|
|
513
|
-
return truncateFetchedText(body)
|
|
514
629
|
}
|
|
515
630
|
throw new Error(`Too many redirects while fetching "${url}"`)
|
|
516
631
|
}
|
|
@@ -623,14 +738,24 @@ async function readRemindersFile(remindersFile) {
|
|
|
623
738
|
|
|
624
739
|
async function writeRemindersFile(remindersFile, reminders) {
|
|
625
740
|
await mkdir(remindersFile.slice(0, remindersFile.lastIndexOf('/')), { recursive: true })
|
|
626
|
-
|
|
741
|
+
// Write-then-rename so a crash mid-write cannot leave a truncated file that fails to parse on every later read.
|
|
742
|
+
const tmp = `${remindersFile}.tmp-${randomUUID()}`
|
|
743
|
+
await writeFile(tmp, JSON.stringify({ key: REMINDERS_KEY, value: reminders }), 'utf-8')
|
|
744
|
+
await rename(tmp, remindersFile)
|
|
627
745
|
}
|
|
628
746
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
747
|
+
// Concurrent create_reminder calls would each read the same list and the last write would drop the others.
|
|
748
|
+
let reminderChain = Promise.resolve()
|
|
749
|
+
|
|
750
|
+
export function createReminder(remindersFile, rawText) {
|
|
751
|
+
const run = reminderChain.then(async () => {
|
|
752
|
+
const reminders = await readRemindersFile(remindersFile)
|
|
753
|
+
const record = { id: randomUUID(), rawText, createdAt: new Date().toISOString(), dueAt: null, done: false }
|
|
754
|
+
await writeRemindersFile(remindersFile, [...reminders, record])
|
|
755
|
+
return record
|
|
756
|
+
})
|
|
757
|
+
reminderChain = run.then(() => undefined, () => undefined)
|
|
758
|
+
return run
|
|
634
759
|
}
|
|
635
760
|
|
|
636
761
|
async function main() {
|
package/dist/file-tools.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export declare function assertRealPathInWorkspace(backend: FsBackend, workspaceR
|
|
|
33
33
|
* "nothing to diff against" signal `applyPendingAction`'s own `previousContent` uses.
|
|
34
34
|
*/
|
|
35
35
|
export declare function readCurrentFileContent(backend: FsBackend, workspaceRoot: string, requestedPath: string): Promise<string | undefined>;
|
|
36
|
+
/** A workspace file path in one comparable form: relative to the workspace root, no leading "./" or "/". Absolute paths inside the workspace and relative ones name the same file. */
|
|
37
|
+
export declare function workspaceRelative(workspaceRoot: string, requestedPath: string): string;
|
|
36
38
|
export declare const READ_FILE_TOOL: ToolDefinition;
|
|
37
39
|
export declare const LIST_DIRECTORY_TOOL: ToolDefinition;
|
|
38
40
|
export declare const WRITE_FILE_TOOL: ToolDefinition;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Directories Aielia keeps inside the workspace for its own state. */
|
|
2
|
+
export declare const AIELIA_STATE_DIRS: readonly [".pending-actions", ".undo-log", ".buildaharness"];
|
|
3
|
+
/**
|
|
4
|
+
* Keeps Aielia's own state directories out of version control when the workspace is inside a git repo, by adding them to
|
|
5
|
+
* `<gitdir>/info/exclude` — the per-clone ignore file git itself provides for this. The user's tracked `.gitignore` is never
|
|
6
|
+
* touched, and a non-git directory is left exactly as it was. Idempotent and best-effort: returns the entries it added and
|
|
7
|
+
* never throws (a read-only .git must not stop the assistant from starting).
|
|
8
|
+
*/
|
|
9
|
+
export declare function excludeAieliaStateFromGit(workspaceRoot: string): string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export { braveSearch } from './web-search-provider.js';
|
|
|
34
34
|
export type { BraveSearchOptions } from './web-search-provider.js';
|
|
35
35
|
export { buildClaudePrompt, parseClaudeCliOutput, ALREADY_STAGED_ACTION_TOOL, stagedActionInput } from './claude-cli-prompt.js';
|
|
36
36
|
export type { ParsedClaudeCliOutput } from './claude-cli-prompt.js';
|
|
37
|
-
export { stripMcpToolPrefix, summarizeToolStep } from './tool-step.js';
|
|
37
|
+
export { stripMcpToolPrefix, summarizeToolStep, summarizeSettledToolStep } from './tool-step.js';
|
|
38
38
|
export type { AssistantToolStep } from './tool-step.js';
|
|
39
39
|
export type { DecomposedTaskSpec } from './decomposition-classifier.js';
|
|
40
40
|
export { detectHomogeneousBatchList } from './batch-list-detector.js';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
-
import {
|
|
5
|
-
import { A, a, b, C, c, D, e, f, g, h, i, j, k, l, m, o, F, p, I, L, q, r, s, t, u, v, w, x, M, P, y, z, B, R, G, S, H, J, W, K, N, O, Q, T, U, V, X, Y, Z, _, $, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ac, ad, ae, ag, ah, ai, aj, ak, al, am,
|
|
4
|
+
import { bj as parseModelJson, a0 as classifyRiskLexical } from "./provider-setup-CV3S_TAS.js";
|
|
5
|
+
import { A, a, b, C, c, D, e, f, g, h, i, j, k, l, m, o, F, p, I, L, q, r, s, t, u, v, w, x, M, P, y, z, B, R, G, S, H, J, W, K, N, O, Q, T, U, V, X, Y, Z, _, $, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ac, ad, ae, ag, ah, ai, aj, ak, al, am, ao, aq, ar, as, at, av, aw, ay, az, aB, aC, aE, aF, aI, aK, aL, aM, aN, aP, aQ, aR, aS, aT, aU, aW, aX, aY, aZ, a_, a$, b0, b1, b2, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf, bh, bk, bl, bm, bp, bq, br, bs, bt, bu, bv, bw, bx, by, bz, bA, bB, bC, bD, bE, bF, bH, bI, bK, bM, bN, bO, bP, bQ, bR, bS, bT } from "./provider-setup-CV3S_TAS.js";
|
|
6
6
|
import { DEFAULT_LAYER_POLICY_MODE, MAX_OPTIONS_PER_QUESTION, MAX_QUESTIONS_PER_BATCH, MIN_OPTIONS_PER_QUESTION, validateAskResponse } from "@buildaharness/harness";
|
|
7
7
|
const PLAN_SCHEMA = {
|
|
8
8
|
type: "object",
|
|
@@ -217,82 +217,83 @@ export {
|
|
|
217
217
|
ak as executeShellTool,
|
|
218
218
|
al as executeWebTool,
|
|
219
219
|
am as explicitEnvOverride,
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
220
|
+
ao as findLayer,
|
|
221
|
+
aq as formatCostSummary,
|
|
222
|
+
ar as formatDoctorReport,
|
|
223
|
+
as as formatEmailApprovalReason,
|
|
224
|
+
at as formatGoalGraphState,
|
|
225
|
+
av as formatLayerListing,
|
|
226
|
+
aw as formatMemoryArchive,
|
|
227
|
+
ay as formatMemoryHistory,
|
|
228
|
+
az as formatMemoryInjection,
|
|
229
|
+
aB as formatMemoryStatus,
|
|
230
|
+
aC as formatMemorySummary,
|
|
231
|
+
aE as formatPlanProgress,
|
|
232
|
+
aF as formatSearchResults,
|
|
233
|
+
aI as formatTranscriptMarkdown,
|
|
234
|
+
aK as getProviderSetup,
|
|
235
|
+
aL as harnessGatePolicy,
|
|
236
|
+
aM as injectionDetectionEnabled,
|
|
237
|
+
aN as isAdaptivePolicyEnabled,
|
|
238
|
+
aP as isGoalGraphEnabled,
|
|
239
|
+
aQ as isGoalGraphSuggestEnabled,
|
|
240
|
+
aR as isLayerPolicyMode,
|
|
241
|
+
aS as isLikelyEmailAddress,
|
|
242
|
+
aT as isPlanGraphEnabled,
|
|
243
|
+
aU as isPolicyRecordingEnabled,
|
|
244
|
+
aW as isToggleable,
|
|
245
|
+
aX as lexicalActive,
|
|
246
|
+
aY as lexicalOffEnvValue,
|
|
247
|
+
aZ as listTemplateNames,
|
|
248
|
+
a_ as loadActivePlan,
|
|
249
|
+
a$ as loadPendingAction,
|
|
250
|
+
b0 as loadTemplate,
|
|
251
|
+
b1 as lowerConfidenceSourceLines,
|
|
252
|
+
b2 as matchTemplateIfConfident,
|
|
253
|
+
b4 as memoryStatusChecks,
|
|
254
|
+
b5 as nextPendingTask,
|
|
255
|
+
b6 as nodeDisplayName,
|
|
256
|
+
b7 as nodeToLayer,
|
|
257
|
+
b8 as normalizeAmbiguityGuardMode,
|
|
258
|
+
b9 as normalizeAskMode,
|
|
259
|
+
ba as normalizeGoalGraphMode,
|
|
260
|
+
bb as normalizeGoalGraphSuggestMode,
|
|
261
|
+
bc as normalizeLayerPolicyMode,
|
|
262
|
+
bd as normalizeOneLoopMode,
|
|
263
|
+
be as normalizePlanGraphMode,
|
|
264
|
+
bf as normalizePlanMode,
|
|
265
|
+
bh as parseClaudeCliOutput,
|
|
266
|
+
bk as pickTemplateForTask,
|
|
267
|
+
bl as planCompletionPct,
|
|
268
|
+
bm as planToSnapshot,
|
|
269
|
+
bp as resolveAmbiguityGuardMode,
|
|
270
|
+
bq as resolveAskMode,
|
|
271
|
+
br as resolveConfig,
|
|
272
|
+
bs as resolveEscalationPlan,
|
|
273
|
+
bt as resolveGoalGraphMode,
|
|
274
|
+
bu as resolveGoalGraphSuggestMode,
|
|
275
|
+
bv as resolveInWorkspace,
|
|
276
|
+
bw as resolveLayerPolicyMode,
|
|
277
|
+
bx as resolveLayerPolicyModeFromConfig,
|
|
278
|
+
by as resolveLexicalMode,
|
|
279
|
+
bz as resolveLexicalOff,
|
|
280
|
+
bA as resolveMemoryWriteMode,
|
|
281
|
+
bB as resolveOneLoopMode,
|
|
282
|
+
bC as resolvePlanGraphMode,
|
|
283
|
+
bD as resolvePlanMode,
|
|
284
|
+
bE as sanitizeLayerChoices,
|
|
285
|
+
bF as savePlan,
|
|
286
|
+
bH as stagePendingAction,
|
|
287
|
+
bI as stagedActionInput,
|
|
288
|
+
bK as stripMcpToolPrefix,
|
|
289
|
+
bM as summarizeSettledToolStep,
|
|
290
|
+
bN as summarizeToolStep,
|
|
291
|
+
bO as syncHarnessLexicalEnv,
|
|
292
|
+
bP as testApiKey,
|
|
293
|
+
bQ as turnPolicyBudget,
|
|
294
|
+
bR as updatePlanFromRun,
|
|
294
295
|
validateAskResponse,
|
|
295
|
-
|
|
296
|
-
|
|
296
|
+
bS as validateConfig,
|
|
297
|
+
bT as withLayerChoice
|
|
297
298
|
};
|
|
298
299
|
//# sourceMappingURL=index.js.map
|
|
@@ -7,6 +7,8 @@ export interface SelectPromptProps {
|
|
|
7
7
|
/** Called once, with the chosen option's `key` — either Enter on the highlighted row, or a direct keystroke matching an option's `key`. */
|
|
8
8
|
onSubmit: (key: string) => void;
|
|
9
9
|
isActive?: boolean;
|
|
10
|
+
/** Keys arriving within this many ms of the prompt appearing are ignored: a key typed ahead for the previous turn (an Enter, a 'y') must not answer a question the user has not seen yet. Default 0 (no guard). */
|
|
11
|
+
armDelayMs?: number;
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Phase 7 of the internal plan: an arrow-key + numbered/shortcut-key
|
|
@@ -17,6 +17,8 @@ export interface TestInkInstance {
|
|
|
17
17
|
lastFrame: () => string | undefined;
|
|
18
18
|
/** See `TestStdout.fullOutput`'s doc comment — needed to assert on `<Static>` content. */
|
|
19
19
|
fullOutput: () => string;
|
|
20
|
+
/** How many writes (frames and control sequences) the render has produced so far — constant while the UI is idle. */
|
|
21
|
+
writeCount: () => number;
|
|
20
22
|
rerender: (tree: ReactElement) => void;
|
|
21
23
|
unmount: () => void;
|
|
22
24
|
}
|
package/dist/memory-service.d.ts
CHANGED
|
@@ -350,7 +350,13 @@ export declare class MemoryService {
|
|
|
350
350
|
* (memory-write-gate.test.ts) asserts this is the only call in the file that writes the durable key.
|
|
351
351
|
*/
|
|
352
352
|
private commitDurable;
|
|
353
|
+
/** Tail of the in-process chain serialising the audit log's read-modify-write (concurrent appends lost entries and reused seq numbers). */
|
|
354
|
+
private auditChain;
|
|
355
|
+
/** Same for the usage flush's read-modify-write of the fact stores. */
|
|
356
|
+
private flushChain;
|
|
357
|
+
private serialized;
|
|
353
358
|
private appendAudit;
|
|
359
|
+
private appendAuditNow;
|
|
354
360
|
/** `/memory history` — newest last; the last `limit` entries. */
|
|
355
361
|
getAuditLog(limit?: number): Promise<AuditEntry[]>;
|
|
356
362
|
/**
|
|
@@ -487,6 +493,7 @@ export declare class MemoryService {
|
|
|
487
493
|
restoreArchivedFact(listIndex: number, sessionId?: string): Promise<UserFact | undefined>;
|
|
488
494
|
/** Writes the batched `injectedCount`/`lastInjectedAt` updates collected by budgeted renders, to whichever store(s) hold each fact. A no-op (no writes) when nothing was rendered. */
|
|
489
495
|
private flushInjectionUsage;
|
|
496
|
+
private flushInjectionUsageNow;
|
|
490
497
|
/**
|
|
491
498
|
* `/memory confirm <n>` — promotes the nth (1-based in `/memory`'s display, 0-based here) entry
|
|
492
499
|
* in PENDING_CONFIRMATION_KEY to DURABLE_FACTS_KEY. Runs the same promotion-time check as
|
|
@@ -535,6 +542,7 @@ export declare class MemoryService {
|
|
|
535
542
|
forgetArchived(index: number): Promise<UserFact | undefined>;
|
|
536
543
|
/** Strips one fact's pre/post-images from the audit log (entries stay, marked `erased`, and can no longer be undone) and unlinks `supersedes` references to it. */
|
|
537
544
|
private eraseFromAuditLog;
|
|
545
|
+
private eraseFromAuditLogNow;
|
|
538
546
|
/** Read-only health snapshot (`/doctor`, memory panel). Never touches usage counters or the last-injection record. */
|
|
539
547
|
getMemoryStatus(sessionId: string): Promise<MemoryStatus>;
|
|
540
548
|
/** M3 (D4): `/memory forget digest <id>` or, with no id, every digest. Returns how many were removed. */
|