@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.
Files changed (127) hide show
  1. package/README.md +832 -0
  2. package/dist/action-approval-service.d.ts +52 -0
  3. package/dist/action-snapshot.d.ts +173 -0
  4. package/dist/action-tools.d.ts +38 -0
  5. package/dist/agent-loop.d.ts +372 -0
  6. package/dist/answer-claim.d.ts +51 -0
  7. package/dist/ask-clarification-service.d.ts +70 -0
  8. package/dist/ask-mode-flag.d.ts +25 -0
  9. package/dist/ask-response-format.d.ts +8 -0
  10. package/dist/assistant-session.d.ts +267 -0
  11. package/dist/assistant-source.d.ts +14 -0
  12. package/dist/assistant-types.d.ts +158 -0
  13. package/dist/assistant.d.ts +419 -0
  14. package/dist/batch-list-detector.d.ts +15 -0
  15. package/dist/batch-research-fixtures.d.ts +50 -0
  16. package/dist/benchmark-injected-failure.d.ts +27 -0
  17. package/dist/claude-cli-llm-client.d.ts +139 -0
  18. package/dist/claude-cli-prompt.d.ts +86 -0
  19. package/dist/cli-config.d.ts +27 -0
  20. package/dist/cli-session-D0uLdT2A.js +7863 -0
  21. package/dist/cli-session-D0uLdT2A.js.map +1 -0
  22. package/dist/cli-session.d.ts +63 -0
  23. package/dist/cli.d.ts +65 -0
  24. package/dist/cli.js +1693 -0
  25. package/dist/cli.js.map +1 -0
  26. package/dist/config.d.ts +175 -0
  27. package/dist/contradiction-checker.d.ts +27 -0
  28. package/dist/debug-log.d.ts +16 -0
  29. package/dist/decomposition-classifier.d.ts +38 -0
  30. package/dist/doctor-checks.d.ts +8 -0
  31. package/dist/email-smtp.d.ts +19 -0
  32. package/dist/email.d.ts +60 -0
  33. package/dist/error-classifier.d.ts +13 -0
  34. package/dist/execution-mode.d.ts +85 -0
  35. package/dist/fact-extraction.d.ts +143 -0
  36. package/dist/failure-mode-matcher.d.ts +32 -0
  37. package/dist/file-tools-mcp-server.mjs +1027 -0
  38. package/dist/file-tools.d.ts +179 -0
  39. package/dist/first-run.d.ts +22 -0
  40. package/dist/harness-bridge.d.ts +103 -0
  41. package/dist/index.d.ts +58 -0
  42. package/dist/index.js +212 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/ink-test-render.d.ts +24 -0
  45. package/dist/lexical/patterns/batch-list-markers.json +8 -0
  46. package/dist/lexical/patterns/batch-list-markers.json.d.ts +11 -0
  47. package/dist/lexical/patterns/coding-fact-markers.json +8 -0
  48. package/dist/lexical/patterns/coding-fact-markers.json.d.ts +11 -0
  49. package/dist/lexical/patterns/enumeration-markers.json +20 -0
  50. package/dist/lexical/patterns/enumeration-markers.json.d.ts +23 -0
  51. package/dist/lexical/patterns/fact-markers.json +16 -0
  52. package/dist/lexical/patterns/fact-markers.json.d.ts +19 -0
  53. package/dist/lexical/patterns/injection-patterns.json +20 -0
  54. package/dist/lexical/patterns/injection-patterns.json.d.ts +23 -0
  55. package/dist/lexical/patterns/plan-mode-markers.json +10 -0
  56. package/dist/lexical/patterns/plan-mode-markers.json.d.ts +13 -0
  57. package/dist/lexical/patterns/risk-patterns.json +122 -0
  58. package/dist/lexical/patterns/risk-patterns.json.d.ts +125 -0
  59. package/dist/lexical/patterns/task-cancel-markers.json +22 -0
  60. package/dist/lexical/patterns/task-cancel-markers.json.d.ts +25 -0
  61. package/dist/lexical/patterns/template-keywords.json +20 -0
  62. package/dist/lexical/patterns/template-keywords.json.d.ts +23 -0
  63. package/dist/lexical/patterns/tool-yield-markers.json +22 -0
  64. package/dist/lexical/patterns/tool-yield-markers.json.d.ts +25 -0
  65. package/dist/lexical/patterns.d.ts +64 -0
  66. package/dist/memory-service.d.ts +219 -0
  67. package/dist/model-pricing.d.ts +19 -0
  68. package/dist/network-containment.d.ts +21 -0
  69. package/dist/node-config-store.d.ts +29 -0
  70. package/dist/node-display-names.d.ts +26 -0
  71. package/dist/node-fs-backend.d.ts +8 -0
  72. package/dist/non-interactive-mode.d.ts +22 -0
  73. package/dist/one-loop-flag.d.ts +38 -0
  74. package/dist/plan-approval-service.d.ts +62 -0
  75. package/dist/plan-builder.d.ts +32 -0
  76. package/dist/plan-drafting-service.d.ts +87 -0
  77. package/dist/plan-drafting.d.ts +58 -0
  78. package/dist/plan-mode-flag.d.ts +34 -0
  79. package/dist/plan-service.d.ts +61 -0
  80. package/dist/plan-sketch-service.d.ts +24 -0
  81. package/dist/plan-sketch.d.ts +20 -0
  82. package/dist/plan-store.d.ts +232 -0
  83. package/dist/plan-templates/data/content_creation.json.d.ts +75 -0
  84. package/dist/plan-templates/data/decision_making.json.d.ts +66 -0
  85. package/dist/plan-templates/data/problem_solving.json.d.ts +75 -0
  86. package/dist/plan-templates/data/process_improvement.json.d.ts +57 -0
  87. package/dist/plan-templates/data/project_planning.json.d.ts +66 -0
  88. package/dist/plan-templates/data/research_analysis.json.d.ts +66 -0
  89. package/dist/plan-templates/data/trip_planning.json.d.ts +66 -0
  90. package/dist/plan-templates/index.d.ts +33 -0
  91. package/dist/plan-verification.d.ts +31 -0
  92. package/dist/reminder-tools.d.ts +13 -0
  93. package/dist/response-service.d.ts +73 -0
  94. package/dist/review-checker.d.ts +22 -0
  95. package/dist/risk-classifier.d.ts +7 -0
  96. package/dist/scripted-llm-client.d.ts +37 -0
  97. package/dist/semantic-criterion-coverage.d.ts +30 -0
  98. package/dist/shell-executor.d.ts +14 -0
  99. package/dist/shell-tools.d.ts +84 -0
  100. package/dist/spend-cap.d.ts +41 -0
  101. package/dist/spike.d.ts +1 -0
  102. package/dist/spike.js +18 -0
  103. package/dist/spike.js.map +1 -0
  104. package/dist/supervisor-decider.d.ts +3 -0
  105. package/dist/system-prompt.d.ts +8 -0
  106. package/dist/task-mapping.d.ts +42 -0
  107. package/dist/tool-control-plane.d.ts +67 -0
  108. package/dist/tool-policy.d.ts +45 -0
  109. package/dist/tool-step.d.ts +23 -0
  110. package/dist/tool-yield-classifier.d.ts +7 -0
  111. package/dist/trace-events.d.ts +126 -0
  112. package/dist/transcript-compaction.d.ts +13 -0
  113. package/dist/trust-tagging.d.ts +38 -0
  114. package/dist/tui-app-DxLPSGh2.js +481 -0
  115. package/dist/tui-app-DxLPSGh2.js.map +1 -0
  116. package/dist/tui-app.d.ts +125 -0
  117. package/dist/tui-input.d.ts +51 -0
  118. package/dist/tui-mode-flag.d.ts +36 -0
  119. package/dist/tui-output-capture.d.ts +30 -0
  120. package/dist/tui-spike.d.ts +1 -0
  121. package/dist/turn-intent-classifier.d.ts +96 -0
  122. package/dist/turn-interpreter.d.ts +71 -0
  123. package/dist/turn-policy.d.ts +45 -0
  124. package/dist/web-fetch-core.d.ts +65 -0
  125. package/dist/web-search-provider.d.ts +12 -0
  126. package/dist/web-tools.d.ts +26 -0
  127. package/package.json +69 -0
