@buildaharness/aielia 0.3.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 +832 -0
- package/dist/action-approval-service.d.ts +52 -0
- package/dist/action-snapshot.d.ts +173 -0
- package/dist/action-tools.d.ts +38 -0
- package/dist/agent-loop.d.ts +372 -0
- package/dist/answer-claim.d.ts +51 -0
- package/dist/ask-clarification-service.d.ts +70 -0
- package/dist/ask-mode-flag.d.ts +25 -0
- package/dist/ask-response-format.d.ts +8 -0
- package/dist/assistant-session.d.ts +267 -0
- package/dist/assistant-source.d.ts +14 -0
- package/dist/assistant-types.d.ts +158 -0
- package/dist/assistant.d.ts +419 -0
- package/dist/batch-list-detector.d.ts +15 -0
- package/dist/batch-research-fixtures.d.ts +50 -0
- package/dist/benchmark-injected-failure.d.ts +27 -0
- package/dist/claude-cli-llm-client.d.ts +139 -0
- package/dist/claude-cli-prompt.d.ts +86 -0
- package/dist/cli-config.d.ts +27 -0
- package/dist/cli-session-D0uLdT2A.js +7863 -0
- package/dist/cli-session-D0uLdT2A.js.map +1 -0
- package/dist/cli-session.d.ts +63 -0
- package/dist/cli.d.ts +65 -0
- package/dist/cli.js +1693 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +175 -0
- package/dist/contradiction-checker.d.ts +27 -0
- package/dist/debug-log.d.ts +16 -0
- package/dist/decomposition-classifier.d.ts +38 -0
- package/dist/doctor-checks.d.ts +8 -0
- package/dist/email-smtp.d.ts +19 -0
- package/dist/email.d.ts +60 -0
- package/dist/error-classifier.d.ts +13 -0
- package/dist/execution-mode.d.ts +85 -0
- package/dist/fact-extraction.d.ts +143 -0
- package/dist/failure-mode-matcher.d.ts +32 -0
- package/dist/file-tools-mcp-server.mjs +1027 -0
- package/dist/file-tools.d.ts +179 -0
- package/dist/first-run.d.ts +22 -0
- package/dist/harness-bridge.d.ts +103 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +212 -0
- package/dist/index.js.map +1 -0
- package/dist/ink-test-render.d.ts +24 -0
- package/dist/lexical/patterns/batch-list-markers.json +8 -0
- package/dist/lexical/patterns/batch-list-markers.json.d.ts +11 -0
- package/dist/lexical/patterns/coding-fact-markers.json +8 -0
- package/dist/lexical/patterns/coding-fact-markers.json.d.ts +11 -0
- package/dist/lexical/patterns/enumeration-markers.json +20 -0
- package/dist/lexical/patterns/enumeration-markers.json.d.ts +23 -0
- package/dist/lexical/patterns/fact-markers.json +16 -0
- package/dist/lexical/patterns/fact-markers.json.d.ts +19 -0
- package/dist/lexical/patterns/injection-patterns.json +20 -0
- package/dist/lexical/patterns/injection-patterns.json.d.ts +23 -0
- package/dist/lexical/patterns/plan-mode-markers.json +10 -0
- package/dist/lexical/patterns/plan-mode-markers.json.d.ts +13 -0
- package/dist/lexical/patterns/risk-patterns.json +122 -0
- package/dist/lexical/patterns/risk-patterns.json.d.ts +125 -0
- package/dist/lexical/patterns/task-cancel-markers.json +22 -0
- package/dist/lexical/patterns/task-cancel-markers.json.d.ts +25 -0
- package/dist/lexical/patterns/template-keywords.json +20 -0
- package/dist/lexical/patterns/template-keywords.json.d.ts +23 -0
- package/dist/lexical/patterns/tool-yield-markers.json +22 -0
- package/dist/lexical/patterns/tool-yield-markers.json.d.ts +25 -0
- package/dist/lexical/patterns.d.ts +64 -0
- package/dist/memory-service.d.ts +219 -0
- package/dist/model-pricing.d.ts +19 -0
- package/dist/network-containment.d.ts +21 -0
- package/dist/node-config-store.d.ts +29 -0
- package/dist/node-display-names.d.ts +26 -0
- package/dist/node-fs-backend.d.ts +8 -0
- package/dist/non-interactive-mode.d.ts +22 -0
- package/dist/one-loop-flag.d.ts +38 -0
- package/dist/plan-approval-service.d.ts +62 -0
- package/dist/plan-builder.d.ts +32 -0
- package/dist/plan-drafting-service.d.ts +87 -0
- package/dist/plan-drafting.d.ts +58 -0
- package/dist/plan-mode-flag.d.ts +34 -0
- package/dist/plan-service.d.ts +61 -0
- package/dist/plan-sketch-service.d.ts +24 -0
- package/dist/plan-sketch.d.ts +20 -0
- package/dist/plan-store.d.ts +232 -0
- package/dist/plan-templates/data/content_creation.json.d.ts +75 -0
- package/dist/plan-templates/data/decision_making.json.d.ts +66 -0
- package/dist/plan-templates/data/problem_solving.json.d.ts +75 -0
- package/dist/plan-templates/data/process_improvement.json.d.ts +57 -0
- package/dist/plan-templates/data/project_planning.json.d.ts +66 -0
- package/dist/plan-templates/data/research_analysis.json.d.ts +66 -0
- package/dist/plan-templates/data/trip_planning.json.d.ts +66 -0
- package/dist/plan-templates/index.d.ts +33 -0
- package/dist/plan-verification.d.ts +31 -0
- package/dist/reminder-tools.d.ts +13 -0
- package/dist/response-service.d.ts +73 -0
- package/dist/review-checker.d.ts +22 -0
- package/dist/risk-classifier.d.ts +7 -0
- package/dist/scripted-llm-client.d.ts +37 -0
- package/dist/semantic-criterion-coverage.d.ts +30 -0
- package/dist/shell-executor.d.ts +14 -0
- package/dist/shell-tools.d.ts +84 -0
- package/dist/spend-cap.d.ts +41 -0
- package/dist/spike.d.ts +1 -0
- package/dist/spike.js +18 -0
- package/dist/spike.js.map +1 -0
- package/dist/supervisor-decider.d.ts +3 -0
- package/dist/system-prompt.d.ts +8 -0
- package/dist/task-mapping.d.ts +42 -0
- package/dist/tool-control-plane.d.ts +67 -0
- package/dist/tool-policy.d.ts +45 -0
- package/dist/tool-step.d.ts +23 -0
- package/dist/tool-yield-classifier.d.ts +7 -0
- package/dist/trace-events.d.ts +126 -0
- package/dist/transcript-compaction.d.ts +13 -0
- package/dist/trust-tagging.d.ts +38 -0
- package/dist/tui-app-DxLPSGh2.js +481 -0
- package/dist/tui-app-DxLPSGh2.js.map +1 -0
- package/dist/tui-app.d.ts +125 -0
- package/dist/tui-input.d.ts +51 -0
- package/dist/tui-mode-flag.d.ts +36 -0
- package/dist/tui-output-capture.d.ts +30 -0
- package/dist/tui-spike.d.ts +1 -0
- package/dist/turn-intent-classifier.d.ts +96 -0
- package/dist/turn-interpreter.d.ts +71 -0
- package/dist/turn-policy.d.ts +45 -0
- package/dist/web-fetch-core.d.ts +65 -0
- package/dist/web-search-provider.d.ts +12 -0
- package/dist/web-tools.d.ts +26 -0
- package/package.json +69 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { RunCliOptions } from './cli.js';
|
|
2
|
+
import { CaptureEvent } from './tui-output-capture.js';
|
|
3
|
+
/**
|
|
4
|
+
* A minimal pub/sub store, one per piece of state the Ink tree needs — deliberately not React
|
|
5
|
+
* state, because the values that drive it arrive from outside React entirely: `startCapture`
|
|
6
|
+
* (Phase 1) calls its `onEvent` callback synchronously from a monkey-patched `console.log`/
|
|
7
|
+
* `process.stdout.write`, and `askYesNo`/`askLine` (Decision 1's override seam) are plain
|
|
8
|
+
* functions handed to `runCli()` before any component has mounted. `useSyncExternalStore` is the
|
|
9
|
+
* React-correct way to subscribe a component to state that's mutated from outside React's own
|
|
10
|
+
* render cycle, without the chicken-and-egg problem of needing a `setState` that only exists
|
|
11
|
+
* after the first render.
|
|
12
|
+
*/
|
|
13
|
+
interface Store<T> {
|
|
14
|
+
subscribe: (listener: () => void) => () => void;
|
|
15
|
+
getSnapshot: () => T;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Which visual style a committed scrollback line gets (this task's ask: "different formats for
|
|
19
|
+
* user text vs aielia response vs tool call details"). `'margin'` is a blank spacer line between
|
|
20
|
+
* turns, not a real writer's output — kept as its own kind rather than reusing `'system'` so
|
|
21
|
+
* rendering never has to guess a blank line's intent from empty text alone.
|
|
22
|
+
*/
|
|
23
|
+
export type LineKind = 'user' | 'assistant' | 'tool' | 'error' | 'system' | 'margin';
|
|
24
|
+
export interface LogLine {
|
|
25
|
+
text: string;
|
|
26
|
+
kind: LineKind;
|
|
27
|
+
}
|
|
28
|
+
export interface TuiLogState {
|
|
29
|
+
/** Every permanently-committed scrollback line, oldest first — fed straight into `<Static>`. */
|
|
30
|
+
lines: LogLine[];
|
|
31
|
+
/** `writeProgress`'s current `\r`-overwritten line — shown in the transient region, never committed. */
|
|
32
|
+
progressText: string;
|
|
33
|
+
/** `writeToken`'s not-yet-newline-terminated streamed reply text — shown in the transient region until the next committed-line event closes it out. */
|
|
34
|
+
transientText: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Classifies and accumulates `CaptureEvent`s (Phase 1) into the three regions Phase 3's layout
|
|
38
|
+
* needs: permanent scrollback, the transient progress line, and the transient in-progress
|
|
39
|
+
* streamed reply. A `'line'` event closes out whatever streamed-token text is currently pending
|
|
40
|
+
* (see `cli.ts`'s `writeToken`/`handleTurn`: a streamed reply is one or more raw `'token'` writes
|
|
41
|
+
* followed by exactly one final `'line'` write for the trailing hint text) — merging the two
|
|
42
|
+
* before committing is what turns "Hi the|re!\n\n" (streamed "Hi the" + committed "re!\n\n") into
|
|
43
|
+
* one correct committed line instead of two garbled ones.
|
|
44
|
+
*/
|
|
45
|
+
export declare class EventLogBridge implements Store<TuiLogState> {
|
|
46
|
+
private lines;
|
|
47
|
+
private progressText;
|
|
48
|
+
private transientText;
|
|
49
|
+
private snapshot;
|
|
50
|
+
private listeners;
|
|
51
|
+
subscribe: (listener: () => void) => (() => void);
|
|
52
|
+
getSnapshot: () => TuiLogState;
|
|
53
|
+
private commit;
|
|
54
|
+
/**
|
|
55
|
+
* A blank spacer line ("some margins between different conversations", later extended to "at
|
|
56
|
+
* least one line spacing between the user and system messages") — called from three places in
|
|
57
|
+
* `handleSubmitChat` (never from a resolved approval/clarification prompt answer, which stays
|
|
58
|
+
* visually grouped with the turn it belongs to): once before a new turn's echo (separating it
|
|
59
|
+
* from the previous turn's reply) and once right after it (separating the echo from whatever
|
|
60
|
+
* system/tool/assistant output follows). A no-op on an empty log or if the last line is already
|
|
61
|
+
* a margin, so calling it from both of those spots back-to-back across turns can't stack blank
|
|
62
|
+
* lines — the "before next turn" and "after this turn" calls collapse into the same one line.
|
|
63
|
+
*/
|
|
64
|
+
pushTurnMargin(): void;
|
|
65
|
+
/** 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. */
|
|
66
|
+
pushEchoLine(prefix: string, text: string): void;
|
|
67
|
+
handleEvent(event: CaptureEvent): void;
|
|
68
|
+
}
|
|
69
|
+
export type PendingPrompt = {
|
|
70
|
+
question: string;
|
|
71
|
+
} | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Bridges `runCli()`'s `askYesNo`/`askLine` override seam (Decision 1) to Ink: each call parks
|
|
74
|
+
* a resolver and exposes the pending question to the component tree via `getSnapshot()`, instead
|
|
75
|
+
* of the original `rl.question`-based implementation. `TuiInput`'s own `promptLabel` prop
|
|
76
|
+
* (Phase 2) already knows how to render this state and route Enter to `onSubmitPrompt` instead
|
|
77
|
+
* of `onSubmitChat` — this class only needs to supply the question text and resolve the answer.
|
|
78
|
+
*/
|
|
79
|
+
export declare class PromptBridge implements Store<PendingPrompt> {
|
|
80
|
+
private pending;
|
|
81
|
+
private resolve;
|
|
82
|
+
private listeners;
|
|
83
|
+
subscribe: (listener: () => void) => (() => void);
|
|
84
|
+
getSnapshot: () => PendingPrompt;
|
|
85
|
+
private ask;
|
|
86
|
+
/** 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`. */
|
|
87
|
+
askYesNo: (question: string) => Promise<boolean>;
|
|
88
|
+
askLine: (question: string) => Promise<string>;
|
|
89
|
+
submit(answer: string): void;
|
|
90
|
+
}
|
|
91
|
+
/** The status line's short indicator list (Phase 3 step 5) — polled from `CliInstance.getStatusIndicators()` after every dispatched line, since plan/spend-cap state only changes as a side effect of a turn. */
|
|
92
|
+
export declare class StatusBridge implements Store<string[]> {
|
|
93
|
+
private indicators;
|
|
94
|
+
private listeners;
|
|
95
|
+
subscribe: (listener: () => void) => (() => void);
|
|
96
|
+
getSnapshot: () => string[];
|
|
97
|
+
set(indicators: string[]): void;
|
|
98
|
+
}
|
|
99
|
+
export interface TuiAppProps {
|
|
100
|
+
eventLog: EventLogBridge;
|
|
101
|
+
prompt: PromptBridge;
|
|
102
|
+
status: StatusBridge;
|
|
103
|
+
onSubmitChat: (line: string) => void;
|
|
104
|
+
onExit: () => void;
|
|
105
|
+
/** Overrides the live terminal width for tests — same seam `TuiInput` already accepts. */
|
|
106
|
+
columns?: number;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The top-level Ink app (Phase 3): a `<Static>` scrollback of every committed line, a transient
|
|
110
|
+
* region for the in-progress progress line/streamed reply, a one-row separator, the Phase 2
|
|
111
|
+
* multiline input box pinned to the bottom, and a reserved status line under that. Composition
|
|
112
|
+
* only — command parsing/dispatch stays entirely inside `cli.ts`'s `dispatchLine()` (Decision 1),
|
|
113
|
+
* and this component never re-implements it.
|
|
114
|
+
*/
|
|
115
|
+
export declare function TuiApp(props: TuiAppProps): React.JSX.Element;
|
|
116
|
+
export type RunTuiAppOptions = Omit<RunCliOptions, 'askYesNo' | 'askLine'>;
|
|
117
|
+
/**
|
|
118
|
+
* Constructs `runCli()` wired to this file's Ink shell (Phase 4 calls this once, behind the
|
|
119
|
+
* `tuiMode` rollout flag) and renders it. Resolves once the user exits (Ctrl+C — see `TuiApp`'s
|
|
120
|
+
* `onExit`), after the same graceful-exit steps `cli.ts`'s own `rl.on('SIGINT', ...)` handler
|
|
121
|
+
* performs: restore the captured `console`/`process.stdout`, close the underlying `CliInstance`,
|
|
122
|
+
* then exit the process.
|
|
123
|
+
*/
|
|
124
|
+
export declare function runTuiApp(options?: RunTuiAppOptions): Promise<void>;
|
|
125
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One word-wrapped visual row of the draft. `wraps: true` means the next row is a continuation
|
|
3
|
+
* of the same logical (`\n`-delimited) line — no real newline between `end` and the next row's
|
|
4
|
+
* `start` — which is what lets {@link offsetToRowCol} tell a wrap boundary (cursor snaps to the
|
|
5
|
+
* start of the next row) apart from a real end-of-line (cursor stays put at this row's end).
|
|
6
|
+
*/
|
|
7
|
+
export interface DraftRow {
|
|
8
|
+
text: string;
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
wraps: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Splits `value` into visual rows at `\n` boundaries and, within each logical line, at every
|
|
15
|
+
* `columns`-wide chunk — plain character wrapping (not word-boundary wrapping): a real terminal
|
|
16
|
+
* hard-wraps by character when a row overflows, so this matches what the terminal will actually
|
|
17
|
+
* show rather than reflowing at word boundaries the way a GUI text area would.
|
|
18
|
+
*/
|
|
19
|
+
export declare function wrapDraftIntoRows(value: string, columns: number): DraftRow[];
|
|
20
|
+
/** Maps a character offset into `value` to its (row, col) among `rows` — see {@link DraftRow.wraps} for the wrap-vs-newline distinction this leans on. */
|
|
21
|
+
export declare function offsetToRowCol(rows: DraftRow[], cursor: number): {
|
|
22
|
+
row: number;
|
|
23
|
+
col: number;
|
|
24
|
+
};
|
|
25
|
+
/** Inverse of {@link offsetToRowCol}, clamping both row and column into range — used to preserve column when moving the cursor a visual row up/down. */
|
|
26
|
+
export declare function rowColToOffset(rows: DraftRow[], row: number, col: number): number;
|
|
27
|
+
/** Home = start of the current logical (`\n`-delimited) line, not the current visual row. */
|
|
28
|
+
export declare function homeOffset(value: string, cursor: number): number;
|
|
29
|
+
/** End = end of the current logical (`\n`-delimited) line, not the current visual row. */
|
|
30
|
+
export declare function endOffset(value: string, cursor: number): number;
|
|
31
|
+
/**
|
|
32
|
+
* Keeps the cursor's row inside a `maxRows`-tall window, scrolling the minimum amount needed —
|
|
33
|
+
* matches an ordinary scrolling textarea rather than re-centering on every keystroke.
|
|
34
|
+
*/
|
|
35
|
+
export declare function computeViewportStart(prevStart: number, cursorRow: number, totalRows: number, maxRows: number): number;
|
|
36
|
+
export interface TuiInputProps {
|
|
37
|
+
/** Question text for a pending askYesNo/askLine prompt (Decision 1's override seam) — undefined means ordinary chat mode. Shown as a label above the box, since it's real information (the question being asked), unlike chat mode's old static "you" label, which was dropped in favor of a placeholder inside the box (see `placeholder` below). */
|
|
38
|
+
promptLabel?: string;
|
|
39
|
+
/** Faint placeholder shown inside the (empty) box in chat mode, replacing the old "you" label above it. */
|
|
40
|
+
placeholder?: string;
|
|
41
|
+
/** Called when Enter submits a non-empty draft in chat mode. Also appended to in-session history. */
|
|
42
|
+
onSubmitChat: (line: string) => void;
|
|
43
|
+
/** Called when Enter resolves a pending prompt (promptLabel set). Not added to chat history. */
|
|
44
|
+
onSubmitPrompt: (line: string) => void;
|
|
45
|
+
/** Terminal width; defaults to the live width from Ink's useWindowSize. Pass explicitly for a deterministic wrap width in tests. */
|
|
46
|
+
columns?: number;
|
|
47
|
+
/** Max input box height in rows before internal scrolling kicks in. */
|
|
48
|
+
maxRows?: number;
|
|
49
|
+
isActive?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export declare function TuiInput(props: TuiInputProps): React.JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure logic backing PersonalAssistant's ASSISTANT_TUI handling — split out so it's
|
|
3
|
+
* unit-testable in isolation, mirroring one-loop-flag.ts/ask-mode-flag.ts/plan-mode-flag.ts's
|
|
4
|
+
* exact shape (module-level default, an injectable resolver so tests never touch real
|
|
5
|
+
* process.env).
|
|
6
|
+
*
|
|
7
|
+
* Phase 4 of plans/personal_assistant_cli_pinned_input_plan.html — the rollout flag gating
|
|
8
|
+
* whether `cli.ts`'s `main()` launches the Phase 3 Ink shell (`runTuiApp()`) instead of
|
|
9
|
+
* today's plain `readline`-based `runCli()` loop. 'disabled' (the default, for this plan's
|
|
10
|
+
* whole rollout window, per `DEFAULT_TUI_MODE`) means `main()` behaves exactly as it does
|
|
11
|
+
* today, byte-for-byte — the Ink shell is never constructed. 'enabled' only takes effect when
|
|
12
|
+
* `main()` also confirms a real interactive TTY on both stdio streams (see cli.ts); it never
|
|
13
|
+
* affects `runCli()` when called directly with test-only options, since that call site is
|
|
14
|
+
* `cli.test.ts`/`non-interactive-mode.ts`'s own, not `main()`'s.
|
|
15
|
+
*/
|
|
16
|
+
export type TuiMode = 'enabled' | 'disabled';
|
|
17
|
+
export declare const DEFAULT_TUI_MODE: TuiMode;
|
|
18
|
+
/**
|
|
19
|
+
* Value-level resolver: `resolveTuiMode` (CLI, reads `process.env.ASSISTANT_TUI`). Unlike
|
|
20
|
+
* `oneLoopMode`/`askMode`/`planMode`, this flag has no browser-facing analog (chat-ui/desktop
|
|
21
|
+
* have no terminal to pin input to), so there's no `envOverridesFromImportMetaEnv` counterpart.
|
|
22
|
+
* An unset or empty value falls back to the default silently; an unrecognized non-empty value
|
|
23
|
+
* falls back with a startup warning naming `varName` — same typo-tolerance-with-a-warning
|
|
24
|
+
* convention `normalizeOneLoopMode`/`normalizeAskMode`/`normalizePlanMode` use.
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizeTuiMode(raw: string | undefined, varName?: string): TuiMode;
|
|
27
|
+
export declare function resolveTuiMode(env: NodeJS.ProcessEnv): TuiMode;
|
|
28
|
+
/**
|
|
29
|
+
* The full gate `cli.ts`'s `main()` applies before ever constructing the Ink shell — split out
|
|
30
|
+
* as a pure function so the decision is unit-testable without a real pty. `tuiMode === 'enabled'`
|
|
31
|
+
* alone isn't sufficient: a piped/scripted invocation (`printf 'msg\nexit\n' | node dist/cli.js`,
|
|
32
|
+
* this repo's own documented headless workflow, or a real CI/non-interactive shell) must always
|
|
33
|
+
* fall through to the plain `readline` loop regardless of the flag, since Ink's raw-mode input
|
|
34
|
+
* has nothing to attach to without a real TTY on both streams.
|
|
35
|
+
*/
|
|
36
|
+
export declare function shouldLaunchTuiApp(tuiMode: TuiMode, stdoutIsTty: boolean, stdinIsTty: boolean): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One classified write, mirroring the three writer patterns `cli.ts` already uses (see that
|
|
3
|
+
* file's `writeProgress`/`clearProgress`, `writeToolStep`, `writeToken`) — not a new convention,
|
|
4
|
+
* just naming what's already there so Phase 3's Ink shell can route each kind to the right
|
|
5
|
+
* region (transient status line, transient streamed-reply buffer, permanent scrollback).
|
|
6
|
+
*/
|
|
7
|
+
export type CaptureEvent = {
|
|
8
|
+
/** `writeProgress`'s `\r`-overwritten step counter, or `clearProgress`'s blank-out of it. */
|
|
9
|
+
type: 'progress';
|
|
10
|
+
/** The current progress text with the `\r` control character and `padEnd` filler removed — `''` once cleared. */
|
|
11
|
+
text: string;
|
|
12
|
+
} | {
|
|
13
|
+
/** `writeToken`'s raw incremental streamed characters — no trailing newline until the turn ends. */
|
|
14
|
+
type: 'token';
|
|
15
|
+
text: string;
|
|
16
|
+
} | {
|
|
17
|
+
/** A `console.log`/`console.error` call, or any write that completes with a trailing newline — one or more committed, permanently-scrolled lines. */
|
|
18
|
+
type: 'line';
|
|
19
|
+
lines: string[];
|
|
20
|
+
/** Which global function produced this — `'stderr'` only for `console.error`, everything else (`console.log`, raw `process.stdout.write`) is `'stdout'`. Lets a consumer (Phase 3's `EventLogBridge`) style error output distinctly without guessing from content. */
|
|
21
|
+
stream: 'stdout' | 'stderr';
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Monkey-patches `console.log`, `console.error`, and `process.stdout.write` for the duration of
|
|
25
|
+
* the interactive Ink session, classifying every write via {@link classify} and routing it to
|
|
26
|
+
* `onEvent` instead of the real terminal — Ink owns the frame once this is active (Decision 1:
|
|
27
|
+
* every command handler still calls the same global functions unmodified). Returns a restore
|
|
28
|
+
* function that puts the originals back; call it on exit and in every test's `afterEach`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function startCapture(onEvent: (event: CaptureEvent) => void): () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
|
|
2
|
+
import { DecomposedTaskSpec } from './decomposition-classifier.js';
|
|
3
|
+
/**
|
|
4
|
+
* 'UNKNOWN' is never produced by a successful classification (TURN_INTENT_SCHEMA's riskLevel enum
|
|
5
|
+
* only ever allows LOW/MEDIUM/HIGH from the model) — it exists solely as failSafeClassification's
|
|
6
|
+
* fail-safe value, so callers can route a classifier failure to their most conservative branch
|
|
7
|
+
* instead of silently treating it as LOW risk. See failSafeClassification's doc comment.
|
|
8
|
+
*/
|
|
9
|
+
export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'UNKNOWN';
|
|
10
|
+
/** Only meaningful for `source: 'model_inferred'` facts — see fact-extraction.ts's `UserFact`. */
|
|
11
|
+
export type FactConfidence = 'high' | 'medium' | 'low';
|
|
12
|
+
/**
|
|
13
|
+
* Fixed enum, not free text, so the pending-confirmation queue (Phase 3 of
|
|
14
|
+
* plans/personal_assistant_fact_extraction_llm_confidence_plan.html) can group entries stably
|
|
15
|
+
* instead of drifting between synonyms ("job"/"occupation"/"work") for the same underlying topic.
|
|
16
|
+
*/
|
|
17
|
+
export type FactCategory = 'identity' | 'health' | 'preference' | 'location' | 'occupation' | 'relationships' | 'project' | 'other';
|
|
18
|
+
export interface StatedFact {
|
|
19
|
+
text: string;
|
|
20
|
+
durable: boolean;
|
|
21
|
+
confidence: FactConfidence;
|
|
22
|
+
category: FactCategory;
|
|
23
|
+
}
|
|
24
|
+
export interface TurnIntentContext {
|
|
25
|
+
/** Whether an active durable plan exists for this session — gates whether the abandon
|
|
26
|
+
* judgment means anything and whether plan-template matching should even be attempted
|
|
27
|
+
* (mirrors assistant.ts's own `if (activePlan) { ... } else { match a template } ` split). */
|
|
28
|
+
hasActivePlan: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface TurnIntentClassification {
|
|
31
|
+
riskLevel: RiskLevel;
|
|
32
|
+
riskReason: string;
|
|
33
|
+
/** Computed here from riskLevel/isBulkReminderRequest for trace/display purposes and any
|
|
34
|
+
* caller that only wants the raw classifier verdict. Phase D3: no gating call site reads this
|
|
35
|
+
* field directly anymore — turn-interpreter.ts's approval gate and assistant.ts's
|
|
36
|
+
* execution-mode classification both recompute the decision from riskLevel/isBulkReminderRequest
|
|
37
|
+
* via turn-policy.ts's evaluateTurnPolicy() instead, so a future bug here can't silently change
|
|
38
|
+
* what those care about (INV-14). */
|
|
39
|
+
requiresApproval: boolean;
|
|
40
|
+
/** Only meaningful when riskLevel === 'LOW' — same precondition classifyTriviality had. */
|
|
41
|
+
isTrivial: boolean;
|
|
42
|
+
decomposedTasks: DecomposedTaskSpec[] | null;
|
|
43
|
+
/** True when the request asks to create a calendar/reminder entry (regardless of how many). */
|
|
44
|
+
isReminderRequest: boolean;
|
|
45
|
+
/** True when isReminderRequest is true AND the request looks like it may create more than one
|
|
46
|
+
* reminder in a single turn — same signal risk-classifier.ts's BULK_REMINDER_REASON gated on,
|
|
47
|
+
* folded into requiresApproval the same way. Always false when isReminderRequest is false. */
|
|
48
|
+
isBulkReminderRequest: boolean;
|
|
49
|
+
/** Only meaningful when context.hasActivePlan is true. */
|
|
50
|
+
isAbandonRequest: boolean;
|
|
51
|
+
/** One of listTemplateNames()'s names, or null. Only ever set when context.hasActivePlan is false. */
|
|
52
|
+
matchedPlanTemplate: string | null;
|
|
53
|
+
/**
|
|
54
|
+
* P3 of plans/ask_question_and_plan_mode_plan.html — the generalized, domain-general
|
|
55
|
+
* counterpart to matchedPlanTemplate: true when the request warrants a durable, tracked,
|
|
56
|
+
* user-approved plan even though it doesn't match one of the 7 named template kinds (e.g. a
|
|
57
|
+
* code-implementation request spanning several files/tests). Always false when
|
|
58
|
+
* matchedPlanTemplate is already set (a template match is itself one flavor of "needs a
|
|
59
|
+
* plan" — callers should treat `matchedPlanTemplate !== null || needsMultiStepPlan` as the
|
|
60
|
+
* combined trigger) and always false when context.hasActivePlan is true, same gating
|
|
61
|
+
* matchedPlanTemplate already uses.
|
|
62
|
+
*/
|
|
63
|
+
needsMultiStepPlan: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Every durable/session fact the message states about the user (name, preference,
|
|
66
|
+
* health/dietary, current location/job, ...) — the LLM-backed primary extraction path this
|
|
67
|
+
* plan builds (plans/personal_assistant_fact_extraction_llm_confidence_plan.html Phase 1),
|
|
68
|
+
* superseding the old single-fact `statesDurableFact` fallback so a turn stating more than one
|
|
69
|
+
* fact ("I'm Priya, I'm vegetarian, and I live in Austin") isn't truncated to one. `confidence`
|
|
70
|
+
* is anchored to an observable criterion, not a bare self-report: `high` = stated directly and
|
|
71
|
+
* unhedged in first person; `medium` = stated about the user but hedged, indirect, or inferred
|
|
72
|
+
* from context; `low` = a weak inference, or primarily about a third party and only tangentially
|
|
73
|
+
* about the user. `durable`'s contract is unchanged from the old field: true only for
|
|
74
|
+
* identity/safety-relevant facts meant to persist indefinitely, false for something expected to
|
|
75
|
+
* change. Empty array (not null) when nothing was stated, including on classifier failure — see
|
|
76
|
+
* failSafeClassification.
|
|
77
|
+
*/
|
|
78
|
+
statesDurableFacts: StatedFact[];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Runs the single consolidated LLM call every turn (replacing the old lexical-gate-then-maybe-
|
|
82
|
+
* LLM-call chain) and derives all seven downstream judgments from one structured response. Falls
|
|
83
|
+
* back to failSafeClassification's conservative defaults on any parse failure or LLM error:
|
|
84
|
+
* UNKNOWN risk requiring approval / not trivial / no decomposition / no abandon / no template
|
|
85
|
+
* match / no stated fact — i.e. "do the careful thing" (run the full harness, require approval
|
|
86
|
+
* rather than guessing LOW, don't auto-abandon, return no stated facts when the call failed).
|
|
87
|
+
* Per-task riskLevel (Phase 1 of plans/lexical_functions_hardening_plan.html) is this call's
|
|
88
|
+
* LLM-backed backstop for what used to be a pure-lexical, no-fallback judgment —
|
|
89
|
+
* risk-classifier.ts's standalone per-task classifyRisk — which stays as the free, zero-latency
|
|
90
|
+
* first check; this call is only trusted when it finds nothing (see assistant.ts's call site).
|
|
91
|
+
* statesDurableFacts (Phase 1 of
|
|
92
|
+
* plans/personal_assistant_fact_extraction_llm_confidence_plan.html) is, as of that plan, the
|
|
93
|
+
* *primary* fact-extraction path rather than a fallback — see that plan for how memory-service.ts
|
|
94
|
+
* merges it with fact-extraction.ts's regex backstop.
|
|
95
|
+
*/
|
|
96
|
+
export declare function classifyTurnIntent(message: string, llmClient: ILLMClient, context: TurnIntentContext, model?: string, onUsage?: (usage: TokenUsage) => void): Promise<TurnIntentClassification>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Task } from '@buildaharness/harness';
|
|
2
|
+
import { ILLMClient, TokenUsage, ReminderStore } from '@buildaharness/runtime';
|
|
3
|
+
import { TurnIntentClassification } from './turn-intent-classifier.js';
|
|
4
|
+
import { PlanRecord } from './plan-store.js';
|
|
5
|
+
import { PlanService } from './plan-service.js';
|
|
6
|
+
import { AssistantTurnResult } from './assistant-types.js';
|
|
7
|
+
/**
|
|
8
|
+
* The front portion of runTurn — plan-cancel-bypass detection, the classifyTurnIntent call, the
|
|
9
|
+
* message-level risk-gate decision, and (via `resolveTasks`) decomposition/plan-template
|
|
10
|
+
* resolution — as a pure decision-maker. Deliberately no MemoryAdapter/onTrace dependency beyond
|
|
11
|
+
* what's passed as plain arguments/collaborators (PlanService, ReminderStore), so this stays
|
|
12
|
+
* unit-testable without heavy mocks. Does NOT append to the transcript or call onTrace itself —
|
|
13
|
+
* every variant below carries enough data for the sequencer (assistant.ts) to do both, in the
|
|
14
|
+
* exact order the pre-split code did.
|
|
15
|
+
*/
|
|
16
|
+
export type TurnInterpretation = {
|
|
17
|
+
kind: 'bypass';
|
|
18
|
+
result: AssistantTurnResult;
|
|
19
|
+
transcriptAppend: {
|
|
20
|
+
user: string;
|
|
21
|
+
assistant: string;
|
|
22
|
+
};
|
|
23
|
+
planUpdatedTrace: {
|
|
24
|
+
templateName: string | null;
|
|
25
|
+
completionPct: number;
|
|
26
|
+
};
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'needs_approval';
|
|
29
|
+
classification: TurnIntentClassification;
|
|
30
|
+
result: AssistantTurnResult;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'proceed';
|
|
33
|
+
classification: TurnIntentClassification;
|
|
34
|
+
planForCancelCheck: PlanRecord | null;
|
|
35
|
+
};
|
|
36
|
+
export interface ResolvedTasks {
|
|
37
|
+
initialTasks: Task[];
|
|
38
|
+
activePlan: PlanRecord | null;
|
|
39
|
+
/** Only set when a NEW plan match was attempted this turn (i.e. no active plan was already being resumed) — mirrors the original code's `else` branch, which is the only place this trace fired. */
|
|
40
|
+
planClassifiedTrace?: {
|
|
41
|
+
isCandidate: boolean;
|
|
42
|
+
matchedTemplate: string | null;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export declare class TurnInterpreter {
|
|
46
|
+
private readonly llmClient;
|
|
47
|
+
private readonly model;
|
|
48
|
+
private readonly planService;
|
|
49
|
+
private readonly reminderStore;
|
|
50
|
+
constructor(llmClient: ILLMClient, model: () => string | undefined, planService: PlanService, reminderStore: ReminderStore);
|
|
51
|
+
interpretIntent(params: {
|
|
52
|
+
userMessage: string;
|
|
53
|
+
sessionId: string;
|
|
54
|
+
toolLoopWillRun: boolean;
|
|
55
|
+
approved: boolean;
|
|
56
|
+
dangerouslySkipPermissions: boolean;
|
|
57
|
+
onUsage: (usage: TokenUsage) => void;
|
|
58
|
+
}): Promise<TurnInterpretation>;
|
|
59
|
+
/**
|
|
60
|
+
* Decomposition/plan-template resolution — only meaningful (and only ever called by the
|
|
61
|
+
* sequencer) once a turn is known NOT to be trivial, since a trivial turn returns before any
|
|
62
|
+
* of this would matter and must not spend buildPlanFromTemplate's LLM call for nothing.
|
|
63
|
+
*/
|
|
64
|
+
resolveTasks(params: {
|
|
65
|
+
userMessage: string;
|
|
66
|
+
sessionId: string;
|
|
67
|
+
classification: TurnIntentClassification;
|
|
68
|
+
planForCancelCheck: PlanRecord | null;
|
|
69
|
+
onUsage: (usage: TokenUsage) => void;
|
|
70
|
+
}): Promise<ResolvedTasks>;
|
|
71
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { RiskLevel } from './turn-intent-classifier.js';
|
|
2
|
+
/**
|
|
3
|
+
* Phase D3 (plans/harness_consolidation_and_control_plane_plan.html, A3): the deterministic,
|
|
4
|
+
* authoritative decision point for whether a turn's message-level risk requires human approval
|
|
5
|
+
* before the turn proceeds — the message-level analog of tool-policy.ts's evaluateToolPolicy().
|
|
6
|
+
*
|
|
7
|
+
* turn-intent-classifier.ts's own `requiresApproval` field is computed from the same
|
|
8
|
+
* riskLevel/isBulkReminderRequest signals and still exists for trace/display purposes, but no
|
|
9
|
+
* gating call site should read that boolean directly — TurnPolicyInput deliberately has no
|
|
10
|
+
* `requiresApproval` field to read, so a gate built on this module always recomputes the decision
|
|
11
|
+
* from the underlying signals rather than trusting a pass-through boolean (INV-14: a consequential
|
|
12
|
+
* decision cannot be set by LLM output alone). See turn-interpreter.ts and assistant.ts's
|
|
13
|
+
* classifyAndTraceExecutionMode call sites for where this replaces a direct
|
|
14
|
+
* `classification.requiresApproval` read.
|
|
15
|
+
*/
|
|
16
|
+
export type TurnPolicyDecision = 'ALLOW' | 'REQUIRE_APPROVAL';
|
|
17
|
+
export interface TurnPolicyInput {
|
|
18
|
+
/** turn-intent-classifier.ts's RiskLevel — a signal, never itself the gate (same contract
|
|
19
|
+
* tool-policy.ts's ToolPolicyInput.riskHint documents). */
|
|
20
|
+
riskHint: RiskLevel;
|
|
21
|
+
/** turn-intent-classifier.ts's own isBulkReminderRequest verdict — only meaningful alongside a
|
|
22
|
+
* MEDIUM riskHint (see turn-intent-classifier.ts), but read here unconditionally since a
|
|
23
|
+
* MEDIUM-only precondition on this field is itself already enforced upstream. */
|
|
24
|
+
isBulkReminderRequest: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface TurnPolicyResult {
|
|
27
|
+
decision: TurnPolicyDecision;
|
|
28
|
+
reason: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function evaluateTurnPolicy(input: TurnPolicyInput): TurnPolicyResult;
|
|
31
|
+
export interface AbandonPolicyInput {
|
|
32
|
+
/** Structural fact — an active durable plan record actually exists for this session,
|
|
33
|
+
* independent of anything the LLM said this turn. */
|
|
34
|
+
hasActivePlan: boolean;
|
|
35
|
+
/** turn-intent-classifier.ts's isAbandonRequest verdict — a signal, never itself sufficient:
|
|
36
|
+
* an LLM claiming "abandon the plan" means nothing when no plan is actually active. */
|
|
37
|
+
abandonHint: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The deterministic gate for plan abandonment: `abandonHint` alone is never enough — it only
|
|
41
|
+
* takes effect when `hasActivePlan` (independent of the LLM call) is also true. Centralizes the
|
|
42
|
+
* same check turn-intent-classifier.ts and turn-interpreter.ts each already performed inline, so
|
|
43
|
+
* it's asserted once, not maintained in two places that could drift apart.
|
|
44
|
+
*/
|
|
45
|
+
export declare function evaluateAbandonPolicy(input: AbandonPolicyInput): boolean;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-agnostic SSRF guard + safe-fetch core used by web-tools.ts (desktop/CLI path). Split
|
|
3
|
+
* out of web-tools.ts so the guard/redirect/byte-cap logic is one focused module instead of
|
|
4
|
+
* living inline in the tool executor.
|
|
5
|
+
*
|
|
6
|
+
* packages/proxy/src/web-fetch.ts (POST /web/fetch, W2) intentionally carries its own copy of
|
|
7
|
+
* this guard rather than importing this module as a workspace dependency — same call W1's
|
|
8
|
+
* web-search.ts already made for the search parsing code: this package's only `exports` entry is
|
|
9
|
+
* the whole `dist/index.js` bundle (nodemailer, the MCP SDK, the CLI), and this repo's proxy CI
|
|
10
|
+
* job (and this plan's own suggested gate script) typechecks/tests packages/proxy without a
|
|
11
|
+
* `build:personal-assistant` step first, so a real cross-package import isn't actually wired up
|
|
12
|
+
* to build here. Keep the two guards in sync by hand if one changes — same discipline the ported
|
|
13
|
+
* DDG/Brave search code in web-search.ts already requires.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Thrown by assertPublicHttpUrl instead of returning a falsy value, so callers
|
|
17
|
+
* can't accidentally proceed past a rejected URL.
|
|
18
|
+
*/
|
|
19
|
+
export declare class PrivateNetworkTargetError extends Error {
|
|
20
|
+
readonly requestedUrl: string;
|
|
21
|
+
readonly detail: string;
|
|
22
|
+
constructor(requestedUrl: string, detail: string);
|
|
23
|
+
}
|
|
24
|
+
/** Thrown when the fetched body's content-type (header and/or sniffed bytes) isn't on the allowlist. */
|
|
25
|
+
export declare class UnsupportedContentTypeError extends Error {
|
|
26
|
+
readonly requestedUrl: string;
|
|
27
|
+
readonly detail: string;
|
|
28
|
+
constructor(requestedUrl: string, detail: string);
|
|
29
|
+
}
|
|
30
|
+
/** Resolves a hostname to its IP addresses. Injected so assertPublicHttpUrl stays unit-testable without real DNS/network access. */
|
|
31
|
+
export type DnsResolver = (hostname: string) => Promise<string[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Parses `url`, rejects non-http(s) schemes / credentialed URLs / non-80/443 ports / raw IP
|
|
34
|
+
* literals outright (all before any DNS call), then resolves the hostname and throws
|
|
35
|
+
* PrivateNetworkTargetError if any resolved address is loopback, RFC1918 private, link-local,
|
|
36
|
+
* or a well-known cloud metadata address. Must be called again on every redirect hop — a public
|
|
37
|
+
* URL can 302 to a private one — which is exactly what fetchTextSafely's redirect loop below does.
|
|
38
|
+
*/
|
|
39
|
+
export declare function assertPublicHttpUrl(url: string, dns?: DnsResolver): Promise<void>;
|
|
40
|
+
export declare const MAX_REDIRECTS = 5;
|
|
41
|
+
export declare const MAX_FETCH_CHARS = 15000;
|
|
42
|
+
export interface FetchTextSafelyOptions {
|
|
43
|
+
url: string;
|
|
44
|
+
dns?: DnsResolver;
|
|
45
|
+
fetchImpl?: typeof fetch;
|
|
46
|
+
maxBytes?: number;
|
|
47
|
+
maxRedirects?: number;
|
|
48
|
+
allowedContentTypes?: string[];
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
}
|
|
51
|
+
export interface FetchTextSafelyResult {
|
|
52
|
+
text: string;
|
|
53
|
+
finalUrl: string;
|
|
54
|
+
/** True if either the streamed byte cap or the char-level cap truncated the returned text. */
|
|
55
|
+
truncated: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Fetches `options.url`, following redirects manually (not via fetch's automatic redirect-follow)
|
|
59
|
+
* so every hop gets its own assertPublicHttpUrl check — a public URL that 302s to a private
|
|
60
|
+
* target is rejected mid-fetch, not silently followed. Enforces a streamed byte cap (a
|
|
61
|
+
* Content-Length header can lie), a content-type allowlist (checked against both the header and
|
|
62
|
+
* the sniffed body bytes), and a connect+read timeout. Sends a fixed User-Agent and nothing else
|
|
63
|
+
* client-supplied — zero ambient authority, safe to run on a caller's behalf server-side.
|
|
64
|
+
*/
|
|
65
|
+
export declare function fetchTextSafely(options: FetchTextSafelyOptions): Promise<FetchTextSafelyResult>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { WebSearchResult } from './web-tools.js';
|
|
2
|
+
export interface BraveSearchOptions {
|
|
3
|
+
maxResults?: number;
|
|
4
|
+
fetchImpl?: typeof fetch;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Queries the Brave Search API and returns a bounded, parsed result list. Matches
|
|
8
|
+
* WebToolsContext['search']'s signature. Requires a Brave Search API key — see
|
|
9
|
+
* https://api.search.brave.com/app/keys — passed as `apiKey`, sent as the
|
|
10
|
+
* `X-Subscription-Token` header Brave's API expects.
|
|
11
|
+
*/
|
|
12
|
+
export declare function braveSearch(query: string, apiKey: string, options?: BraveSearchOptions): Promise<WebSearchResult[]>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ToolDefinition } from '@buildaharness/runtime';
|
|
2
|
+
import { DnsResolver } from './web-fetch-core.js';
|
|
3
|
+
export interface WebSearchResult {
|
|
4
|
+
title: string;
|
|
5
|
+
url: string;
|
|
6
|
+
snippet: string;
|
|
7
|
+
}
|
|
8
|
+
export { PrivateNetworkTargetError, assertPublicHttpUrl } from './web-fetch-core.js';
|
|
9
|
+
export type { DnsResolver } from './web-fetch-core.js';
|
|
10
|
+
export interface WebToolsContext {
|
|
11
|
+
/** No default implementation — the caller supplies a real search backend (an API client, etc.), same way FileToolsContext's `backend` is injected rather than defaulted to real disk. See `braveSearch` in web-search-provider.ts for a ready-made one. */
|
|
12
|
+
search(query: string): Promise<WebSearchResult[]>;
|
|
13
|
+
/** Overrides the HTTP client fetch_url uses — defaults to the global `fetch`. Lets tests and non-browser runtimes inject their own. */
|
|
14
|
+
fetchImpl?: typeof fetch;
|
|
15
|
+
/** Injected DNS resolver for the SSRF guard below — defaults to a lazily-imported node:dns/promises. Tests inject a fake to avoid real network access. */
|
|
16
|
+
dns?: DnsResolver;
|
|
17
|
+
}
|
|
18
|
+
export declare const WEB_SEARCH_TOOL: ToolDefinition;
|
|
19
|
+
export declare const FETCH_URL_TOOL: ToolDefinition;
|
|
20
|
+
export declare const WEB_TOOLS: ToolDefinition[];
|
|
21
|
+
export type WebToolResult = {
|
|
22
|
+
kind: 'text';
|
|
23
|
+
text: string;
|
|
24
|
+
};
|
|
25
|
+
/** Executes web_search/fetch_url. Both return raw, untagged text — trust-tagging is applied by the caller (assistant.ts), not here, so this stays a plain I/O layer like executeFileTool. */
|
|
26
|
+
export declare function executeWebTool(ctx: WebToolsContext, toolName: string, input: Record<string, unknown>): Promise<WebToolResult>;
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@buildaharness/aielia",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "An open-source AI chat assistant that runs the full 11-layer Build A Harness every turn — light for a fact lookup, and it stops for approval before it sends an email or runs a shell command. CLI, browser, and desktop.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://buildaharness.com/personal-assistant",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/3IVIS/buildaharness.git",
|
|
10
|
+
"directory": "packages/aielia"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"aielia",
|
|
14
|
+
"ai-assistant",
|
|
15
|
+
"personal-assistant",
|
|
16
|
+
"chat-agent",
|
|
17
|
+
"agent-harness",
|
|
18
|
+
"human-in-the-loop",
|
|
19
|
+
"tool-approval",
|
|
20
|
+
"self-hosted",
|
|
21
|
+
"llm",
|
|
22
|
+
"openclaw-alternative",
|
|
23
|
+
"open-source"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"bin": {
|
|
36
|
+
"aielia": "./dist/cli.js"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "vite build --config vite.config.lib.ts && cp src/file-tools-mcp-server.mjs dist/file-tools-mcp-server.mjs && mkdir -p dist/lexical/patterns && cp src/lexical/patterns/*.json dist/lexical/patterns/ && node -e \"const f='dist/cli.js',s=require('fs'),c=s.readFileSync(f,'utf8');if(!c.startsWith('#!'))s.writeFileSync(f,'#!/usr/bin/env node\\n'+c)\"",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"cli": "tsx src/cli.ts",
|
|
46
|
+
"eval:turn-intent": "tsx scripts/eval-turn-intent.ts",
|
|
47
|
+
"eval:contradiction-checker": "tsx scripts/eval-contradiction-checker.ts",
|
|
48
|
+
"eval:harness-benchmark": "tsx scripts/run-harness-benchmark.ts"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@buildaharness/harness": "^0.2.0",
|
|
52
|
+
"@buildaharness/runtime": "^0.2.0",
|
|
53
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
54
|
+
"ink": "^7.1.1",
|
|
55
|
+
"nodemailer": "^6.9.14",
|
|
56
|
+
"react": "^19.3.0",
|
|
57
|
+
"zod": "^3.25.76"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "^20.14.0",
|
|
61
|
+
"@types/nodemailer": "^6.4.15",
|
|
62
|
+
"@types/react": "^19.3.0",
|
|
63
|
+
"tsx": "^4.16.0",
|
|
64
|
+
"typescript": "^5.4.5",
|
|
65
|
+
"vite": "^5.3.4",
|
|
66
|
+
"vite-plugin-dts": "^4.5.4",
|
|
67
|
+
"vitest": "^1.6.0"
|
|
68
|
+
}
|
|
69
|
+
}
|