@buildaharness/aielia 0.3.0 → 0.3.2
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 +30 -7
- package/dist/agent-loop.d.ts +8 -8
- package/dist/ask-clarification-service.d.ts +1 -1
- package/dist/ask-mode-flag.d.ts +1 -1
- package/dist/assistant-session.d.ts +1 -1
- package/dist/assistant-types.d.ts +3 -3
- package/dist/assistant.d.ts +12 -12
- package/dist/batch-research-fixtures.d.ts +1 -1
- package/dist/benchmark-injected-failure.d.ts +1 -1
- package/dist/bin-CqkqT6KK.js +2107 -0
- package/dist/bin-CqkqT6KK.js.map +1 -0
- package/dist/bin-sea-bootstrap.d.ts +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/claude-cli-llm-client.d.ts +2 -2
- package/dist/cli-args.d.ts +20 -0
- package/dist/cli-icons.d.ts +28 -0
- package/dist/{cli-session-D0uLdT2A.js → cli-session-BiX2DOni.js} +550 -121
- package/dist/cli-session-BiX2DOni.js.map +1 -0
- package/dist/cli.d.ts +31 -1
- package/dist/cli.js +1 -1691
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +14 -6
- package/dist/contradiction-checker.d.ts +1 -1
- package/dist/diff-format.d.ts +16 -0
- package/dist/execution-mode.d.ts +1 -1
- package/dist/fact-extraction.d.ts +4 -4
- package/dist/failure-mode-matcher.d.ts +1 -1
- package/dist/file-tools-mcp-server.mjs +2 -2
- package/dist/file-tools.d.ts +10 -2
- package/dist/harness-bridge.d.ts +4 -4
- package/dist/index.js +68 -68
- package/dist/index.js.map +1 -1
- package/dist/ink-select-prompt.d.ts +21 -0
- package/dist/markdown-line.d.ts +42 -0
- package/dist/mcp-server-asset.d.ts +24 -0
- package/dist/memory-service.d.ts +2 -2
- package/dist/network-containment.d.ts +1 -1
- package/dist/node-display-names.d.ts +1 -1
- package/dist/one-loop-flag.d.ts +1 -1
- package/dist/plan-drafting-service.d.ts +1 -1
- package/dist/plan-drafting.d.ts +4 -4
- package/dist/plan-mode-flag.d.ts +1 -1
- package/dist/plan-sketch-service.d.ts +1 -1
- package/dist/plan-sketch.d.ts +1 -1
- package/dist/plan-store.d.ts +2 -2
- package/dist/plan-templates/index.d.ts +1 -1
- package/dist/plan-verification.d.ts +1 -1
- package/dist/scripted-llm-client.d.ts +1 -1
- package/dist/sea-cache.d.ts +9 -0
- package/dist/self-update.d.ts +101 -0
- package/dist/shell-tools.d.ts +1 -1
- package/dist/spike.js.map +1 -1
- package/dist/tool-control-plane.d.ts +1 -1
- package/dist/tool-policy.d.ts +1 -1
- package/dist/tool-step.d.ts +10 -0
- package/dist/trace-events.d.ts +5 -5
- package/dist/trust-tagging.d.ts +1 -1
- package/dist/{tui-app-DxLPSGh2.js → tui-app-4_TrzESz.js} +176 -20
- package/dist/tui-app-4_TrzESz.js.map +1 -0
- package/dist/tui-app.d.ts +34 -9
- package/dist/tui-mode-flag.d.ts +1 -1
- package/dist/turn-intent-classifier.d.ts +5 -5
- package/dist/turn-policy.d.ts +1 -1
- package/dist/update-check-flag.d.ts +11 -0
- package/dist/version.d.ts +6 -0
- package/package.json +9 -5
- package/dist/cli-session-D0uLdT2A.js.map +0 -1
- package/dist/tui-app-DxLPSGh2.js.map +0 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export declare const MANIFEST_URL = "https://myaielia.com/aielia-latest.json";
|
|
2
|
+
export declare const RELEASES_API_URL = "https://api.github.com/repos/3IVIS/buildaharness/releases?per_page=50";
|
|
3
|
+
export declare const RELEASE_TAG_PREFIX = "aielia-v";
|
|
4
|
+
export declare const CHECK_INTERVAL_MS: number;
|
|
5
|
+
export interface ReleaseAsset {
|
|
6
|
+
url: string;
|
|
7
|
+
/** From the manifest — the trusted hash (a different origin than the binary's github.com URL). Absent on the GitHub-API fallback. */
|
|
8
|
+
sha256?: string;
|
|
9
|
+
size?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ReleaseInfo {
|
|
12
|
+
tag: string;
|
|
13
|
+
version: string;
|
|
14
|
+
/** Keyed `<process.platform>-<process.arch>`, e.g. `linux-x64`, `darwin-arm64`, `win32-x64`. */
|
|
15
|
+
assets: Record<string, ReleaseAsset>;
|
|
16
|
+
source: 'manifest' | 'github';
|
|
17
|
+
}
|
|
18
|
+
export interface UpdateCheckCache {
|
|
19
|
+
checkedAt: number;
|
|
20
|
+
latestVersion?: string;
|
|
21
|
+
latestTag?: string;
|
|
22
|
+
/** ETag of the GitHub Releases API response, for conditional re-requests. */
|
|
23
|
+
etag?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface SelfUpdateOptions {
|
|
26
|
+
fetchFn?: typeof fetch;
|
|
27
|
+
manifestUrl?: string;
|
|
28
|
+
releasesApiUrl?: string;
|
|
29
|
+
/** Test-only: permit http:// manifest/asset URLs (a local mock server). Production never sets this. */
|
|
30
|
+
allowInsecureHttp?: boolean;
|
|
31
|
+
now?: () => number;
|
|
32
|
+
/** Defaults to `~/.buildaharness/personal-assistant`. */
|
|
33
|
+
dataDir?: string;
|
|
34
|
+
currentVersion?: string;
|
|
35
|
+
platform?: NodeJS.Platform;
|
|
36
|
+
arch?: string;
|
|
37
|
+
/** Path of the running binary. Defaults to `process.execPath` (which, in a SEA, is the binary itself). */
|
|
38
|
+
execPath?: string;
|
|
39
|
+
/** Defaults to whether `node:sea` reports a SEA. */
|
|
40
|
+
isSea?: boolean;
|
|
41
|
+
log?: (line: string) => void;
|
|
42
|
+
}
|
|
43
|
+
/** Numeric `major.minor.patch` comparison; a `-prerelease` suffix sorts below the same release. Returns <0, 0, >0. */
|
|
44
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
45
|
+
/** Throws on anything but https:// (unless the test-only override is set) — applied to the manifest URL and every asset URL. */
|
|
46
|
+
export declare function assertHttps(url: string, allowInsecureHttp?: boolean): void;
|
|
47
|
+
/**
|
|
48
|
+
* Whether the passive check may make its unsolicited outbound request at all. Off for scripted/CI
|
|
49
|
+
* runs (stdin not a TTY, or `ASSISTANT_NON_INTERACTIVE_APPROVAL` set) and when opted out via
|
|
50
|
+
* `ASSISTANT_UPDATE_CHECK=disabled` / `/config set updateCheck disabled`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function shouldRunPassiveUpdateCheck(input: {
|
|
53
|
+
updateCheck: 'enabled' | 'disabled' | undefined;
|
|
54
|
+
env: NodeJS.ProcessEnv;
|
|
55
|
+
stdinIsTty: boolean;
|
|
56
|
+
}): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Resolves the latest release: the product-scoped manifest first, the GitHub Releases API (filtered
|
|
59
|
+
* to `aielia-v*` tags, ETag-cached) as fallback. A plain GET each — no query string, no
|
|
60
|
+
* identifiers, default User-Agent only. Returns `notModified` when the API answered 304 to the
|
|
61
|
+
* cached ETag; the caller then keeps the cached version.
|
|
62
|
+
*/
|
|
63
|
+
export declare function fetchLatestRelease(options: SelfUpdateOptions, etag?: string): Promise<{
|
|
64
|
+
release: ReleaseInfo;
|
|
65
|
+
etag?: string;
|
|
66
|
+
} | {
|
|
67
|
+
notModified: true;
|
|
68
|
+
}>;
|
|
69
|
+
export interface UpdateCheckResult {
|
|
70
|
+
latestVersion: string;
|
|
71
|
+
latestTag?: string;
|
|
72
|
+
updateAvailable: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The passive check: 24h-throttled via `<dataDir>/update-check.json`. Never throws — any failure
|
|
76
|
+
* (offline, malformed manifest, …) resolves to `null`, since an unsolicited background check must
|
|
77
|
+
* never disturb a session. Callers are responsible for gating it with `shouldRunPassiveUpdateCheck`
|
|
78
|
+
* first and for not awaiting it before the prompt renders.
|
|
79
|
+
*/
|
|
80
|
+
export declare function checkForUpdate(options?: SelfUpdateOptions): Promise<UpdateCheckResult | null>;
|
|
81
|
+
/** One-line notice; the instruction branches on how this copy was installed. */
|
|
82
|
+
export declare function updateAvailableNotice(latestVersion: string, currentVersion: string, isSea: boolean): string;
|
|
83
|
+
/** `isSea` the way the rest of the CLI decides it — false under npm/tsx, where nothing can be self-replaced. */
|
|
84
|
+
export declare function isRunningAsSea(): boolean;
|
|
85
|
+
/** Removes leftovers of a previous update (the Windows `.old` rename-aside copy, an interrupted temp download). Never throws. */
|
|
86
|
+
export declare function cleanupStaleUpdateFiles(execPath?: string): void;
|
|
87
|
+
/**
|
|
88
|
+
* Replaces the binary at `execPath` with the already-verified file at `newFile` (same directory).
|
|
89
|
+
* Unix: `rename` over the running binary — POSIX lets a still-open inode be replaced. Windows can't
|
|
90
|
+
* overwrite a running exe, so the current one is renamed aside to `.old` first (cleaned up on the
|
|
91
|
+
* next launch by `cleanupStaleUpdateFiles`), rolling back if the second rename fails.
|
|
92
|
+
*/
|
|
93
|
+
export declare function replaceBinary(execPath: string, newFile: string, platform?: NodeJS.Platform): void;
|
|
94
|
+
/**
|
|
95
|
+
* `aielia update [--dry-run]`. Returns the process exit code (0 = up to date / updated / dry-run
|
|
96
|
+
* reported; 1 = refused or failed) and reports through `options.log`. Always does a fresh check —
|
|
97
|
+
* never the throttled cache — and works regardless of the `updateCheck` opt-out.
|
|
98
|
+
*/
|
|
99
|
+
export declare function runUpdateCommand(input?: {
|
|
100
|
+
dryRun?: boolean;
|
|
101
|
+
} & SelfUpdateOptions): Promise<number>;
|
package/dist/shell-tools.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ export interface ShellToolsContext extends ShellStagingContext {
|
|
|
60
60
|
* Hostnames an approved command's HTTP(S)_PROXY traffic may reach (exact match or subdomain) —
|
|
61
61
|
* see network-containment.ts and shell-executor.ts's networkContainmentEnv. Passed through to
|
|
62
62
|
* executeCommand at apply time, same as timeoutMs. Undefined/empty denies all network access,
|
|
63
|
-
* the safe default (Decision 6,
|
|
63
|
+
* the safe default (Decision 6, the internal plan Phase 4).
|
|
64
64
|
*/
|
|
65
65
|
networkAllowlist?: string[];
|
|
66
66
|
executeCommand: ShellCommandExecutor;
|
package/dist/spike.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spike.js","sources":["../src/tui-spike.tsx"],"sourcesContent":["// Phase 0 feasibility spike for
|
|
1
|
+
{"version":3,"file":"spike.js","sources":["../src/tui-spike.tsx"],"sourcesContent":["// Phase 0 feasibility spike for the internal plan.\n// Throwaway — not part of the shipped feature. Delete once Phase 5 confirms Ink\n// works over a real live terminal (raw-mode input + redraw), along with its\n// temporary build entry in vite.config.lib.ts.\nimport React, { useState } from 'react'\nimport { Box, Text, render, useInput } from 'ink'\n\nfunction Spike() {\n const [lastKey, setLastKey] = useState('(none yet)')\n\n useInput((input, key) => {\n if (key.escape || (key.ctrl && input === 'c')) {\n process.exit(0)\n }\n setLastKey(key.return ? '<enter>' : input || JSON.stringify(key))\n })\n\n return (\n <Box borderStyle=\"round\" padding={1}>\n <Text>Ink spike — hello world. Last key: {lastKey}</Text>\n </Box>\n )\n}\n\nrender(<Spike />)\n"],"names":[],"mappings":";;;AAOA,SAAS,QAAQ;AACf,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,YAAY;AAEnD,WAAS,CAAC,OAAO,QAAQ;AACvB,QAAI,IAAI,UAAW,IAAI,QAAQ,UAAU,KAAM;AAC7C,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,eAAW,IAAI,SAAS,YAAY,SAAS,KAAK,UAAU,GAAG,CAAC;AAAA,EAClE,CAAC;AAED,6BACG,KAAA,EAAI,aAAY,SAAQ,SAAS,GAChC,+BAAC,MAAA,EAAK,UAAA;AAAA,IAAA;AAAA,IAAoC;AAAA,EAAA,EAAA,CAAQ,EAAA,CACpD;AAEJ;AAEA,OAAO,oBAAC,SAAM,CAAE;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { WorldModel, EvidenceStore, Diagnostics, FailureDiagnostics, ControlState, ToolAvailability } from '@buildaharness/harness';
|
|
2
2
|
/**
|
|
3
|
-
* Phase 4c of
|
|
3
|
+
* Phase 4c of the internal plan — a live, per-turn
|
|
4
4
|
* harness ControlState, so tool-policy.ts's evaluateToolPolicy() can be given a real
|
|
5
5
|
* controlState instead of the pre-evidence-baseline `undefined` every call site passed before
|
|
6
6
|
* this phase. Deliberately builds on the fallback control-plane design (no packages/harness core
|
package/dist/tool-policy.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RiskLevel } from './turn-intent-classifier.js';
|
|
2
2
|
/**
|
|
3
|
-
* Phase 4 of
|
|
3
|
+
* Phase 4 of the internal plan: the deterministic,
|
|
4
4
|
* authoritative decision point for whether a specific tool call may proceed — replacing "advisory
|
|
5
5
|
* LLM classification, checked after the fact" with "deterministic policy informed by real harness
|
|
6
6
|
* state, checked before execution" (the substantive fix both external architecture critiques
|
package/dist/tool-step.d.ts
CHANGED
|
@@ -8,6 +8,16 @@ import { ToolStepEvent } from '@buildaharness/runtime';
|
|
|
8
8
|
*/
|
|
9
9
|
export interface AssistantToolStep extends ToolStepEvent {
|
|
10
10
|
summary: string;
|
|
11
|
+
/**
|
|
12
|
+
* Set only on a second, follow-up `onToolStep` call fired immediately after `tool-policy.ts`'s
|
|
13
|
+
* `evaluateToolPolicy()` denies this exact call (see `agent-loop.ts`'s `checkToolPolicy`) — the
|
|
14
|
+
* first call (this field absent) already reported the proposal itself. Without this, a denied
|
|
15
|
+
* read-only tool call was invisible to the user: the model saw a "Denied by tool policy: …"
|
|
16
|
+
* result, but nothing ever told the person watching the transcript that the call didn't happen
|
|
17
|
+
* (report finding: "no confirmation, no denial message, nothing to distinguish 'the policy
|
|
18
|
+
* allowed this' from 'there was no policy at all'").
|
|
19
|
+
*/
|
|
20
|
+
deniedReason?: string;
|
|
11
21
|
}
|
|
12
22
|
/**
|
|
13
23
|
* Strips an MCP-qualified tool name (`mcp__<server>__<tool>`, how Claude Code CLI surfaces
|
package/dist/trace-events.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export type TraceEvent = {
|
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* One of the harness's 11 layers did (or explicitly skipped) real work this step —
|
|
50
|
-
* see
|
|
50
|
+
* see the internal plan Phase 2/3.1. `layer` is a stable slug
|
|
51
51
|
* ('world_model' | 'evidence_reasoning' | 'hypothesis' | 'contradiction' | 'diagnostics' |
|
|
52
52
|
* 'control_state' | 'planning' | 'execution' | 'verification' | 'recovery' | 'reviewer_pass'),
|
|
53
53
|
* not a free-text name, so a "Why?"/`/layers` renderer can key off it directly.
|
|
@@ -71,7 +71,7 @@ export type TraceEvent = {
|
|
|
71
71
|
failedAttempts: number;
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
|
-
* Phase 4 of
|
|
74
|
+
* Phase 4 of the internal plan — see
|
|
75
75
|
* execution-mode.ts's own doc comment for each mode's guarantee level.
|
|
76
76
|
*/
|
|
77
77
|
| {
|
|
@@ -93,14 +93,14 @@ export type TraceEvent = {
|
|
|
93
93
|
* Which proposer drove this turn — 'posthoc' (flag OFF, or a trivial / no-tool turn),
|
|
94
94
|
* 'flat-oneloop', or 'batch-oneloop' (both flag ON). Emitted once per turn from runTurn, right
|
|
95
95
|
* after `useOneLoop` is resolved. See AssistantTurnResult.proposerKind and
|
|
96
|
-
*
|
|
96
|
+
* the internal plan phase B1 for why this exists.
|
|
97
97
|
*/
|
|
98
98
|
| {
|
|
99
99
|
kind: 'proposer_selected';
|
|
100
100
|
proposerKind: ProposerKind;
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
|
-
* P9 of
|
|
103
|
+
* P9 of the internal plan — the lightweight plan-sketch delegate
|
|
104
104
|
* (PlanSketchService) ran. `requestPreview` is truncated to stay name/status-only per this
|
|
105
105
|
* type's own doc comment, not the full request text. Distinct from `plan_classified`/
|
|
106
106
|
* `plan_updated`, which are about the stateful drafting/approval machinery (PlanDraftingService)
|
|
@@ -111,7 +111,7 @@ export type TraceEvent = {
|
|
|
111
111
|
requestPreview: string;
|
|
112
112
|
}
|
|
113
113
|
/**
|
|
114
|
-
* P10 of
|
|
114
|
+
* P10 of the internal plan — a write_file/run_shell_command/send_email
|
|
115
115
|
* proposal was auto-applied instead of staged for approval because it was proposed while
|
|
116
116
|
* `taskId` (part of a plan approved with `'approve_trusted'`) was the currently RUNNING task —
|
|
117
117
|
* see PlanRecord.trustApprovedSteps and INV-36. The action itself still went through
|
package/dist/trust-tagging.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface InjectionDetection {
|
|
|
15
15
|
export declare function detectInjectionLikely(text: string): InjectionDetection;
|
|
16
16
|
/**
|
|
17
17
|
* `AUDIT_LLM_INJECTION_DETECT` gate — feature-value audit (Phase A5 of
|
|
18
|
-
*
|
|
18
|
+
* the internal plan). Default **ON**: the semantic injection-detection LLM
|
|
19
19
|
* call ships enabled, so an unset / empty / truthy value keeps today's behaviour. Set to a falsy
|
|
20
20
|
* value (`0` / `false` / `off` / `no` / `disabled`) to skip the LLM escalation entirely and fall
|
|
21
21
|
* back to the always-on deterministic regex/pattern pass only. Read at exactly one place —
|
|
@@ -3,10 +3,11 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
5
5
|
import { Readable, Writable } from "node:stream";
|
|
6
|
-
import { useState, useRef, useCallback, useSyncExternalStore } from "react";
|
|
6
|
+
import { useState, useRef, useCallback, useSyncExternalStore, useEffect } from "react";
|
|
7
7
|
import { useWindowSize, usePaste, useInput, Box, Text, render, Static } from "ink";
|
|
8
|
-
import { runCli } from "./
|
|
8
|
+
import { r as runCli, P as PLAN_LINE_PREFIX, I as ICONS } from "./bin-CqkqT6KK.js";
|
|
9
9
|
import { format } from "node:util";
|
|
10
|
+
import { h as DIFF_INDENT } from "./cli-session-BiX2DOni.js";
|
|
10
11
|
function classify(raw, stream) {
|
|
11
12
|
if (raw.startsWith("\r")) {
|
|
12
13
|
return { type: "progress", text: raw.replace(/\r/g, "").trimEnd() };
|
|
@@ -210,18 +211,131 @@ function TuiInput(props) {
|
|
|
210
211
|
}) })
|
|
211
212
|
] });
|
|
212
213
|
}
|
|
214
|
+
function SelectPrompt(props) {
|
|
215
|
+
const { question, options, onSubmit, isActive = true } = props;
|
|
216
|
+
const [highlighted, setHighlighted] = useState(0);
|
|
217
|
+
useInput(
|
|
218
|
+
(input, key) => {
|
|
219
|
+
if (key.upArrow) {
|
|
220
|
+
setHighlighted((h) => (h - 1 + options.length) % options.length);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (key.downArrow) {
|
|
224
|
+
setHighlighted((h) => (h + 1) % options.length);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (key.return) {
|
|
228
|
+
onSubmit(options[highlighted].key);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const matched = options.find((option) => option.key.toLowerCase() === input.toLowerCase());
|
|
232
|
+
if (matched) onSubmit(matched.key);
|
|
233
|
+
},
|
|
234
|
+
{ isActive }
|
|
235
|
+
);
|
|
236
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
237
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: question }),
|
|
238
|
+
/* @__PURE__ */ jsx(Box, { borderStyle: "round", borderColor: "yellow", flexDirection: "column", paddingX: 1, children: options.map((option, i) => /* @__PURE__ */ jsxs(Text, { inverse: i === highlighted, children: [
|
|
239
|
+
i === highlighted ? "› " : " ",
|
|
240
|
+
"[",
|
|
241
|
+
option.key,
|
|
242
|
+
"] ",
|
|
243
|
+
option.label
|
|
244
|
+
] }, option.key)) })
|
|
245
|
+
] });
|
|
246
|
+
}
|
|
247
|
+
const HEADER_RE = /^(#{1,6})\s+(.*)$/;
|
|
248
|
+
const CHECKBOX_RE = /^(\s*[-*]\s)\[([ xX])\]\s+(.*)$/;
|
|
249
|
+
const DIFF_LINE_RE = /^(\s*\d+) ([+\- ])(.*)$/;
|
|
250
|
+
const INLINE_TOKEN_RE = /\*\*(.+?)\*\*|`([^`]+)`/g;
|
|
251
|
+
const ASSISTANT_BULLET = "- ";
|
|
252
|
+
function matchDiffLine(text) {
|
|
253
|
+
if (!text.startsWith(DIFF_INDENT)) return void 0;
|
|
254
|
+
const rest = text.slice(DIFF_INDENT.length);
|
|
255
|
+
const match = DIFF_LINE_RE.exec(rest);
|
|
256
|
+
if (!match) return void 0;
|
|
257
|
+
const sign = match[2];
|
|
258
|
+
return { content: rest, color: sign === "+" ? "green" : sign === "-" ? "red" : void 0 };
|
|
259
|
+
}
|
|
260
|
+
function renderDiffLine(text, key) {
|
|
261
|
+
const diffLine = matchDiffLine(text);
|
|
262
|
+
if (!diffLine) return void 0;
|
|
263
|
+
return /* @__PURE__ */ jsxs(Text, { color: diffLine.color, children: [
|
|
264
|
+
DIFF_INDENT,
|
|
265
|
+
diffLine.content
|
|
266
|
+
] }, key);
|
|
267
|
+
}
|
|
268
|
+
const NEEDS_APPROVAL_RE = /^(\[needs approval[^\]]*\])(.*)$/;
|
|
269
|
+
function renderNeedsApprovalLine(text, key) {
|
|
270
|
+
const match = NEEDS_APPROVAL_RE.exec(text);
|
|
271
|
+
if (!match) return void 0;
|
|
272
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
273
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", children: match[1] }),
|
|
274
|
+
match[2]
|
|
275
|
+
] }, key);
|
|
276
|
+
}
|
|
277
|
+
function renderInlineSegments(text) {
|
|
278
|
+
const segments = [];
|
|
279
|
+
let lastIndex = 0;
|
|
280
|
+
let key = 0;
|
|
281
|
+
INLINE_TOKEN_RE.lastIndex = 0;
|
|
282
|
+
let match;
|
|
283
|
+
while ((match = INLINE_TOKEN_RE.exec(text)) !== null) {
|
|
284
|
+
if (match.index > lastIndex) segments.push(text.slice(lastIndex, match.index));
|
|
285
|
+
if (match[1] !== void 0) {
|
|
286
|
+
segments.push(
|
|
287
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: match[1] }, key++)
|
|
288
|
+
);
|
|
289
|
+
} else if (match[2] !== void 0) {
|
|
290
|
+
segments.push(
|
|
291
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: match[2] }, key++)
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
lastIndex = INLINE_TOKEN_RE.lastIndex;
|
|
295
|
+
}
|
|
296
|
+
if (lastIndex < text.length || segments.length === 0) segments.push(text.slice(lastIndex));
|
|
297
|
+
return segments;
|
|
298
|
+
}
|
|
299
|
+
function renderMarkdownLine(text, key, continuation = false) {
|
|
300
|
+
const diffElement = renderDiffLine(text, key);
|
|
301
|
+
if (diffElement) return diffElement;
|
|
302
|
+
const headerMatch = HEADER_RE.exec(text);
|
|
303
|
+
if (headerMatch) {
|
|
304
|
+
return /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: renderInlineSegments(headerMatch[2]) }, key);
|
|
305
|
+
}
|
|
306
|
+
const checkboxMatch = CHECKBOX_RE.exec(text);
|
|
307
|
+
if (checkboxMatch) {
|
|
308
|
+
const checked = checkboxMatch[2].toLowerCase() === "x";
|
|
309
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
310
|
+
checkboxMatch[1],
|
|
311
|
+
checked ? "☑" : "☐",
|
|
312
|
+
" ",
|
|
313
|
+
renderInlineSegments(checkboxMatch[3])
|
|
314
|
+
] }, key);
|
|
315
|
+
}
|
|
316
|
+
if (text.length === 0) return /* @__PURE__ */ jsx(Text, { children: " " }, key);
|
|
317
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
318
|
+
continuation ? "" : ASSISTANT_BULLET,
|
|
319
|
+
renderInlineSegments(text)
|
|
320
|
+
] }, key);
|
|
321
|
+
}
|
|
322
|
+
const ASSISTANT_INDENT_WIDTH = 2;
|
|
213
323
|
function useStore(store) {
|
|
214
324
|
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
215
325
|
}
|
|
216
|
-
const EMPTY_LOG = { lines: [], progressText: "", transientText: "" };
|
|
217
|
-
const
|
|
326
|
+
const EMPTY_LOG = { lines: [], progressText: "", transientText: "", waitingForOutput: false };
|
|
327
|
+
const TOOL_STEP_PREFIXES = [ICONS.toolStep, ICONS.proposalStep, ICONS.deniedStep];
|
|
218
328
|
const ASSISTANT_REPLY_PREFIX = "Aielia>";
|
|
219
329
|
function classifyLineKind(mergedText, streamedReplyWasOpen, stream) {
|
|
220
330
|
if (stream === "stderr") return "error";
|
|
331
|
+
if (mergedText.trimStart().startsWith(PLAN_LINE_PREFIX)) return "plan";
|
|
221
332
|
if (streamedReplyWasOpen || mergedText.trimStart().startsWith(ASSISTANT_REPLY_PREFIX)) return "assistant";
|
|
222
|
-
if (mergedText.trimStart().startsWith(
|
|
333
|
+
if (TOOL_STEP_PREFIXES.some((prefix) => mergedText.trimStart().startsWith(prefix))) return "tool";
|
|
223
334
|
return "system";
|
|
224
335
|
}
|
|
336
|
+
function stripPlanLabel(text) {
|
|
337
|
+
return text.replace(new RegExp(`^\\s*${PLAN_LINE_PREFIX}\\s*`), "");
|
|
338
|
+
}
|
|
225
339
|
function stripAssistantLabel(text) {
|
|
226
340
|
return text.replace(/^\s*Aielia>\s?/, "");
|
|
227
341
|
}
|
|
@@ -230,6 +344,7 @@ class EventLogBridge {
|
|
|
230
344
|
__publicField(this, "lines", []);
|
|
231
345
|
__publicField(this, "progressText", "");
|
|
232
346
|
__publicField(this, "transientText", "");
|
|
347
|
+
__publicField(this, "waitingForOutput", false);
|
|
233
348
|
__publicField(this, "snapshot", EMPTY_LOG);
|
|
234
349
|
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
235
350
|
__publicField(this, "subscribe", (listener) => {
|
|
@@ -241,9 +356,14 @@ class EventLogBridge {
|
|
|
241
356
|
__publicField(this, "getSnapshot", () => this.snapshot);
|
|
242
357
|
}
|
|
243
358
|
commit() {
|
|
244
|
-
this.snapshot = { lines: this.lines, progressText: this.progressText, transientText: this.transientText };
|
|
359
|
+
this.snapshot = { lines: this.lines, progressText: this.progressText, transientText: this.transientText, waitingForOutput: this.waitingForOutput };
|
|
245
360
|
for (const listener of this.listeners) listener();
|
|
246
361
|
}
|
|
362
|
+
/** Marks the start of a turn's wait for its first output (see `TuiLogState.waitingForOutput`'s doc comment) — called by `TuiApp` right after a chat line or resolved prompt answer is submitted. */
|
|
363
|
+
beginTurn() {
|
|
364
|
+
this.waitingForOutput = true;
|
|
365
|
+
this.commit();
|
|
366
|
+
}
|
|
247
367
|
/**
|
|
248
368
|
* A blank spacer line ("some margins between different conversations", later extended to "at
|
|
249
369
|
* least one line spacing between the user and system messages") — called from three places in
|
|
@@ -260,12 +380,13 @@ class EventLogBridge {
|
|
|
260
380
|
this.lines = [...this.lines, { text: "", kind: "margin" }];
|
|
261
381
|
this.commit();
|
|
262
382
|
}
|
|
263
|
-
/** Records a line the user just submitted (chat or a resolved prompt answer) into permanent scrollback — mirrors a real terminal's own line-echo, which the inert (non-TTY) input stream this shell uses never produces on its own. `prefix` is `''` for an ordinary chat line (no "you>" label — see `LogLineText`'s doc comment) and `'> '` for a resolved prompt answer, which keeps its prefix since it's answering a question printed just above it, not identifying whose turn it is. */
|
|
383
|
+
/** Records a line the user just submitted (chat or a resolved prompt answer) into permanent scrollback — mirrors a real terminal's own line-echo, which the inert (non-TTY) input stream this shell uses never produces on its own. `prefix` is `''` for an ordinary chat line (no "you>" label — see `LogLineText`'s doc comment, kind `'user'`) and `'> '` for a resolved prompt answer (kind `'approval'`, its own box color — see `LogLineText` — distinct from a real chat message even though both are the user's own input), which keeps its prefix since it's answering a question printed just above it, not identifying whose turn it is. */
|
|
264
384
|
pushEchoLine(prefix, text) {
|
|
265
|
-
this.lines = [...this.lines, { text: `${prefix}${text}`, kind: "user" }];
|
|
385
|
+
this.lines = [...this.lines, { text: `${prefix}${text}`, kind: prefix === "" ? "user" : "approval" }];
|
|
266
386
|
this.commit();
|
|
267
387
|
}
|
|
268
388
|
handleEvent(event) {
|
|
389
|
+
this.waitingForOutput = false;
|
|
269
390
|
if (event.type === "progress") {
|
|
270
391
|
this.progressText = event.text;
|
|
271
392
|
this.commit();
|
|
@@ -279,8 +400,13 @@ class EventLogBridge {
|
|
|
279
400
|
const streamedReplyWasOpen = this.transientText.length > 0;
|
|
280
401
|
const merged = this.transientText + event.lines.join("\n");
|
|
281
402
|
const kind = classifyLineKind(merged, streamedReplyWasOpen, event.stream);
|
|
282
|
-
const displayText = kind === "assistant" ? stripAssistantLabel(merged) : merged;
|
|
283
|
-
this.lines = [...this.lines,
|
|
403
|
+
const displayText = kind === "assistant" ? stripAssistantLabel(merged) : kind === "plan" ? stripPlanLabel(merged) : merged;
|
|
404
|
+
this.lines = kind === "plan" ? [...this.lines, { text: displayText, kind }] : (
|
|
405
|
+
// continuation is only meaningful (and only set) for 'assistant' — every other kind's
|
|
406
|
+
// LogLine stays a bare { text, kind } so existing exact-shape equality checks elsewhere
|
|
407
|
+
// (classifyLineKind's own test coverage) don't have to account for an unused field.
|
|
408
|
+
[...this.lines, ...displayText.split("\n").map((text, i) => kind === "assistant" ? { text, kind, continuation: i > 0 } : { text, kind })]
|
|
409
|
+
);
|
|
284
410
|
this.transientText = "";
|
|
285
411
|
this.progressText = "";
|
|
286
412
|
this.commit();
|
|
@@ -302,10 +428,12 @@ class PromptBridge {
|
|
|
302
428
|
/** Matches `RunCliOptions.askYesNo`'s exact parsing convention (`answer.trim().toLowerCase().startsWith('y')`) — same fail-closed shape, just answered via the Ink prompt instead of `rl.question`. */
|
|
303
429
|
__publicField(this, "askYesNo", (question) => this.ask(question).then((answer) => answer.trim().toLowerCase().startsWith("y")));
|
|
304
430
|
__publicField(this, "askLine", (question) => this.ask(question));
|
|
431
|
+
/** Matches `RunCliOptions.askSelect`'s contract exactly — resolves with the chosen `SelectOption.key`, answered via `SelectPrompt` instead of `rl.question`. */
|
|
432
|
+
__publicField(this, "askSelect", (question, options) => this.ask(question, options));
|
|
305
433
|
}
|
|
306
|
-
ask(question) {
|
|
434
|
+
ask(question, options) {
|
|
307
435
|
return new Promise((resolve) => {
|
|
308
|
-
this.pending = { question };
|
|
436
|
+
this.pending = { question, options };
|
|
309
437
|
this.resolve = resolve;
|
|
310
438
|
for (const listener of this.listeners) listener();
|
|
311
439
|
});
|
|
@@ -335,16 +463,24 @@ class StatusBridge {
|
|
|
335
463
|
for (const listener of this.listeners) listener();
|
|
336
464
|
}
|
|
337
465
|
}
|
|
338
|
-
function UserMessageBox({ text, width }) {
|
|
339
|
-
return /* @__PURE__ */ jsx(Box, { borderStyle: "round", borderColor
|
|
466
|
+
function UserMessageBox({ text, width, borderColor = "gray" }) {
|
|
467
|
+
return /* @__PURE__ */ jsx(Box, { borderStyle: "round", borderColor, flexDirection: "column", paddingX: 1, width, children: text.split("\n").map((line, i) => /* @__PURE__ */ jsx(Text, { children: line.length > 0 ? line : " " }, i)) });
|
|
468
|
+
}
|
|
469
|
+
function PlanBox({ text, width }) {
|
|
470
|
+
return /* @__PURE__ */ jsx(Box, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1, width, children: text.split("\n").map((line, i) => /* @__PURE__ */ jsx(Text, { bold: i === 0, children: line.length > 0 ? line : " " }, i)) });
|
|
340
471
|
}
|
|
341
472
|
function LogLineText({ line, width }) {
|
|
342
473
|
switch (line.kind) {
|
|
343
474
|
case "user":
|
|
344
475
|
return /* @__PURE__ */ jsx(UserMessageBox, { text: line.text, width });
|
|
476
|
+
case "approval":
|
|
477
|
+
return /* @__PURE__ */ jsx(UserMessageBox, { text: line.text, width, borderColor: "greenBright" });
|
|
345
478
|
case "assistant":
|
|
479
|
+
return /* @__PURE__ */ jsx(Box, { paddingLeft: ASSISTANT_INDENT_WIDTH, children: renderMarkdownLine(line.text, 0, line.continuation) });
|
|
480
|
+
case "plan":
|
|
481
|
+
return /* @__PURE__ */ jsx(PlanBox, { text: line.text, width });
|
|
346
482
|
case "system":
|
|
347
|
-
return /* @__PURE__ */ jsx(Text, { children: line.text });
|
|
483
|
+
return renderDiffLine(line.text, 0) ?? renderNeedsApprovalLine(line.text, 0) ?? /* @__PURE__ */ jsx(Text, { children: line.text });
|
|
348
484
|
case "tool":
|
|
349
485
|
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: line.text });
|
|
350
486
|
case "error":
|
|
@@ -353,6 +489,19 @@ function LogLineText({ line, width }) {
|
|
|
353
489
|
return /* @__PURE__ */ jsx(Text, { children: " " });
|
|
354
490
|
}
|
|
355
491
|
}
|
|
492
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
493
|
+
const SPINNER_INTERVAL_MS = 80;
|
|
494
|
+
function Spinner() {
|
|
495
|
+
const [frame, setFrame] = useState(0);
|
|
496
|
+
useEffect(() => {
|
|
497
|
+
const id = setInterval(() => setFrame((f) => (f + 1) % SPINNER_FRAMES.length), SPINNER_INTERVAL_MS);
|
|
498
|
+
return () => clearInterval(id);
|
|
499
|
+
}, []);
|
|
500
|
+
return /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
501
|
+
SPINNER_FRAMES[frame],
|
|
502
|
+
" Thinking…"
|
|
503
|
+
] });
|
|
504
|
+
}
|
|
356
505
|
function StatusLine({ indicators }) {
|
|
357
506
|
return /* @__PURE__ */ jsx(Text, { children: indicators.map((indicator, index) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
358
507
|
index > 0 ? " " : "",
|
|
@@ -374,26 +523,32 @@ function TuiApp(props) {
|
|
|
374
523
|
eventLog.pushTurnMargin();
|
|
375
524
|
eventLog.pushEchoLine("", line);
|
|
376
525
|
eventLog.pushTurnMargin();
|
|
526
|
+
eventLog.beginTurn();
|
|
377
527
|
onSubmitChat(line);
|
|
378
528
|
},
|
|
379
529
|
[eventLog, onSubmitChat]
|
|
380
530
|
);
|
|
381
531
|
const handleSubmitPrompt = useCallback(
|
|
382
532
|
(line) => {
|
|
383
|
-
|
|
533
|
+
var _a, _b;
|
|
534
|
+
const displayAnswer = ((_b = (_a = pending == null ? void 0 : pending.options) == null ? void 0 : _a.find((option) => option.key === line)) == null ? void 0 : _b.label) ?? line;
|
|
535
|
+
eventLog.pushEchoLine("> ", (pending == null ? void 0 : pending.question) !== void 0 ? `${pending.question.trimEnd()} → ${displayAnswer}` : line);
|
|
536
|
+
eventLog.beginTurn();
|
|
384
537
|
prompt.submit(line);
|
|
385
538
|
},
|
|
386
|
-
[eventLog, prompt]
|
|
539
|
+
[eventLog, prompt, pending]
|
|
387
540
|
);
|
|
388
541
|
const hasTransient = log.progressText.length > 0 || log.transientText.length > 0;
|
|
542
|
+
const showSpinner = log.waitingForOutput && !hasTransient;
|
|
389
543
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
390
544
|
/* @__PURE__ */ jsx(Static, { items: log.lines, children: (line, index) => /* @__PURE__ */ jsx(LogLineText, { line, width }, index) }),
|
|
545
|
+
showSpinner && /* @__PURE__ */ jsx(Spinner, {}),
|
|
391
546
|
hasTransient && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
392
547
|
log.progressText.length > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: log.progressText }),
|
|
393
548
|
log.transientText.length > 0 && /* @__PURE__ */ jsx(Text, { children: stripAssistantLabel(log.transientText) })
|
|
394
549
|
] }),
|
|
395
550
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "─".repeat(Math.max(1, width)) }),
|
|
396
|
-
/* @__PURE__ */ jsx(
|
|
551
|
+
(pending == null ? void 0 : pending.options) ? /* @__PURE__ */ jsx(SelectPrompt, { question: pending.question, options: pending.options, onSubmit: handleSubmitPrompt }) : /* @__PURE__ */ jsx(
|
|
397
552
|
TuiInput,
|
|
398
553
|
{
|
|
399
554
|
promptLabel: pending == null ? void 0 : pending.question,
|
|
@@ -436,7 +591,8 @@ async function runTuiApp(options = {}) {
|
|
|
436
591
|
input: options.input ?? inert.input,
|
|
437
592
|
output: options.output ?? inert.output,
|
|
438
593
|
askYesNo: prompt.askYesNo,
|
|
439
|
-
askLine: prompt.askLine
|
|
594
|
+
askLine: prompt.askLine,
|
|
595
|
+
askSelect: prompt.askSelect
|
|
440
596
|
});
|
|
441
597
|
} catch (err) {
|
|
442
598
|
restoreCapture();
|
|
@@ -478,4 +634,4 @@ export {
|
|
|
478
634
|
TuiApp,
|
|
479
635
|
runTuiApp
|
|
480
636
|
};
|
|
481
|
-
//# sourceMappingURL=tui-app-
|
|
637
|
+
//# sourceMappingURL=tui-app-4_TrzESz.js.map
|