@@ -0,0 +1,179 @@
1
+ import { FsBackend, ToolDefinition } from '@buildaharness/runtime';
2
+ import { SendEmail, SendEmailResult } from './email.js';
3
+ /**
4
+ * Thrown by resolveInWorkspace/assertRealPathInWorkspace instead of returning
5
+ * a falsy value, so callers can't accidentally proceed past a rejected path.
6
+ */
7
+ export declare class PathOutsideWorkspaceError extends Error {
8
+ readonly requestedPath: string;
9
+ constructor(requestedPath: string);
10
+ }
11
+ /**
12
+ * Resolves `requestedPath` against `workspaceRoot` and rejects anything that
13
+ * escapes it (`../` traversal, or an absolute path outside the root). Purely
14
+ * string-based — no real filesystem access, no node:path — so this module has
15
+ * no environment-specific dependency and stays safe to import from assistant.ts,
16
+ * which is bundled into both the Node CLI and the browser (chat-ui) builds.
17
+ *
18
+ * This alone cannot catch a symlink *inside* the workspace that points outside
19
+ * it; see assertRealPathInWorkspace for that (needs real I/O via FsBackend).
20
+ */
21
+ export declare function resolveInWorkspace(workspaceRoot: string, requestedPath: string): string;
22
+ /**
23
+ * Defense in depth beyond resolveInWorkspace's string-prefix check: resolves
24
+ * symlinks (via the injected backend's optional `realpath`) on the nearest
25
+ * existing ancestor of `resolvedPath` and re-verifies it's still under the
26
+ * workspace root's real path. A no-op (best-effort only) if the backend
27
+ * doesn't implement `realpath`.
28
+ */
29
+ export declare function assertRealPathInWorkspace(backend: FsBackend, workspaceRoot: string, resolvedPath: string): Promise<void>;
30
+ export declare const READ_FILE_TOOL: ToolDefinition;
31
+ export declare const LIST_DIRECTORY_TOOL: ToolDefinition;
32
+ export declare const WRITE_FILE_TOOL: ToolDefinition;
33
+ export declare const FILE_TOOLS: ToolDefinition[];
34
+ export interface FileToolsContext {
35
+ backend: FsBackend;
36
+ workspaceRoot: string;
37
+ }
38
+ export type FileToolResult = {
39
+ kind: 'text';
40
+ text: string;
41
+ } | {
42
+ kind: 'staged_write';
43
+ id: string;
44
+ path: string;
45
+ content: string;
46
+ };
47
+ export declare function requireStringArg(input: Record<string, unknown>, key: string): string;
48
+ /** Executes one of the three file tools by name. Real I/O for read/list; write_file only stages (see T2). */
49
+ export declare function executeFileTool(ctx: FileToolsContext, toolName: string, input: Record<string, unknown>): Promise<FileToolResult>;
50
+ export type PendingActionPayload = {
51
+ kind: 'write';
52
+ path: string;
53
+ content: string;
54
+ } | {
55
+ kind: 'shell';
56
+ command: string;
57
+ cwd: string;
58
+ }
59
+ /**
60
+ * An email the model has proposed via send_email (action-tools.ts). Staged like write/shell and
61
+ * never delivered until a human approves — see applyPendingAction's email branch, which calls the
62
+ * injected SendEmail transport. Deliberately not in the undo-log: a sent email is one-shot and
63
+ * irreversible, the same reason `kind: 'revert'` below writes no undo entry either.
64
+ */
65
+ | {
66
+ kind: 'email';
67
+ to: string;
68
+ subject: string;
69
+ body: string;
70
+ cc?: string;
71
+ bcc?: string;
72
+ }
73
+ /**
74
+ * Staged by /undo-action (T3), never by the model — reverts a previously-applied write/shell
75
+ * action back to its pre-action state. `revertedEntryId` names the undo-log entry this reverts
76
+ * (deleted once applied — see applyPendingAction's revert branch). `restore`/`remove` are the
77
+ * concrete filesystem operations (see action-snapshot.ts's buildRevertPlan), computed once at
78
+ * staging time so apply doesn't need to re-derive them from a (possibly since-pruned) undo-log
79
+ * entry.
80
+ */
81
+ | {
82
+ kind: 'revert';
83
+ revertedEntryId: string;
84
+ restore: {
85
+ path: string;
86
+ content: string;
87
+ }[];
88
+ remove: string[];
89
+ };
90
+ /**
91
+ * `nextPendingActionId` chains a second (or later) approval-gated action staged from the same
92
+ * originating turn — e.g. the claude-cli MCP server (file-tools-mcp-server.mjs) links
93
+ * consecutive stagePendingAction calls within one `claude -p` subprocess this way, so
94
+ * resolvePendingAction (assistant.ts) can surface the next one as its own needs_approval instead
95
+ * of silently dropping it once the first resolves. Absent for a standalone staged action.
96
+ *
97
+ * `chainedFrom` marks a record as itself being the *target* of such a chain — i.e. a preceding
98
+ * action from the same turn was already resolved (approved-and-applied, or declined) before this
99
+ * one was ever shown for approval. resolvePendingAction's decline branch (assistant.ts) uses this
100
+ * to avoid claiming "nothing was written or run" when something from earlier in the same chain
101
+ * may already have been.
102
+ */
103
+ export type PendingActionRecord = {
104
+ id: string;
105
+ stagedAt: string;
106
+ nextPendingActionId?: string;
107
+ chainedFrom?: boolean;
108
+ } & PendingActionPayload;
109
+ /** Result of actually running a previously staged shell command — see shell-executor.ts. */
110
+ export interface ShellExecutionResult {
111
+ /** Combined stdout+stderr, truncated to a byte cap. */
112
+ output: string;
113
+ exitCode: number | null;
114
+ timedOut: boolean;
115
+ }
116
+ export type ApplyPendingActionResult = ({
117
+ kind: 'write';
118
+ } & PendingActionRecord) | ({
119
+ kind: 'shell';
120
+ } & PendingActionRecord & {
121
+ execution: ShellExecutionResult;
122
+ }) | ({
123
+ kind: 'revert';
124
+ } & PendingActionRecord) | ({
125
+ kind: 'email';
126
+ } & PendingActionRecord & {
127
+ delivery: SendEmailResult;
128
+ });
129
+ /**
130
+ * Stages an action for later approval. `id` is a random UUID, not derived from the session — see
131
+ * T4 of the file-tools plan.
132
+ *
133
+ * Structural hardening principle (plans/lexical_functions_hardening_plan.html Phase 4 step 1):
134
+ * this gate runs unconditionally on the tool call's own concrete arguments (the literal path/
135
+ * content or command/cwd being proposed), independent of whatever risk-classifier.ts's pre-flight
136
+ * text patterns concluded about the user's phrasing. That's deliberate — it's the same "gate on
137
+ * the concrete tool call, not the free text" pattern Claude Code and Codex CLI use (matching
138
+ * literal tool-call/command strings, not classifying natural-language risk). Any future
139
+ * consequential tool (an MCP-provided send_email, a payments API, a social-posting integration)
140
+ * must get this same unconditional stagePendingAction-style gate on its own call arguments — it
141
+ * must not rely solely on risk-classifier.ts catching the request first, since a classifier can
142
+ * always miss a phrasing it wasn't built to recognize.
143
+ */
144
+ export declare function stagePendingAction(backend: FsBackend, workspaceRoot: string, payload: PendingActionPayload): Promise<{
145
+ id: string;
146
+ }>;
147
+ export declare function loadPendingAction(backend: FsBackend, workspaceRoot: string, id: string): Promise<PendingActionRecord | undefined>;
148
+ /**
149
+ * Applies a previously staged action for real, then deletes its staging record. Throws if `id`
150
+ * isn't staged. `kind: 'write'` is applied directly (pure FsBackend I/O, safe in any environment
151
+ * this package runs in). `kind: 'shell'` requires an injected `executeShell` callback instead of
152
+ * this module spawning a process itself — file-tools.ts has no Node dependency of its own (it's
153
+ * bundled into the browser build via assistant.ts/index.ts), so the real child_process.spawn call
154
+ * lives in shell-executor.ts and is only ever wired in by Node-only callers (cli.ts).
155
+ */
156
+ export declare function applyPendingAction(backend: FsBackend, workspaceRoot: string, id: string, options?: {
157
+ executeShell?: (command: string, cwd: string) => Promise<ShellExecutionResult>;
158
+ sendEmail?: SendEmail;
159
+ }): Promise<ApplyPendingActionResult>;
160
+ /** Deletes a staged action without applying it. Used when the user declines. No-op if `id` isn't staged. */
161
+ export declare function discardPendingAction(backend: FsBackend, workspaceRoot: string, id: string): Promise<void>;
162
+ /**
163
+ * A staged action left behind by a crashed or abandoned turn is harmless (never applied without
164
+ * its matching id — see applyPendingAction) but, left unswept, accumulates forever. Records older
165
+ * than this are considered abandoned rather than legitimately pending — mirrors the naming
166
+ * convention of action-snapshot.ts's UNDO_LOG_MAX_ENTRIES (T12 of the gap-coverage plan).
167
+ */
168
+ export declare const PENDING_ACTION_MAX_AGE_MS: number;
169
+ /**
170
+ * Deletes `.pending-actions/` records older than `PENDING_ACTION_MAX_AGE_MS`. Called from
171
+ * PersonalAssistant's startup sweep (see assistant.ts), which is itself responsible for first
172
+ * confirming no session has a checkpoint still eligible for resume — this function only applies
173
+ * the age cutoff and has no knowledge of checkpoints itself. `now` is a parameter (not read
174
+ * directly from Date.now()) so tests can pin a fixed reference time. Corrupt records are left in
175
+ * place rather than swept — not this function's job to repair malformed JSON.
176
+ */
177
+ export declare function sweepAbandonedPendingActions(backend: FsBackend, workspaceRoot: string, now?: number): Promise<{
178
+ swept: string[];
179
+ }>;
@@ -0,0 +1,22 @@
1
+ import { AssistantConfig, ConfigStore } from './config.js';
2
+ export interface FirstRunDeps {
3
+ configStore: ConfigStore;
4
+ /** Result of `configStore.load()` — the setup is skipped entirely if this already names a backend/key. */
5
+ persisted: Partial<AssistantConfig>;
6
+ /** Keys pinned by an env var (ASSISTANT_LLM_BACKEND etc.) — also a signal the user has already chosen. */
7
+ overriddenKeys: ReadonlySet<keyof AssistantConfig>;
8
+ /** False for piped/scripted stdin — setup is skipped and today's behavior (proxy default) is kept. */
9
+ isInteractive: boolean;
10
+ /** Reads one line from the user, already trimmed. */
11
+ ask: (question: string) => Promise<string>;
12
+ /** True when an authenticated `claude` CLI is available on PATH. */
13
+ detectClaudeCli: () => Promise<boolean>;
14
+ /** Where to print prompts/status (defaults to process.stdout in cli.ts). */
15
+ log: (line: string) => void;
16
+ }
17
+ /**
18
+ * Runs the first-run pass if needed. Returns the persisted config to use from
19
+ * here on — unchanged when setup was skipped or the user bailed out, or a fresh
20
+ * object reflecting what was just written.
21
+ */
22
+ export declare function maybeRunFirstRunSetup(deps: FirstRunDeps): Promise<Partial<AssistantConfig>>;
@@ -0,0 +1,103 @@
1
+ import { ExperienceStore, Task, CheckpointStore, LayerActivityEvent, HarnessCheckpoint, VerificationResult, HarnessRunResult, ToolExecutorContext, InvestigationRequestData, InvestigationFinding, UpdateChannel } from '@buildaharness/harness';
2
+ import { ILLMClient, MemoryAdapter, TokenUsage } from '@buildaharness/runtime';
3
+ import { OneLoopMode } from './one-loop-flag.js';
4
+ import { UserFact } from './fact-extraction.js';
5
+ import { AssistantSession } from './assistant-session.js';
6
+ import { PlanRecord } from './plan-store.js';
7
+ import { PlanService } from './plan-service.js';
8
+ import { TurnIntentClassification } from './turn-intent-classifier.js';
9
+ import { AssistantSource } from './assistant-source.js';
10
+ import { AssistantProgress } from './assistant-types.js';
11
+ import { TraceEvent } from './trace-events.js';
12
+ export type HarnessOutcome = {
13
+ status: 'paused';
14
+ checkpoint: HarnessCheckpoint;
15
+ lastVerification: VerificationResult | null;
16
+ layerActivity: LayerActivityEvent[];
17
+ } | {
18
+ status: 'completed';
19
+ result: HarnessRunResult;
20
+ lastVerification: VerificationResult | null;
21
+ layerActivity: LayerActivityEvent[];
22
+ };
23
+ export interface HarnessRunParams {
24
+ sessionId: string;
25
+ userMessage: string;
26
+ facts: UserFact[];
27
+ /**
28
+ * Phase 4 of plans/personal_assistant_fact_extraction_llm_confidence_plan.html: this turn's
29
+ * merged lexical+LLM fact list — memory-service.ts's `buildTurnFacts()`, the exact helper
30
+ * `recordFacts()` itself uses to decide what to write to the fact stores — fed into
31
+ * `factExtractor` below as this turn's "isNew" belief seed. Replaces this file's former direct
32
+ * `extractFactsFromTurn(objective, runId)` call, which only ever saw the free lexical pass and
33
+ * left a same-turn LLM-caught fact invisible to contradiction detection until (if ever) a later
34
+ * turn's re-seed. Defaults to `[]` when omitted, matching pre-Phase-4 behavior for a caller that
35
+ * doesn't supply it.
36
+ */
37
+ currentTurnFacts?: UserFact[];
38
+ draftReply: string;
39
+ classification: TurnIntentClassification;
40
+ initialTasks: Task[];
41
+ activePlan: PlanRecord | null;
42
+ sources: AssistantSource[] | undefined;
43
+ onProgress?: (progress: AssistantProgress) => void;
44
+ onUsage: (usage: TokenUsage) => void;
45
+ /**
46
+ * R2 of plans/harness_d2_one_loop_rewire_plan.html: a harness-driven proposer (built by
47
+ * AgentLoop.createHarnessProposer) swapped in as the toolExecutors 'default' entry instead of
48
+ * `() => draftReply`, when the one-loop flag is enabled and a caller actually supplies one.
49
+ * `undefined` (every caller today, before R3 wires runTurn to build one) means `run()` falls
50
+ * back to `() => draftReply` regardless of the flag — R2 only builds the mechanism and proves it
51
+ * against a caller that supplies this directly; wiring runTurn to always supply one when the
52
+ * flag is on is R3's scope.
53
+ */
54
+ oneLoopProposer?: (toolCtx: ToolExecutorContext) => unknown | Promise<unknown>;
55
+ /**
56
+ * Trajectory Supervisor GATHER_EVIDENCE host (S5 of
57
+ * plans/harness_trajectory_supervisor_plan.html) — AgentLoop.runSupervisorInvestigation bound
58
+ * to this turn's read-only tools + risk hint. Passed straight through to
59
+ * HarnessRunOptions.runInvestigation. `undefined` (every caller until a supervisorDecider is
60
+ * also wired) → GATHER_EVIDENCE degrades to CONTINUE inside the harness, so this is inert by
61
+ * default exactly like onSupervisorDirective.
62
+ */
63
+ runInvestigation?: (req: InvestigationRequestData) => Promise<InvestigationFinding[]>;
64
+ /**
65
+ * Q2 of plans/ask_question_and_plan_mode_plan.html — the already-resolved effective askMode
66
+ * (Q1's three-tier INV-29 resolution), threaded straight into HarnessRunOptions.askMode so the
67
+ * harness's own supervisor ASK_USER path (S3) batches questions consistently with
68
+ * AskClarificationService's own gating. Also decides whether a thrown EscalationHalt carrying
69
+ * a populated `blocker.questions` gets to keep its checkpoint alive (see the `finally` block
70
+ * below) instead of being deleted as a terminal escalation. Defaults to false — byte-identical
71
+ * to pre-Q2 behavior when omitted.
72
+ */
73
+ askModeEnabled?: boolean;
74
+ /**
75
+ * Q2 — AskClarificationService.resolvePendingClarification's one-shot channel carrying the
76
+ * user's AskResponse back into the paused run's `callerState` via the harness's own
77
+ * checkCallerUpdates/applyConstraintChangePropagation path (nodes/check-caller-updates.ts).
78
+ * Passed straight through to HarnessRunOptions.updateChannel; undefined (every caller before
79
+ * Q2) defaults to a no-op channel inside the harness, unchanged.
80
+ */
81
+ updateChannel?: UpdateChannel;
82
+ }
83
+ /**
84
+ * HarnessRuntime construction + invocation: the runOptions assembly, checkpoint load +
85
+ * RESUME_ATTEMPT_CAP logic, runtime.run()/.resume() dispatch, and the paused-vs-completed
86
+ * outcome split — split out of runTurn in Phase 4d of the architecture remediation plan.
87
+ * `EscalationHalt` is deliberately NOT caught here — it propagates out of `run()` so the
88
+ * sequencer's single try/catch still handles it (assistant.ts's runTurn/ResponseService).
89
+ */
90
+ export declare class HarnessBridge {
91
+ private readonly memory;
92
+ private readonly experienceStore;
93
+ private readonly checkpointStore;
94
+ private readonly llmClient;
95
+ private readonly model;
96
+ private readonly maxSteps;
97
+ private readonly planService;
98
+ private readonly assistantSession;
99
+ private readonly onTrace;
100
+ private readonly oneLoopMode;
101
+ constructor(memory: MemoryAdapter, experienceStore: ExperienceStore, checkpointStore: CheckpointStore, llmClient: ILLMClient, model: () => string | undefined, maxSteps: number, planService: PlanService, assistantSession: AssistantSession, onTrace: ((event: TraceEvent) => void) | undefined, oneLoopMode?: OneLoopMode);
102
+ run(params: HarnessRunParams): Promise<HarnessOutcome>;
103
+ }
@@ -0,0 +1,58 @@
1
+ export { PersonalAssistant } from './assistant.js';
2
+ export type { AssistantTurnResult, ProposerKind, PersonalAssistantOptions, AssistantProgress, AssistantTrace, AssistantSource, MemorySummary, TranscriptSearchHit, DebugLogEntry } from './assistant.js';
3
+ export type { AnswerClaim, AnswerClaimSourceType, AnswerClaimVerificationStatus } from './answer-claim.js';
4
+ export { classifyRisk } from './risk-classifier.js';
5
+ export type { RiskClassification } from './risk-classifier.js';
6
+ export { classifyTurnIntent } from './turn-intent-classifier.js';
7
+ export type { TurnIntentClassification, TurnIntentContext, RiskLevel } from './turn-intent-classifier.js';
8
+ export { nodeDisplayName, nodeToLayer, buildWhyChain, LAYER_ORDER, LAYER_DISPLAY_NAME, LAYER_SHORT_CODE } from './node-display-names.js';
9
+ export type { LayerSlug, WhyChainItem } from './node-display-names.js';
10
+ export { classifyError } from './error-classifier.js';
11
+ export type { ErrorClassification } from './error-classifier.js';
12
+ export { FILE_TOOLS, READ_FILE_TOOL, LIST_DIRECTORY_TOOL, WRITE_FILE_TOOL, resolveInWorkspace, executeFileTool, stagePendingAction, loadPendingAction, applyPendingAction, discardPendingAction, PathOutsideWorkspaceError, } from './file-tools.js';
13
+ export type { FileToolsContext, FileToolResult, PendingActionRecord, PendingActionPayload, ApplyPendingActionResult, ShellExecutionResult, } from './file-tools.js';
14
+ export { SHELL_TOOLS, RUN_SHELL_COMMAND_TOOL, executeShellTool } from './shell-tools.js';
15
+ export type { ShellToolsContext, ShellStagingContext, ShellToolResult, ShellCommandExecutor } from './shell-tools.js';
16
+ export { ACTION_TOOLS, SEND_EMAIL_TOOL, executeActionTool, InvalidEmailArgsError } from './action-tools.js';
17
+ export type { ActionToolsContext, ActionStagingContext, ActionToolResult } from './action-tools.js';
18
+ export { createResendSender, formatEmailApprovalReason, isLikelyEmailAddress, EmailDeliveryError } from './email.js';
19
+ export type { EmailMessage, SendEmail, SendEmailResult, ResendSenderOptions } from './email.js';
20
+ export { createSmtpSender } from './email-smtp.js';
21
+ export type { SmtpSenderOptions } from './email-smtp.js';
22
+ export { WEB_TOOLS, WEB_SEARCH_TOOL, FETCH_URL_TOOL, executeWebTool, assertPublicHttpUrl, PrivateNetworkTargetError } from './web-tools.js';
23
+ export type { WebToolsContext, WebToolResult, WebSearchResult, DnsResolver } from './web-tools.js';
24
+ export { braveSearch } from './web-search-provider.js';
25
+ export type { BraveSearchOptions } from './web-search-provider.js';
26
+ export { buildClaudePrompt, parseClaudeCliOutput, ALREADY_STAGED_ACTION_TOOL, stagedActionInput } from './claude-cli-prompt.js';
27
+ export type { ParsedClaudeCliOutput } from './claude-cli-prompt.js';
28
+ export { stripMcpToolPrefix, summarizeToolStep } from './tool-step.js';
29
+ export type { AssistantToolStep } from './tool-step.js';
30
+ export type { DecomposedTaskSpec } from './decomposition-classifier.js';
31
+ export { detectHomogeneousBatchList } from './batch-list-detector.js';
32
+ export type { BatchListDetection } from './batch-list-detector.js';
33
+ export { classifyToolYield } from './tool-yield-classifier.js';
34
+ export type { ToolYield } from './tool-yield-classifier.js';
35
+ export { buildPlanFromTemplate } from './plan-builder.js';
36
+ export type { Plan } from './plan-builder.js';
37
+ export { loadTemplate, listTemplateNames, pickTemplateForTask, matchTemplateIfConfident } from './plan-templates/index.js';
38
+ export type { PlanTask, PlanTemplate } from './plan-templates/index.js';
39
+ export { loadActivePlan, createPlanRecord, savePlan, abandonPlan, updatePlanFromRun, planCompletionPct, computePlanPosition, nextPendingTask, formatPlanProgress, } from './plan-store.js';
40
+ export type { PlanRecord, PlanTaskRecord, PlanPosition, PlanMode } from './plan-store.js';
41
+ export type { PlanDecision, PlanApprovalEdits } from './plan-approval-service.js';
42
+ export { resolveConfig, validateConfig, ConfigValidationError, DEFAULT_CONFIG, CONFIG_KEYS } from './config.js';
43
+ export type { AssistantConfig, ConfigStore, ResolvedConfig } from './config.js';
44
+ export { resolveOneLoopMode, normalizeOneLoopMode, DEFAULT_ONE_LOOP_MODE } from './one-loop-flag.js';
45
+ export type { OneLoopMode } from './one-loop-flag.js';
46
+ export { resolveAskMode, normalizeAskMode, DEFAULT_ASK_MODE } from './ask-mode-flag.js';
47
+ export type { AskMode } from './ask-mode-flag.js';
48
+ export { resolvePlanMode, normalizePlanMode, DEFAULT_PLAN_MODE } from './plan-mode-flag.js';
49
+ export type { PlanRolloutMode } from './plan-mode-flag.js';
50
+ export { AskClarificationService } from './ask-clarification-service.js';
51
+ export type { AskClarificationPendingState } from './ask-clarification-service.js';
52
+ export { validateAskResponse, MAX_QUESTIONS_PER_BATCH, MIN_OPTIONS_PER_QUESTION, MAX_OPTIONS_PER_QUESTION } from '@buildaharness/harness';
53
+ export type { AskQuestion, AskQuestionOption, AskAnswer, AskResponse } from '@buildaharness/harness';
54
+ export { createScriptedLLMClient } from './scripted-llm-client.js';
55
+ export type { ScriptedLLMClientScript } from './scripted-llm-client.js';
56
+ export { formatMemorySummary, formatSearchResults, formatCostSummary, formatDoctorReport, formatTranscriptMarkdown, defaultExportFilename } from './cli-session.js';
57
+ export type { CostSummaryInfo, DoctorCheck } from './cli-session.js';
58
+ export { estimateCostUsd } from './model-pricing.js';
package/dist/index.js ADDED
@@ -0,0 +1,212 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { z as classifyRisk } from "./cli-session-D0uLdT2A.js";
5
+ import { A, a, b, C, c, D, e, f, g, h, F, i, I, L, j, k, l, P, m, n, R, o, S, p, W, q, r, s, t, u, v, w, x, y, B, G, H, J, K, M, N, Q, T, V, X, Y, Z, _, a0, a1, a2, a6, a7, a8, ab, ae, af, ag, ah, ai, aj, ak, al, am, an, ao, ap, aq, as, at, au, av, aw, ax, ay, az, aB, aC, aE, aF, aG, aH } from "./cli-session-D0uLdT2A.js";
6
+ import { MAX_OPTIONS_PER_QUESTION, MAX_QUESTIONS_PER_BATCH, MIN_OPTIONS_PER_QUESTION, validateAskResponse } from "@buildaharness/harness";
7
+ const PLAN_SCHEMA = {
8
+ type: "object",
9
+ properties: {
10
+ tasks: {
11
+ type: "array",
12
+ items: {
13
+ type: "object",
14
+ properties: {
15
+ id: { type: "string" },
16
+ description: { type: "string" },
17
+ depends_on: { type: "array", items: { type: "string" } }
18
+ },
19
+ required: ["id", "description", "depends_on"]
20
+ }
21
+ }
22
+ },
23
+ required: ["tasks"]
24
+ };
25
+ function buildSystemPrompt(template) {
26
+ const skeleton = template.tasks.map((t2) => `- id: ${t2.id}; title: ${t2.title}; depends_on: [${t2.depends_on.join(", ")}]`).join("\n");
27
+ return `You are adapting a "${template.name}" plan template to a specific user request. Here is the template's task skeleton — keep the exact same ids and depends_on structure, one output task per skeleton task, but personalize each description to the actual request:
28
+ ${skeleton}
29
+
30
+ Success criteria for this kind of plan: ${template.success_criteria}
31
+
32
+ Phrase each \`description\` starting with the concrete subject or object it acts on (e.g. "the login tests: rerun after the config fix" rather than "rerun the login tests after the config fix"), so later comparisons against this task's completion/failure beliefs share matching vocabulary. Respond with JSON only, no prose: {"tasks":[{"id": string, "description": string, "depends_on": string[]}]}. \`id\` and \`depends_on\` values must exactly match the skeleton above.`;
33
+ }
34
+ function isRawPlanTask(value) {
35
+ if (typeof value !== "object" || value === null) return false;
36
+ const v2 = value;
37
+ return typeof v2.id === "string" && typeof v2.description === "string" && Array.isArray(v2.depends_on) && v2.depends_on.every((d) => typeof d === "string");
38
+ }
39
+ async function buildPlanFromTemplate(llmClient, message, template, model, onUsage) {
40
+ try {
41
+ const response = await llmClient.callChatStructured(
42
+ [
43
+ { role: "system", content: buildSystemPrompt(template) },
44
+ { role: "user", content: message }
45
+ ],
46
+ void 0,
47
+ { model, onUsage, structuredOutput: { schema: PLAN_SCHEMA } }
48
+ );
49
+ const parsed = JSON.parse(response.content);
50
+ if (!Array.isArray(parsed.tasks)) return null;
51
+ const rawTasks = parsed.tasks.filter(isRawPlanTask);
52
+ if (rawTasks.length <= 1) return null;
53
+ const riskByTemplateId = new Map(template.tasks.map((t2) => [t2.id, t2.risk_level]));
54
+ const tasks = rawTasks.map((t2) => ({ ...t2, riskLevel: riskByTemplateId.get(t2.id) ?? "LOW" }));
55
+ return { templateName: template.name, successCriteria: template.success_criteria, tasks };
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+ const TURN_INTENT_MARKER = "eight independent judgments";
61
+ function isTurnIntentRequest(messages) {
62
+ return messages.some((m2) => m2.role === "system" && m2.content.includes(TURN_INTENT_MARKER));
63
+ }
64
+ function deriveTurnIntentJSON(messages, override) {
65
+ var _a;
66
+ const userContent = ((_a = messages.find((m2) => m2.role === "user")) == null ? void 0 : _a.content) ?? "";
67
+ const risk = classifyRisk(userContent);
68
+ const isReminderRequest = risk.reason.includes("reminder");
69
+ const base = {
70
+ riskLevel: risk.riskLevel,
71
+ riskReason: risk.reason,
72
+ isTrivial: false,
73
+ decomposedTasks: [],
74
+ isReminderRequest,
75
+ isBulkReminderRequest: isReminderRequest && risk.requiresApproval,
76
+ isAbandonRequest: false,
77
+ matchedPlanTemplate: null,
78
+ needsMultiStepPlan: false,
79
+ statesDurableFacts: []
80
+ };
81
+ return JSON.stringify({ ...base, ...override });
82
+ }
83
+ class ScriptedLLMClient {
84
+ constructor(script) {
85
+ /** Non-classifier `callChatStructured` calls made so far — the index into `responses`. */
86
+ __publicField(this, "toolCalls", 0);
87
+ __publicField(this, "responseIndex", 0);
88
+ this.script = script;
89
+ }
90
+ get streamChunks() {
91
+ return this.script.streamChunks && this.script.streamChunks.length > 0 ? this.script.streamChunks : [""];
92
+ }
93
+ async *callChat(_messages, _options) {
94
+ for (const chunk of this.streamChunks) yield chunk;
95
+ }
96
+ async callChatSync(messages, options) {
97
+ const chunks = [];
98
+ for await (const chunk of this.callChat(messages, options)) chunks.push(chunk);
99
+ return chunks.join("");
100
+ }
101
+ async callChatStructured(messages, _tools, _options) {
102
+ var _a, _b, _c;
103
+ if (isTurnIntentRequest(messages)) {
104
+ const userContent = ((_a = messages.find((m2) => m2.role === "user")) == null ? void 0 : _a.content) ?? "";
105
+ return { content: deriveTurnIntentJSON(messages, (_c = (_b = this.script).classify) == null ? void 0 : _c.call(_b, userContent)) };
106
+ }
107
+ this.toolCalls++;
108
+ const responses = this.script.responses ?? [];
109
+ if (this.responseIndex >= responses.length) {
110
+ throw new Error(
111
+ `createScriptedLLMClient: no scripted response for tool-loop call #${this.responseIndex + 1} (scripted ${responses.length})`
112
+ );
113
+ }
114
+ const next = responses[this.responseIndex++];
115
+ return typeof next === "string" ? { content: next } : next;
116
+ }
117
+ }
118
+ function createScriptedLLMClient(script = {}) {
119
+ return new ScriptedLLMClient(script);
120
+ }
121
+ export {
122
+ A as ACTION_TOOLS,
123
+ a as ALREADY_STAGED_ACTION_TOOL,
124
+ b as AskClarificationService,
125
+ C as CONFIG_KEYS,
126
+ c as ConfigValidationError,
127
+ D as DEFAULT_ASK_MODE,
128
+ e as DEFAULT_CONFIG,
129
+ f as DEFAULT_ONE_LOOP_MODE,
130
+ g as DEFAULT_PLAN_MODE,
131
+ h as EmailDeliveryError,
132
+ F as FETCH_URL_TOOL,
133
+ i as FILE_TOOLS,
134
+ I as InvalidEmailArgsError,
135
+ L as LAYER_DISPLAY_NAME,
136
+ j as LAYER_ORDER,
137
+ k as LAYER_SHORT_CODE,
138
+ l as LIST_DIRECTORY_TOOL,
139
+ MAX_OPTIONS_PER_QUESTION,
140
+ MAX_QUESTIONS_PER_BATCH,
141
+ MIN_OPTIONS_PER_QUESTION,
142
+ P as PathOutsideWorkspaceError,
143
+ m as PersonalAssistant,
144
+ n as PrivateNetworkTargetError,
145
+ R as READ_FILE_TOOL,
146
+ o as RUN_SHELL_COMMAND_TOOL,
147
+ S as SEND_EMAIL_TOOL,
148
+ p as SHELL_TOOLS,
149
+ W as WEB_SEARCH_TOOL,
150
+ q as WEB_TOOLS,
151
+ r as WRITE_FILE_TOOL,
152
+ s as abandonPlan,
153
+ t as applyPendingAction,
154
+ u as assertPublicHttpUrl,
155
+ v as braveSearch,
156
+ w as buildClaudePrompt,
157
+ buildPlanFromTemplate,
158
+ x as buildWhyChain,
159
+ y as classifyError,
160
+ classifyRisk,
161
+ B as classifyToolYield,
162
+ G as classifyTurnIntent,
163
+ H as computePlanPosition,
164
+ J as createPlanRecord,
165
+ K as createResendSender,
166
+ createScriptedLLMClient,
167
+ M as createSmtpSender,
168
+ N as defaultExportFilename,
169
+ Q as detectHomogeneousBatchList,
170
+ T as discardPendingAction,
171
+ V as estimateCostUsd,
172
+ X as executeActionTool,
173
+ Y as executeFileTool,
174
+ Z as executeShellTool,
175
+ _ as executeWebTool,
176
+ a0 as formatCostSummary,
177
+ a1 as formatDoctorReport,
178
+ a2 as formatEmailApprovalReason,
179
+ a6 as formatMemorySummary,
180
+ a7 as formatPlanProgress,
181
+ a8 as formatSearchResults,
182
+ ab as formatTranscriptMarkdown,
183
+ ae as isLikelyEmailAddress,
184
+ af as listTemplateNames,
185
+ ag as loadActivePlan,
186
+ ah as loadPendingAction,
187
+ ai as loadTemplate,
188
+ aj as matchTemplateIfConfident,
189
+ ak as nextPendingTask,
190
+ al as nodeDisplayName,
191
+ am as nodeToLayer,
192
+ an as normalizeAskMode,
193
+ ao as normalizeOneLoopMode,
194
+ ap as normalizePlanMode,
195
+ aq as parseClaudeCliOutput,
196
+ as as pickTemplateForTask,
197
+ at as planCompletionPct,
198
+ au as resolveAskMode,
199
+ av as resolveConfig,
200
+ aw as resolveInWorkspace,
201
+ ax as resolveOneLoopMode,
202
+ ay as resolvePlanMode,
203
+ az as savePlan,
204
+ aB as stagePendingAction,
205
+ aC as stagedActionInput,
206
+ aE as stripMcpToolPrefix,
207
+ aF as summarizeToolStep,
208
+ aG as updatePlanFromRun,
209
+ validateAskResponse,
210
+ aH as validateConfig
211
+ };
212
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/plan-builder.ts","../src/scripted-llm-client.ts"],"sourcesContent":["import type { ILLMClient, TokenUsage } from '@buildaharness/runtime'\nimport type { DecomposedTaskSpec } from './decomposition-classifier.js'\nimport type { PlanTemplate } from './plan-templates/index.js'\n\nexport interface Plan {\n templateName: string\n successCriteria: string\n tasks: DecomposedTaskSpec[]\n /**\n * Why this approach, not just what the success criteria are — optional for now since neither\n * buildPlanFromTemplate's template-personalization call nor any other current producer of a\n * Plan asks the LLM for one; plan-store.ts's createPlanRecord defaults it to '' when absent.\n * A future from-scratch drafting path (plan mode's P3) is expected to populate it.\n */\n rationale?: string\n}\n\nconst PLAN_SCHEMA = {\n type: 'object',\n properties: {\n tasks: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: { type: 'string' },\n description: { type: 'string' },\n depends_on: { type: 'array', items: { type: 'string' } },\n },\n required: ['id', 'description', 'depends_on'],\n },\n },\n },\n required: ['tasks'],\n}\n\nfunction buildSystemPrompt(template: PlanTemplate): string {\n const skeleton = template.tasks\n .map((t) => `- id: ${t.id}; title: ${t.title}; depends_on: [${t.depends_on.join(', ')}]`)\n .join('\\n')\n return (\n `You are adapting a \"${template.name}\" plan template to a specific user request. ` +\n `Here is the template's task skeleton — keep the exact same ids and depends_on structure, ` +\n `one output task per skeleton task, but personalize each description to the actual request:\\n${skeleton}\\n\\n` +\n `Success criteria for this kind of plan: ${template.success_criteria}\\n\\n` +\n 'Phrase each `description` starting with the concrete subject or object it acts on (e.g. \"the login tests: ' +\n 'rerun after the config fix\" rather than \"rerun the login tests after the config fix\"), so later comparisons ' +\n \"against this task's completion/failure beliefs share matching vocabulary. \" +\n 'Respond with JSON only, no prose: {\"tasks\":[{\"id\": string, \"description\": string, \"depends_on\": ' +\n 'string[]}]}. `id` and `depends_on` values must exactly match the skeleton above.'\n )\n}\n\n// Only the shape the LLM actually returns (id/description/depends_on) — riskLevel isn't asked for\n// here at all (see buildPlanFromTemplate's doc comment for why) and gets attached afterward from\n// the template's own already-curated risk_level, so this is deliberately narrower than\n// DecomposedTaskSpec itself.\ninterface RawPlanTask {\n id: string\n description: string\n depends_on: string[]\n}\n\nfunction isRawPlanTask(value: unknown): value is RawPlanTask {\n if (typeof value !== 'object' || value === null) return false\n const v = value as Record<string, unknown>\n return (\n typeof v.id === 'string' &&\n typeof v.description === 'string' &&\n Array.isArray(v.depends_on) &&\n v.depends_on.every((d) => typeof d === 'string')\n )\n}\n\n/**\n * Spends one real LLM call personalizing `template`'s task skeleton to `message` —\n * only called once classifyTurnIntent has already named a matchedPlanTemplate, so an\n * ordinary turn never pays for this. Same \"malformed/incomplete JSON is the expected\n * failure mode, not the edge case\" fallback classifyTurnIntent itself uses: any parse\n * failure or a response with fewer than 2 usable tasks returns null, meaning \"fall back to\n * the caller's own ad hoc decomposition for this turn\" rather than throwing.\n *\n * Each output task's `riskLevel` is attached from the matching skeleton task's own\n * `risk_level` (curated by whoever wrote the template's JSON) by id, not asked of the LLM —\n * the template already has this, more accurately than an LLM re-deriving it from a\n * personalized one-line description would, and it costs zero extra output tokens. Falls back\n * to 'LOW' only for the pathological case of a task id the LLM invented that doesn't match any\n * skeleton task (the id/depends_on-must-match-skeleton instruction above means this should not\n * happen in practice).\n */\nexport async function buildPlanFromTemplate(\n llmClient: ILLMClient,\n message: string,\n template: PlanTemplate,\n model?: string,\n onUsage?: (usage: TokenUsage) => void,\n): Promise<Plan | null> {\n try {\n const response = await llmClient.callChatStructured(\n [\n { role: 'system', content: buildSystemPrompt(template) },\n { role: 'user', content: message },\n ],\n undefined,\n { model, onUsage, structuredOutput: { schema: PLAN_SCHEMA } },\n )\n const parsed = JSON.parse(response.content) as { tasks?: unknown }\n if (!Array.isArray(parsed.tasks)) return null\n const rawTasks = parsed.tasks.filter(isRawPlanTask)\n if (rawTasks.length <= 1) return null\n const riskByTemplateId = new Map(template.tasks.map((t) => [t.id, t.risk_level]))\n const tasks: DecomposedTaskSpec[] = rawTasks.map((t) => ({ ...t, riskLevel: riskByTemplateId.get(t.id) ?? 'LOW' }))\n return { templateName: template.name, successCriteria: template.success_criteria, tasks }\n } catch {\n return null\n }\n}\n","import type {\n ChatMessage,\n ChatOptions,\n ILLMClient,\n LLMStructuredResponse,\n ToolDefinition,\n} from '@buildaharness/runtime'\nimport { classifyRisk } from './risk-classifier.js'\n\n/**\n * A deterministic, network-free {@link ILLMClient} for tests and demos — the one publicly\n * supported way to drive a real `PersonalAssistant` turn end to end without a proxy, an API key,\n * or a live model. Built for plans/chat_ui_browser_e2e_plan.html phase B1 (the browser-e2e lane\n * that unblocks the `ASSISTANT_ONE_LOOP` default-flip), and reusable by chat-ui's `demo-seed.ts`.\n *\n * It mirrors the shape of the internal `ScriptedToolLLMClient` that `assistant.test.ts` has used\n * for a long time: every turn spends exactly one mandatory `classifyTurnIntent` call up front,\n * which this client recognises (by a distinctive phrase from that classifier's system prompt) and\n * answers on its own from a lightweight, deterministic default — so `responses` only has to\n * script the *tool loop / one-loop proposer's* own calls, not that bookkeeping call.\n *\n * `responses` entries are consumed in order, one per non-classifier `callChatStructured` call:\n * - a plain `string` → `{ content: string }` (a final answer, no tool calls)\n * - an `LLMStructuredResponse` → use as-is (set `toolCalls` to simulate the model calling a tool)\n *\n * `callChat`/`callChatSync` (the no-tools reply path, and post-approval synthesis) stream\n * `streamChunks` (default: `['']`).\n */\nexport interface ScriptedLLMClientScript {\n /** Ordered structured responses for the tool loop / one-loop proposer. A bare string is shorthand for a final answer with no tool calls. */\n responses?: Array<string | LLMStructuredResponse>\n /** Chunks streamed from `callChat`/`callChatSync`. Defaults to `['']`. */\n streamChunks?: string[]\n /**\n * Per-user-message override of the auto-answered `classifyTurnIntent` JSON fields (e.g.\n * `{ isTrivial: true }`, `{ riskLevel: 'HIGH' }`, `{ decomposedTasks: [...] }`). Return\n * `undefined` to keep the derived default for that message.\n */\n classify?: (userMessage: string) => Record<string, unknown> | undefined\n}\n\n/** The phrase that opens `turn-intent-classifier.ts`'s system prompt — stable, and how every scripted client tells that mandatory call apart from a real tool-loop call. */\nconst TURN_INTENT_MARKER = 'eight independent judgments'\n\nfunction isTurnIntentRequest(messages: ChatMessage[]): boolean {\n return messages.some((m) => m.role === 'system' && m.content.includes(TURN_INTENT_MARKER))\n}\n\n/**\n * The deterministic stand-in for what a real model would return for `classifyTurnIntent` —\n * risk comes from the same lexical `classifyRisk` the real fallback path uses; everything else\n * is inert (not trivial, not a reminder, no decomposition, no plan template) unless `override`\n * says otherwise. `isTrivial` defaults to `false` so a scripted turn actually exercises the\n * harness loop (the interesting case for one-loop parity tests); pass `{ isTrivial: true }` via\n * `classify` for the triviality fast path.\n */\nfunction deriveTurnIntentJSON(messages: ChatMessage[], override?: Record<string, unknown>): string {\n const userContent = messages.find((m) => m.role === 'user')?.content ?? ''\n const risk = classifyRisk(userContent)\n const isReminderRequest = risk.reason.includes('reminder')\n const base = {\n riskLevel: risk.riskLevel,\n riskReason: risk.reason,\n isTrivial: false,\n decomposedTasks: [],\n isReminderRequest,\n isBulkReminderRequest: isReminderRequest && risk.requiresApproval,\n isAbandonRequest: false,\n matchedPlanTemplate: null,\n needsMultiStepPlan: false,\n statesDurableFacts: [],\n }\n return JSON.stringify({ ...base, ...override })\n}\n\nclass ScriptedLLMClient implements ILLMClient {\n /** Non-classifier `callChatStructured` calls made so far — the index into `responses`. */\n toolCalls = 0\n private responseIndex = 0\n\n constructor(private readonly script: ScriptedLLMClientScript) {}\n\n private get streamChunks(): string[] {\n return this.script.streamChunks && this.script.streamChunks.length > 0 ? this.script.streamChunks : ['']\n }\n\n async *callChat(_messages: ChatMessage[], _options?: ChatOptions): AsyncIterable<string> {\n for (const chunk of this.streamChunks) yield chunk\n }\n\n async callChatSync(messages: ChatMessage[], options?: ChatOptions): Promise<string> {\n const chunks: string[] = []\n for await (const chunk of this.callChat(messages, options)) chunks.push(chunk)\n return chunks.join('')\n }\n\n async callChatStructured(messages: ChatMessage[], _tools?: ToolDefinition[], _options?: ChatOptions): Promise<LLMStructuredResponse> {\n if (isTurnIntentRequest(messages)) {\n const userContent = messages.find((m) => m.role === 'user')?.content ?? ''\n return { content: deriveTurnIntentJSON(messages, this.script.classify?.(userContent)) }\n }\n this.toolCalls++\n const responses = this.script.responses ?? []\n if (this.responseIndex >= responses.length) {\n throw new Error(\n `createScriptedLLMClient: no scripted response for tool-loop call #${this.responseIndex + 1} (scripted ${responses.length})`,\n )\n }\n const next = responses[this.responseIndex++]\n return typeof next === 'string' ? { content: next } : next\n }\n}\n\n/**\n * Builds a scripted {@link ILLMClient}. See {@link ScriptedLLMClientScript}. The returned value\n * is a plain `ILLMClient` — pass it straight to `PersonalAssistant.create({ llmClient })`.\n */\nexport function createScriptedLLMClient(script: ScriptedLLMClientScript = {}): ILLMClient {\n return new ScriptedLLMClient(script)\n}\n"],"names":["t","v","m"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { ReactElement } from 'react';
3
+ declare class TestStdin extends EventEmitter {
4
+ isTTY: boolean;
5
+ private data;
6
+ write: (chunk: string) => void;
7
+ setEncoding(): void;
8
+ setRawMode(): void;
9
+ resume(): void;
10
+ pause(): void;
11
+ ref(): void;
12
+ unref(): void;
13
+ read: () => string | null;
14
+ }
15
+ export interface TestInkInstance {
16
+ stdin: TestStdin;
17
+ lastFrame: () => string | undefined;
18
+ /** See `TestStdout.fullOutput`'s doc comment — needed to assert on `<Static>` content. */
19
+ fullOutput: () => string;
20
+ rerender: (tree: ReactElement) => void;
21
+ unmount: () => void;
22
+ }
23
+ export declare function renderInk(tree: ReactElement): TestInkInstance;
24
+ export {};
@@ -0,0 +1,8 @@
1
+ {
2
+ "en": {
3
+ "connectorWords": [
4
+ "a", "an", "the", "of", "and", "or", "in", "on", "at", "to", "for", "with",
5
+ "am", "im", "an", "der", "die", "das", "von", "zu", "de", "la", "le", "van", "al"
6
+ ]
7
+ }
8
+ }
@@ -0,0 +1,11 @@
1
+ declare const _default: {
2
+ "en": {
3
+ "connectorWords": [
4
+ "a", "an", "the", "of", "and", "or", "in", "on", "at", "to", "for", "with",
5
+ "am", "im", "an", "der", "die", "das", "von", "zu", "de", "la", "le", "van", "al"
6
+ ]
7
+ }
8
+ }
9
+ ;
10
+
11
+ export default _default;