@buildaharness/aielia 0.3.3 → 0.3.5

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 (111) hide show
  1. package/README.md +63 -20
  2. package/dist/PlanGraphPane.d.ts +19 -0
  3. package/dist/_memory-core-generated.d.ts +1764 -0
  4. package/dist/agent-loop.d.ts +31 -8
  5. package/dist/ambiguity-guard-flag.d.ts +12 -0
  6. package/dist/ambiguity-guard.d.ts +20 -0
  7. package/dist/answer-claim.d.ts +13 -1
  8. package/dist/ask-clarification-service.d.ts +12 -1
  9. package/dist/ask-response-format.d.ts +6 -0
  10. package/dist/assistant-session.d.ts +16 -5
  11. package/dist/assistant-source.d.ts +7 -0
  12. package/dist/assistant-types.d.ts +25 -1
  13. package/dist/assistant.d.ts +166 -5
  14. package/dist/benchmark-injected-failure.d.ts +6 -0
  15. package/dist/bin-CjKpxDxl.js +4836 -0
  16. package/dist/bin-CjKpxDxl.js.map +1 -0
  17. package/dist/cli-session.d.ts +26 -1
  18. package/dist/cli.d.ts +20 -2
  19. package/dist/cli.js +1 -1
  20. package/dist/code-plan-templates.d.ts +21 -0
  21. package/dist/config.d.ts +51 -0
  22. package/dist/constraint-check.d.ts +35 -0
  23. package/dist/contradiction-checker.d.ts +7 -0
  24. package/dist/decomposed-answer.d.ts +13 -0
  25. package/dist/decomposition-classifier.d.ts +2 -0
  26. package/dist/doctor-checks.d.ts +3 -0
  27. package/dist/episodic-digest.d.ts +89 -0
  28. package/dist/fact-extraction.d.ts +39 -0
  29. package/dist/file-tools-mcp-server.mjs +138 -12
  30. package/dist/file-tools.d.ts +4 -0
  31. package/dist/first-run.d.ts +3 -2
  32. package/dist/goal-graph-flag.d.ts +44 -0
  33. package/dist/goal-graph-reconcile.d.ts +55 -0
  34. package/dist/goal-graph-service.d.ts +48 -0
  35. package/dist/goal-graph-store.d.ts +485 -0
  36. package/dist/goal-graph-suggest-flag.d.ts +34 -0
  37. package/dist/goal-identity-matcher.d.ts +21 -0
  38. package/dist/goal-thread-identity.d.ts +32 -0
  39. package/dist/goal-thread-scheduler.d.ts +78 -0
  40. package/dist/grounding-check.d.ts +33 -0
  41. package/dist/harness-bridge.d.ts +75 -2
  42. package/dist/harness-token-budget.d.ts +9 -0
  43. package/dist/index.d.ts +30 -1
  44. package/dist/index.js +166 -80
  45. package/dist/index.js.map +1 -1
  46. package/dist/layer-policy-flag.d.ts +13 -0
  47. package/dist/layer-policy-wiring.d.ts +77 -0
  48. package/dist/layer-settings.d.ts +49 -0
  49. package/dist/layer-telemetry.d.ts +2 -0
  50. package/dist/lexical/lexical-mode.d.ts +29 -0
  51. package/dist/live-steering-channel.d.ts +31 -0
  52. package/dist/memory-consolidation.d.ts +80 -0
  53. package/dist/memory-governance.d.ts +30 -0
  54. package/dist/memory-reviewer.d.ts +114 -0
  55. package/dist/memory-service.d.ts +348 -9
  56. package/dist/model-json.d.ts +17 -0
  57. package/dist/next-step-context.d.ts +51 -0
  58. package/dist/next-step-proposer.d.ts +53 -0
  59. package/dist/plan-approval-service.d.ts +1 -1
  60. package/dist/plan-drafting-service.d.ts +1 -1
  61. package/dist/plan-graph-flag.d.ts +20 -0
  62. package/dist/plan-question.d.ts +60 -0
  63. package/dist/plan-service.d.ts +6 -2
  64. package/dist/plan-step-prompt.d.ts +15 -0
  65. package/dist/plan-store.d.ts +7 -1
  66. package/dist/plan-viz/normalize.d.ts +14 -0
  67. package/dist/plan-viz/plan-detail-lines.d.ts +3 -0
  68. package/dist/plan-viz/plan-layout.d.ts +48 -0
  69. package/dist/plan-viz/plan-nav.d.ts +29 -0
  70. package/dist/plan-viz/plan-raster.d.ts +36 -0
  71. package/dist/plan-viz/plan-routes.d.ts +6 -0
  72. package/dist/plan-viz/plan-snapshot.d.ts +73 -0
  73. package/dist/plan-viz/plan-viewport.d.ts +29 -0
  74. package/dist/plan-viz/plan-width.d.ts +15 -0
  75. package/dist/plan-viz/testkit/display-width.d.ts +12 -0
  76. package/dist/plan-viz/testkit/generators.d.ts +61 -0
  77. package/dist/plan-viz/testkit/grid-oracles.d.ts +38 -0
  78. package/dist/plan-viz/testkit/nav-oracles.d.ts +79 -0
  79. package/dist/plan-viz/testkit/types.d.ts +8 -0
  80. package/dist/plan-viz/types.d.ts +34 -0
  81. package/dist/provider-setup-LFsaTkpf.js +16606 -0
  82. package/dist/provider-setup-LFsaTkpf.js.map +1 -0
  83. package/dist/provider-setup.d.ts +51 -0
  84. package/dist/recall-tool.d.ts +43 -0
  85. package/dist/recovery-note.d.ts +22 -0
  86. package/dist/response-service.d.ts +28 -2
  87. package/dist/review-checker.d.ts +10 -4
  88. package/dist/reviewer-revision.d.ts +23 -0
  89. package/dist/risk-classifier.d.ts +7 -0
  90. package/dist/scope-urgency-classifier.d.ts +29 -0
  91. package/dist/scripted-llm-client.d.ts +26 -1
  92. package/dist/semantic-compaction.d.ts +15 -0
  93. package/dist/semantic-hypotheses.d.ts +40 -0
  94. package/dist/source-reliability.d.ts +71 -0
  95. package/dist/supervisor-flag.d.ts +20 -0
  96. package/dist/task-completion-check.d.ts +24 -0
  97. package/dist/tool-control-plane.d.ts +33 -1
  98. package/dist/tool-effect-class.d.ts +9 -0
  99. package/dist/transcript-compaction.d.ts +9 -0
  100. package/dist/{tui-app-CFfOP2DC.js → tui-app-BLR-PRFp.js} +321 -7
  101. package/dist/tui-app-BLR-PRFp.js.map +1 -0
  102. package/dist/tui-app.d.ts +38 -0
  103. package/dist/turn-intent-classifier.d.ts +75 -16
  104. package/dist/turn-interpreter.d.ts +25 -3
  105. package/dist/version.d.ts +1 -1
  106. package/package.json +7 -7
  107. package/dist/bin-Btzhiyzi.js +0 -2108
  108. package/dist/bin-Btzhiyzi.js.map +0 -1
  109. package/dist/cli-session-DnSc6Hhx.js +0 -8343
  110. package/dist/cli-session-DnSc6Hhx.js.map +0 -1
  111. package/dist/tui-app-CFfOP2DC.js.map +0 -1
@@ -0,0 +1,33 @@
1
+ import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
2
+ import { AssistantSource } from './assistant-source.js';
3
+ /**
4
+ * - 'grounded': every specific claim in the reply that rests on the tool results matches them.
5
+ * - 'ungrounded': at least one specific claim or figure conflicts with, or isn't supported by,
6
+ * what the tools actually returned (`discrepancy` says which).
7
+ * - 'not_checked': nothing was compared — no raw tool text was visible (claude-cli runs its tool
8
+ * loop in a subprocess), or the check errored / returned something unparseable.
9
+ */
10
+ export type GroundingVerdict = 'grounded' | 'ungrounded' | 'not_checked';
11
+ export interface GroundingResult {
12
+ verdict: GroundingVerdict;
13
+ discrepancy?: string;
14
+ }
15
+ /**
16
+ * `AUDIT_SEMANTIC_GROUNDING` gate. Default **ON**: an unset / empty / truthy value runs the grounding
17
+ * check, so `answerClaim.verification_status` can only say `verified` for a reply that was compared
18
+ * against the raw tool results. A falsy value (`0` / `false` / `off` / `no` / `disabled`) skips the
19
+ * LLM call and restores the previous, mechanical-only derivation of `verified`. Same shape as
20
+ * `semanticFailureMatchEnabled()`; read at exactly one site, assistant.ts's ResponseService wiring.
21
+ */
22
+ export declare function semanticGroundingEnabled(env?: Record<string, string | undefined>): boolean;
23
+ /**
24
+ * One bounded LLM call comparing a finished reply to the raw text its tools returned — the content
25
+ * check `verify()` (mechanical/environmental tiers only) has no way to make. Returns `not_checked`
26
+ * when there is nothing to compare or on any error, never a guessed `grounded`: a check that
27
+ * couldn't run must not upgrade a reply to `verified`.
28
+ */
29
+ export declare function checkReplyGrounding(input: {
30
+ question: string;
31
+ reply: string;
32
+ sources: readonly AssistantSource[] | undefined;
33
+ }, llmClient: ILLMClient, model?: string, onUsage?: (usage: TokenUsage) => void): Promise<GroundingResult>;
@@ -1,4 +1,4 @@
1
- import { ExperienceStore, Task, CheckpointStore, LayerActivityEvent, HarnessCheckpoint, VerificationResult, HarnessRunResult, ToolExecutorContext, InvestigationRequestData, InvestigationFinding, UpdateChannel } from '@buildaharness/harness';
1
+ import { ExperienceStore, Task, CheckpointStore, LayerActivityEvent, HarnessCheckpoint, VerificationResult, HarnessRunResult, ToolExecutorContext, InvestigationRequestData, InvestigationFinding, UpdateChannel, SemanticHypothesisEvent, SemanticHypothesisProposal, LayerPolicyMode } from '@buildaharness/harness';
2
2
  import { ILLMClient, MemoryAdapter, TokenUsage } from '@buildaharness/runtime';
3
3
  import { OneLoopMode } from './one-loop-flag.js';
4
4
  import { UserFact } from './fact-extraction.js';
@@ -9,6 +9,7 @@ import { TurnIntentClassification } from './turn-intent-classifier.js';
9
9
  import { AssistantSource } from './assistant-source.js';
10
10
  import { AssistantProgress } from './assistant-types.js';
11
11
  import { TraceEvent } from './trace-events.js';
12
+ import { EscalationPlan } from './layer-policy-wiring.js';
12
13
  /**
13
14
  * `AUDIT_VERIFICATION` gate — feature-value audit (Phase C5 of the internal plan). EVAL-ONLY:
14
15
  * default **ON** (an unset / empty / truthy value keeps today's always-on verification). Only the
@@ -17,6 +18,29 @@ import { TraceEvent } from './trace-events.js';
17
18
  * one call site (below); no product path sets it. Same shape as `semanticContradictionEnabled()`.
18
19
  */
19
20
  export declare function verificationEnabled(env?: Record<string, string | undefined>): boolean;
21
+ /**
22
+ * `AUDIT_EXPERIENCE_LEARNING` gate. Default **OFF**: the harness never writes its journal or updates the experience store, so
23
+ * `warmStart` / the recovery ladder keep reading an empty store, exactly as before. A truthy value (`1` / `true` / `on` / `yes` /
24
+ * `enabled`) makes HarnessBridge.run pass `experienceLearning: true` — see packages/harness/src/experience-learning.ts. It changes
25
+ * how a LATER run orders its recovery strategies, so it ships off until a benchmark shows it helps. Read at one site (below).
26
+ */
27
+ export declare function experienceLearningEnabled(env?: Record<string, string | undefined>): boolean;
28
+ /**
29
+ * `AUDIT_RETRY_FAILED_TASK` gate. Default **OFF**. On (`1`/`true`/`on`/`yes`/`enabled`) makes HarnessBridge.run pass
30
+ * `retryFailedTask: true`: after the recovery ladder switches strategy for a failed task, the failed task is re-queued so the
31
+ * new strategy actually gets an attempt (HarnessRunOptions.retryFailedTask). Off, a failed single task — or a multi-task turn
32
+ * whose tasks fail together — ends with nothing to run and the turn answers "could not complete". Read fresh each run.
33
+ */
34
+ export declare function retryFailedTaskEnabled(env?: Record<string, string | undefined>): boolean;
35
+ /**
36
+ * `AUDIT_RETRY_SYSTEM_ERRORS` gate. Default **ON** (`0`/`false`/`off`/`no`/`disabled` restores the old behaviour). Makes
37
+ * HarnessBridge.run pass `retryFailedSystemErrors: true`: after the recovery ladder switches strategy for a task whose failure
38
+ * was a SYSTEM error (the model or a tool call threw), the task is re-queued so the new strategy gets an attempt, instead of
39
+ * the turn giving up with "I couldn't complete this" after one transient error. An exhausted tool-loop budget, a rejected
40
+ * completion check and a failed verification are NOT retried (the wider `AUDIT_RETRY_FAILED_TASK` retries those too and stays
41
+ * off). Bounded by the ladder's MAX_SWITCHES and the stall rule. Read fresh each run.
42
+ */
43
+ export declare function retrySystemErrorsEnabled(env?: Record<string, string | undefined>): boolean;
20
44
  /**
21
45
  * `AUDIT_REVIEWER_PASS` gate — feature-value audit (Phase C6 of the internal plan). EVAL-ONLY:
22
46
  * default **ON** (an unset / empty / truthy value keeps today's always-on 3-lens reviewer pass).
@@ -31,11 +55,13 @@ export type HarnessOutcome = {
31
55
  checkpoint: HarnessCheckpoint;
32
56
  lastVerification: VerificationResult | null;
33
57
  layerActivity: LayerActivityEvent[];
58
+ taskNotes: Record<string, string>;
34
59
  } | {
35
60
  status: 'completed';
36
61
  result: HarnessRunResult;
37
62
  lastVerification: VerificationResult | null;
38
63
  layerActivity: LayerActivityEvent[];
64
+ taskNotes: Record<string, string>;
39
65
  };
40
66
  export interface HarnessRunParams {
41
67
  sessionId: string;
@@ -69,6 +95,41 @@ export interface HarnessRunParams {
69
95
  * flag is on is R3's scope.
70
96
  */
71
97
  oneLoopProposer?: (toolCtx: ToolExecutorContext) => unknown | Promise<unknown>;
98
+ /** Semantic change reviewer found a conflict — advisory (see HarnessRunOptions.onReviewConflict). The caller decides how to surface it. */
99
+ onReviewConflict?: (event: {
100
+ taskId: string;
101
+ reason: string;
102
+ }) => void;
103
+ /** Competing explanations were generated for an underdetermined request, or one was eliminated by evidence (AUDIT_SEMANTIC_HYPOTHESES). The caller decides how to surface it. */
104
+ onSemanticHypothesis?: (event: SemanticHypothesisEvent) => void;
105
+ /** The competing explanations were already asked for (a tool-less turn drafts its reply first): the harness registers this answer instead of making a second call. `null` = asked, none. `undefined` = not asked. */
106
+ precomputedHypotheses?: SemanticHypothesisProposal[] | null;
107
+ /** The opt-in layers' plan for this turn (adaptive mode only); see resolveOptInPlan. Absent: each layer's own flag decides. */
108
+ optInPlan?: EscalationPlan;
109
+ /** The reviewer pass's verdict sent the last answer back for one revision (AUDIT_REVIEWER_REVISION): the note to put in front of the proposer. */
110
+ onReviewerRevision?: (event: {
111
+ taskId: string;
112
+ note: string;
113
+ }) => void;
114
+ /** A constraint the user stated this turn was violated by the finished answer: the note to hand the proposer for its one second answer. */
115
+ onConstraintRevision?: (event: {
116
+ taskId: string;
117
+ note: string;
118
+ }) => void;
119
+ /** Tokens this turn's model calls have used so far (input + output) — what AUDIT_HARNESS_TOKEN_BUDGET reports to the harness. */
120
+ tokensUsed?: () => number;
121
+ /** A confident failure-mode match picked the recovery strategy — advisory (see HarnessRunOptions.onFailureModeSwitch). The caller decides how to surface it. */
122
+ onFailureModeSwitch?: (event: {
123
+ taskId: string;
124
+ failure_class: string;
125
+ strategy: string;
126
+ }) => void;
127
+ /** With AUDIT_EXPERIENCE_LEARNING on, a ranking learned from earlier runs picked the recovery strategy — advisory (see HarnessRunOptions.onLearnedStrategySwitch). */
128
+ onLearnedStrategySwitch?: (event: {
129
+ taskId: string;
130
+ failure_class: string;
131
+ strategy: string;
132
+ }) => void;
72
133
  /**
73
134
  * Trajectory Supervisor GATHER_EVIDENCE host (S5 of
74
135
  * the internal plan) — AgentLoop.runSupervisorInvestigation bound
@@ -115,6 +176,18 @@ export declare class HarnessBridge {
115
176
  private readonly assistantSession;
116
177
  private readonly onTrace;
117
178
  private readonly oneLoopMode;
118
- 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);
179
+ private readonly layerPolicyMode;
180
+ 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, layerPolicyMode?: LayerPolicyMode);
181
+ /** AL9b: runId of the previous turn's outcome row, so this turn's `pushbackOnPriorTurn` can be recorded against it. */
182
+ private lastOutcomeRunId;
183
+ private outcomeSeq;
184
+ /** AL9b: persist the per-layer outcome row (and, under shadow, the shadow-vs-executed row). Never throws, never affects the turn. */
185
+ private recordTurnTelemetry;
186
+ /**
187
+ * Drops the paused harness run a `needs_clarification` left behind (its checkpoint and its resume-attempt counter),
188
+ * so the next ordinary run for this session starts fresh instead of resuming it. Used when a clarification answer
189
+ * is handed to the ordinary turn pipeline rather than fed back into the paused run.
190
+ */
191
+ discardPausedRun(sessionId: string): Promise<void>;
119
192
  run(params: HarnessRunParams): Promise<HarnessOutcome>;
120
193
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `AUDIT_HARNESS_TOKEN_BUDGET` gate. Default **OFF**: the harness's memory layer keeps its `token_budget` at 0 used of 200,000,
3
+ * so its context-compression pressure test (90%) can never trip, exactly as before. A positive integer turns it on and IS the
4
+ * budget (`AUDIT_HARNESS_TOKEN_BUDGET=30000`): the turn's real model usage (input + output tokens, every call the turn made) is
5
+ * reported to the harness each iteration, and compression runs once usage passes 90% of the total. What it trims is the harness's
6
+ * own bookkeeping (compressed-structure and pruned-region records), never the model's conversation context — that is
7
+ * `semantic-compaction.ts`. Read at one site: harness-bridge.ts.
8
+ */
9
+ export declare function harnessTokenBudgetTotal(env?: Record<string, string | undefined>): number | undefined;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,16 @@
1
1
  export { PersonalAssistant } from './assistant.js';
2
2
  export type { AssistantTurnResult, ProposerKind, PersonalAssistantOptions, AssistantProgress, AssistantTrace, AssistantSource, MemorySummary, TranscriptSearchHit, DebugLogEntry } from './assistant.js';
3
+ export type { MemoryStatus, MemoryInjection, AuditEntry, ConsolidationOutcome } from './memory-service.js';
4
+ export type { ConsolidationProposal } from './memory-consolidation.js';
5
+ export type { SessionDigest } from './episodic-digest.js';
6
+ export type { MemoryWriteMode } from './memory-governance.js';
7
+ export { MEMORY_WRITE_MODES, DEFAULT_MEMORY_WRITE_MODE, resolveMemoryWriteMode } from './memory-governance.js';
8
+ export type { GoalGraphState, GoalThreadView, GoalThreadVisibility, GoalTaskSummary } from './goal-graph-service.js';
3
9
  export type { AnswerClaim, AnswerClaimSourceType, AnswerClaimVerificationStatus } from './answer-claim.js';
10
+ export { lowerConfidenceSourceLines } from './source-reliability.js';
4
11
  export { classifyRisk } from './risk-classifier.js';
12
+ export { LEXICAL_FAMILIES, LEXICAL_CHECK_FAMILIES, DEFAULT_LEXICAL_MODE, lexicalActive, resolveLexicalMode, resolveLexicalOff, lexicalOffEnvValue, syncHarnessLexicalEnv } from './lexical/lexical-mode.js';
13
+ export type { LexicalFamily, LexicalMode } from './lexical/lexical-mode.js';
5
14
  export type { RiskClassification } from './risk-classifier.js';
6
15
  export { classifyTurnIntent } from './turn-intent-classifier.js';
7
16
  export type { TurnIntentClassification, TurnIntentContext, RiskLevel } from './turn-intent-classifier.js';
@@ -41,18 +50,38 @@ export type { PlanRecord, PlanTaskRecord, PlanPosition, PlanMode } from './plan-
41
50
  export type { PlanDecision, PlanApprovalEdits } from './plan-approval-service.js';
42
51
  export { resolveConfig, validateConfig, ConfigValidationError, DEFAULT_CONFIG, CONFIG_KEYS } from './config.js';
43
52
  export type { AssistantConfig, ConfigStore, ResolvedConfig } from './config.js';
53
+ export { LAYER_SETTINGS, applyLayerSettings, effectiveState, withLayerChoice, sanitizeLayerChoices, formatLayerListing, findLayer, isToggleable, LayerSettingError } from './layer-settings.js';
54
+ export type { LayerSetting, LayerGroup, LayerChoices, AppliedLayers } from './layer-settings.js';
44
55
  export { resolveOneLoopMode, normalizeOneLoopMode, DEFAULT_ONE_LOOP_MODE } from './one-loop-flag.js';
45
56
  export type { OneLoopMode } from './one-loop-flag.js';
46
57
  export { resolveAskMode, normalizeAskMode, DEFAULT_ASK_MODE } from './ask-mode-flag.js';
47
58
  export type { AskMode } from './ask-mode-flag.js';
59
+ export { resolveAmbiguityGuardMode, normalizeAmbiguityGuardMode, DEFAULT_AMBIGUITY_GUARD_MODE } from './ambiguity-guard-flag.js';
60
+ export type { AmbiguityGuardMode } from './ambiguity-guard-flag.js';
48
61
  export { resolvePlanMode, normalizePlanMode, DEFAULT_PLAN_MODE } from './plan-mode-flag.js';
49
62
  export type { PlanRolloutMode } from './plan-mode-flag.js';
63
+ export { resolveGoalGraphMode, normalizeGoalGraphMode, isGoalGraphEnabled, DEFAULT_GOAL_GRAPH_MODE } from './goal-graph-flag.js';
64
+ export type { GoalGraphMode } from './goal-graph-flag.js';
65
+ export { resolvePlanGraphMode, normalizePlanGraphMode, isPlanGraphEnabled, DEFAULT_PLAN_GRAPH_MODE } from './plan-graph-flag.js';
66
+ export type { PlanGraphMode } from './plan-graph-flag.js';
67
+ export { planToSnapshot } from './plan-viz/plan-snapshot.js';
68
+ export type { PlanSnapshot, PlanSnapshotResult } from './plan-viz/plan-snapshot.js';
69
+ export type { NextStepSuggestion } from './next-step-proposer.js';
70
+ export { resolveGoalGraphSuggestMode, normalizeGoalGraphSuggestMode, isGoalGraphSuggestEnabled, DEFAULT_GOAL_GRAPH_SUGGEST_MODE } from './goal-graph-suggest-flag.js';
71
+ export type { GoalGraphSuggestMode } from './goal-graph-suggest-flag.js';
72
+ export { resolveLayerPolicyMode, normalizeLayerPolicyMode, isAdaptivePolicyEnabled, isPolicyRecordingEnabled, isLayerPolicyMode, resolveLayerPolicyModeFromConfig, DEFAULT_LAYER_POLICY_MODE } from './layer-policy-flag.js';
73
+ export type { LayerPolicyMode } from './layer-policy-flag.js';
74
+ export { resolveEscalationPlan, turnPolicyBudget, escalationEnabled, explicitEnvOverride, SEMANTIC_ESCALATIONS, harnessGatePolicy, decompositionReframeEnabled, injectionDetectionEnabled, decisionNote } from './layer-policy-wiring.js';
75
+ export { LiveSteeringChannel } from './live-steering-channel.js';
76
+ export type { SteeringEvent } from './live-steering-channel.js';
50
77
  export { AskClarificationService } from './ask-clarification-service.js';
51
78
  export type { AskClarificationPendingState } from './ask-clarification-service.js';
52
79
  export { validateAskResponse, MAX_QUESTIONS_PER_BATCH, MIN_OPTIONS_PER_QUESTION, MAX_OPTIONS_PER_QUESTION } from '@buildaharness/harness';
53
80
  export type { AskQuestion, AskQuestionOption, AskAnswer, AskResponse } from '@buildaharness/harness';
54
81
  export { createScriptedLLMClient } from './scripted-llm-client.js';
55
82
  export type { ScriptedLLMClientScript } from './scripted-llm-client.js';
56
- export { formatMemorySummary, formatSearchResults, formatCostSummary, formatDoctorReport, formatTranscriptMarkdown, defaultExportFilename } from './cli-session.js';
83
+ export { formatMemorySummary, formatMemoryHistory, formatMemoryArchive, formatMemoryInjection, formatMemoryStatus, memoryStatusChecks, formatSearchResults, formatGoalGraphState, formatCostSummary, formatDoctorReport, formatTranscriptMarkdown, defaultExportFilename } from './cli-session.js';
57
84
  export type { CostSummaryInfo, DoctorCheck } from './cli-session.js';
58
85
  export { estimateCostUsd } from './model-pricing.js';
86
+ export { PROVIDER_SETUP, getProviderSetup, cleanApiKey, checkApiKeyFormat, testApiKey } from './provider-setup.js';
87
+ export type { KeyedBackend, ProviderSetupInfo, KeyTestResult } from './provider-setup.js';
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { B as classifyRisk } from "./cli-session-DnSc6Hhx.js";
5
- import { A, a, b, C, c, D, e, f, g, i, F, j, I, L, k, l, m, P, n, o, R, p, S, q, W, r, s, t, u, v, w, x, y, z, G, H, J, K, M, N, O, T, U, X, Y, Z, _, $, a1, a2, a3, a7, a8, a9, ac, af, ag, ah, ai, aj, ak, al, am, an, ao, ap, aq, ar, at, au, av, aw, ax, ay, az, aA, aC, aD, aF, aG, aH, aI } from "./cli-session-DnSc6Hhx.js";
6
- import { MAX_OPTIONS_PER_QUESTION, MAX_QUESTIONS_PER_BATCH, MIN_OPTIONS_PER_QUESTION, validateAskResponse } from "@buildaharness/harness";
4
+ import { bi as parseModelJson, a0 as classifyRiskLexical } from "./provider-setup-LFsaTkpf.js";
5
+ import { A, a, b, C, c, D, e, f, g, h, i, j, k, l, m, o, F, p, I, L, q, r, s, t, u, v, w, x, M, P, y, z, B, R, G, S, H, J, W, K, N, O, Q, T, U, V, X, Y, Z, _, $, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ac, ad, ae, ag, ah, ai, aj, ak, al, am, an, ap, aq, ar, as, au, av, ax, ay, aA, aB, aD, aE, aH, aJ, aK, aL, aM, aO, aP, aQ, aR, aS, aT, aV, aW, aX, aY, aZ, a_, a$, b0, b1, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bg, bj, bk, bl, bn, bo, bp, bq, br, bs, bt, bu, bv, bw, bx, by, bz, bA, bB, bC, bD, bF, bG, bI, bJ, bK, bL, bM, bN, bO, bP } from "./provider-setup-LFsaTkpf.js";
6
+ import { DEFAULT_LAYER_POLICY_MODE, MAX_OPTIONS_PER_QUESTION, MAX_QUESTIONS_PER_BATCH, MIN_OPTIONS_PER_QUESTION, validateAskResponse } from "@buildaharness/harness";
7
7
  const PLAN_SCHEMA = {
8
8
  type: "object",
9
9
  properties: {
@@ -46,7 +46,7 @@ async function buildPlanFromTemplate(llmClient, message, template, model, onUsag
46
46
  void 0,
47
47
  { model, onUsage, structuredOutput: { schema: PLAN_SCHEMA } }
48
48
  );
49
- const parsed = JSON.parse(response.content);
49
+ const parsed = parseModelJson(response.content);
50
50
  if (!Array.isArray(parsed.tasks)) return null;
51
51
  const rawTasks = parsed.tasks.filter(isRawPlanTask);
52
52
  if (rawTasks.length <= 1) return null;
@@ -57,14 +57,30 @@ async function buildPlanFromTemplate(llmClient, message, template, model, onUsag
57
57
  return null;
58
58
  }
59
59
  }
60
- const TURN_INTENT_MARKER = "eight independent judgments";
60
+ const SIDE_CALL_MARKERS = [
61
+ ["You check whether a proposed action genuinely conflicts", '{"conflict":false}'],
62
+ ["You match a set of observed symptoms against a curated library", "null"],
63
+ ["You are a security classifier analyzing untrusted external content", '{"flagged":false}'],
64
+ ["You check whether an assistant's reply is faithful to the raw results", '{"verdict":"grounded"}'],
65
+ ["You judge whether an assistant actually accomplished a task", '{"done":true}'],
66
+ ["You condense the earlier part of a conversation", '{"summary":""}'],
67
+ ["You judge whether an assistant's reply violates constraints", '{"violations":[]}'],
68
+ ["You weigh the reliability of the sources an assistant read", '{"assessments":[],"weighed":true}'],
69
+ ["You propose competing explanations for an underdetermined request", '{"hypotheses":[]}'],
70
+ ["You decide which explanations a set of new observations rules out", '{"contradicted":[]}'],
71
+ ["You write a session handoff digest", '{"digest":null}'],
72
+ ["You consolidate a small store of remembered facts about a user", '{"proposals":[]}'],
73
+ ["You review what a user said across several turns", '{"ops":[]}'],
74
+ ["You check proposed memory entries against what a user actually said", '{"verdicts":[]}']
75
+ ];
76
+ const TURN_INTENT_MARKER = " independent judgments";
61
77
  function isTurnIntentRequest(messages) {
62
78
  return messages.some((m2) => m2.role === "system" && m2.content.includes(TURN_INTENT_MARKER));
63
79
  }
64
80
  function deriveTurnIntentJSON(messages, override) {
65
81
  var _a;
66
82
  const userContent = ((_a = messages.find((m2) => m2.role === "user")) == null ? void 0 : _a.content) ?? "";
67
- const risk = classifyRisk(userContent);
83
+ const risk = classifyRiskLexical(userContent);
68
84
  const isReminderRequest = risk.reason.includes("reminder");
69
85
  const base = {
70
86
  riskLevel: risk.riskLevel,
@@ -74,6 +90,8 @@ function deriveTurnIntentJSON(messages, override) {
74
90
  isReminderRequest,
75
91
  isBulkReminderRequest: isReminderRequest && risk.requiresApproval,
76
92
  isAbandonRequest: false,
93
+ isPlanQuestion: false,
94
+ isUnderdetermined: false,
77
95
  matchedPlanTemplate: null,
78
96
  needsMultiStepPlan: false,
79
97
  statesDurableFacts: []
@@ -99,11 +117,14 @@ class ScriptedLLMClient {
99
117
  return chunks.join("");
100
118
  }
101
119
  async callChatStructured(messages, _tools, _options) {
102
- var _a, _b, _c;
120
+ var _a, _b, _c, _d;
103
121
  if (isTurnIntentRequest(messages)) {
104
122
  const userContent = ((_a = messages.find((m2) => m2.role === "user")) == null ? void 0 : _a.content) ?? "";
105
123
  return { content: deriveTurnIntentJSON(messages, (_c = (_b = this.script).classify) == null ? void 0 : _c.call(_b, userContent)) };
106
124
  }
125
+ const system = ((_d = messages.find((m2) => m2.role === "system")) == null ? void 0 : _d.content) ?? "";
126
+ const side = [...this.script.sideResponses ?? [], ...SIDE_CALL_MARKERS].find(([marker]) => system.includes(marker));
127
+ if (side) return { content: side[1] };
107
128
  this.toolCalls++;
108
129
  const responses = this.script.responses ?? [];
109
130
  if (this.responseIndex >= responses.length) {
@@ -124,89 +145,154 @@ export {
124
145
  b as AskClarificationService,
125
146
  C as CONFIG_KEYS,
126
147
  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
- i as EmailDeliveryError,
148
+ D as DEFAULT_AMBIGUITY_GUARD_MODE,
149
+ e as DEFAULT_ASK_MODE,
150
+ f as DEFAULT_CONFIG,
151
+ g as DEFAULT_GOAL_GRAPH_MODE,
152
+ h as DEFAULT_GOAL_GRAPH_SUGGEST_MODE,
153
+ DEFAULT_LAYER_POLICY_MODE,
154
+ i as DEFAULT_LEXICAL_MODE,
155
+ j as DEFAULT_MEMORY_WRITE_MODE,
156
+ k as DEFAULT_ONE_LOOP_MODE,
157
+ l as DEFAULT_PLAN_GRAPH_MODE,
158
+ m as DEFAULT_PLAN_MODE,
159
+ o as EmailDeliveryError,
132
160
  F as FETCH_URL_TOOL,
133
- j as FILE_TOOLS,
161
+ p as FILE_TOOLS,
134
162
  I as InvalidEmailArgsError,
135
163
  L as LAYER_DISPLAY_NAME,
136
- k as LAYER_ORDER,
137
- l as LAYER_SHORT_CODE,
138
- m as LIST_DIRECTORY_TOOL,
164
+ q as LAYER_ORDER,
165
+ r as LAYER_SETTINGS,
166
+ s as LAYER_SHORT_CODE,
167
+ t as LEXICAL_CHECK_FAMILIES,
168
+ u as LEXICAL_FAMILIES,
169
+ v as LIST_DIRECTORY_TOOL,
170
+ w as LayerSettingError,
171
+ x as LiveSteeringChannel,
139
172
  MAX_OPTIONS_PER_QUESTION,
140
173
  MAX_QUESTIONS_PER_BATCH,
174
+ M as MEMORY_WRITE_MODES,
141
175
  MIN_OPTIONS_PER_QUESTION,
142
- P as PathOutsideWorkspaceError,
143
- n as PersonalAssistant,
144
- o as PrivateNetworkTargetError,
176
+ P as PROVIDER_SETUP,
177
+ y as PathOutsideWorkspaceError,
178
+ z as PersonalAssistant,
179
+ B as PrivateNetworkTargetError,
145
180
  R as READ_FILE_TOOL,
146
- p as RUN_SHELL_COMMAND_TOOL,
147
- S as SEND_EMAIL_TOOL,
148
- q as SHELL_TOOLS,
181
+ G as RUN_SHELL_COMMAND_TOOL,
182
+ S as SEMANTIC_ESCALATIONS,
183
+ H as SEND_EMAIL_TOOL,
184
+ J as SHELL_TOOLS,
149
185
  W as WEB_SEARCH_TOOL,
150
- r as WEB_TOOLS,
151
- s as WRITE_FILE_TOOL,
152
- t as abandonPlan,
153
- u as applyPendingAction,
154
- v as assertPublicHttpUrl,
155
- w as braveSearch,
156
- x as buildClaudePrompt,
186
+ K as WEB_TOOLS,
187
+ N as WRITE_FILE_TOOL,
188
+ O as abandonPlan,
189
+ Q as applyLayerSettings,
190
+ T as applyPendingAction,
191
+ U as assertPublicHttpUrl,
192
+ V as braveSearch,
193
+ X as buildClaudePrompt,
157
194
  buildPlanFromTemplate,
158
- y as buildWhyChain,
159
- z as classifyError,
160
- classifyRisk,
161
- G as classifyToolYield,
162
- H as classifyTurnIntent,
163
- J as computePlanPosition,
164
- K as createPlanRecord,
165
- M as createResendSender,
195
+ Y as buildWhyChain,
196
+ Z as checkApiKeyFormat,
197
+ _ as classifyError,
198
+ $ as classifyRisk,
199
+ a1 as classifyToolYield,
200
+ a2 as classifyTurnIntent,
201
+ a3 as cleanApiKey,
202
+ a4 as computePlanPosition,
203
+ a5 as createPlanRecord,
204
+ a6 as createResendSender,
166
205
  createScriptedLLMClient,
167
- N as createSmtpSender,
168
- O as defaultExportFilename,
169
- T as detectHomogeneousBatchList,
170
- U as discardPendingAction,
171
- X as estimateCostUsd,
172
- Y as executeActionTool,
173
- Z as executeFileTool,
174
- _ as executeShellTool,
175
- $ as executeWebTool,
176
- a1 as formatCostSummary,
177
- a2 as formatDoctorReport,
178
- a3 as formatEmailApprovalReason,
179
- a7 as formatMemorySummary,
180
- a8 as formatPlanProgress,
181
- a9 as formatSearchResults,
182
- ac as formatTranscriptMarkdown,
183
- af as isLikelyEmailAddress,
184
- ag as listTemplateNames,
185
- ah as loadActivePlan,
186
- ai as loadPendingAction,
187
- aj as loadTemplate,
188
- ak as matchTemplateIfConfident,
189
- al as nextPendingTask,
190
- am as nodeDisplayName,
191
- an as nodeToLayer,
192
- ao as normalizeAskMode,
193
- ap as normalizeOneLoopMode,
194
- aq as normalizePlanMode,
195
- ar as parseClaudeCliOutput,
196
- at as pickTemplateForTask,
197
- au as planCompletionPct,
198
- av as resolveAskMode,
199
- aw as resolveConfig,
200
- ax as resolveInWorkspace,
201
- ay as resolveOneLoopMode,
202
- az as resolvePlanMode,
203
- aA as savePlan,
204
- aC as stagePendingAction,
205
- aD as stagedActionInput,
206
- aF as stripMcpToolPrefix,
207
- aG as summarizeToolStep,
208
- aH as updatePlanFromRun,
206
+ a7 as createSmtpSender,
207
+ a8 as decisionNote,
208
+ a9 as decompositionReframeEnabled,
209
+ aa as defaultExportFilename,
210
+ ac as detectHomogeneousBatchList,
211
+ ad as discardPendingAction,
212
+ ae as effectiveState,
213
+ ag as escalationEnabled,
214
+ ah as estimateCostUsd,
215
+ ai as executeActionTool,
216
+ aj as executeFileTool,
217
+ ak as executeShellTool,
218
+ al as executeWebTool,
219
+ am as explicitEnvOverride,
220
+ an as findLayer,
221
+ ap as formatCostSummary,
222
+ aq as formatDoctorReport,
223
+ ar as formatEmailApprovalReason,
224
+ as as formatGoalGraphState,
225
+ au as formatLayerListing,
226
+ av as formatMemoryArchive,
227
+ ax as formatMemoryHistory,
228
+ ay as formatMemoryInjection,
229
+ aA as formatMemoryStatus,
230
+ aB as formatMemorySummary,
231
+ aD as formatPlanProgress,
232
+ aE as formatSearchResults,
233
+ aH as formatTranscriptMarkdown,
234
+ aJ as getProviderSetup,
235
+ aK as harnessGatePolicy,
236
+ aL as injectionDetectionEnabled,
237
+ aM as isAdaptivePolicyEnabled,
238
+ aO as isGoalGraphEnabled,
239
+ aP as isGoalGraphSuggestEnabled,
240
+ aQ as isLayerPolicyMode,
241
+ aR as isLikelyEmailAddress,
242
+ aS as isPlanGraphEnabled,
243
+ aT as isPolicyRecordingEnabled,
244
+ aV as isToggleable,
245
+ aW as lexicalActive,
246
+ aX as lexicalOffEnvValue,
247
+ aY as listTemplateNames,
248
+ aZ as loadActivePlan,
249
+ a_ as loadPendingAction,
250
+ a$ as loadTemplate,
251
+ b0 as lowerConfidenceSourceLines,
252
+ b1 as matchTemplateIfConfident,
253
+ b3 as memoryStatusChecks,
254
+ b4 as nextPendingTask,
255
+ b5 as nodeDisplayName,
256
+ b6 as nodeToLayer,
257
+ b7 as normalizeAmbiguityGuardMode,
258
+ b8 as normalizeAskMode,
259
+ b9 as normalizeGoalGraphMode,
260
+ ba as normalizeGoalGraphSuggestMode,
261
+ bb as normalizeLayerPolicyMode,
262
+ bc as normalizeOneLoopMode,
263
+ bd as normalizePlanGraphMode,
264
+ be as normalizePlanMode,
265
+ bg as parseClaudeCliOutput,
266
+ bj as pickTemplateForTask,
267
+ bk as planCompletionPct,
268
+ bl as planToSnapshot,
269
+ bn as resolveAmbiguityGuardMode,
270
+ bo as resolveAskMode,
271
+ bp as resolveConfig,
272
+ bq as resolveEscalationPlan,
273
+ br as resolveGoalGraphMode,
274
+ bs as resolveGoalGraphSuggestMode,
275
+ bt as resolveInWorkspace,
276
+ bu as resolveLayerPolicyMode,
277
+ bv as resolveLayerPolicyModeFromConfig,
278
+ bw as resolveLexicalMode,
279
+ bx as resolveLexicalOff,
280
+ by as resolveMemoryWriteMode,
281
+ bz as resolveOneLoopMode,
282
+ bA as resolvePlanGraphMode,
283
+ bB as resolvePlanMode,
284
+ bC as sanitizeLayerChoices,
285
+ bD as savePlan,
286
+ bF as stagePendingAction,
287
+ bG as stagedActionInput,
288
+ bI as stripMcpToolPrefix,
289
+ bJ as summarizeToolStep,
290
+ bK as syncHarnessLexicalEnv,
291
+ bL as testApiKey,
292
+ bM as turnPolicyBudget,
293
+ bN as updatePlanFromRun,
209
294
  validateAskResponse,
210
- aI as validateConfig
295
+ bO as validateConfig,
296
+ bP as withLayerChoice
211
297
  };
212
298
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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 the internal plan 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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"}
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'\nimport { parseModelJson } from './model-json.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 = parseModelJson(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 { classifyRiskLexical } 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 the internal plan 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, non-side-call\n * `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 * A handful of other calls are auxiliary semantic escalations (the change reviewer, the failure\n * matcher, the LLM injection classifier) that a tool-using turn can trigger without any script\n * author asking for them — with lexicalMode's default now `disabled`, the lexical checks these\n * sit on top of never short-circuit them, so they fire on every qualifying call, not just ones a\n * scenario is specifically testing. Answered from a safe, inert default (see SIDE_CALL_MARKERS)\n * so they never silently consume a `responses` slot meant for the real tool loop — the exact\n * silent-consumption bug that broke chat-ui's W5 proxy e2e test when the default flipped. Override\n * or add to this list via `sideResponses` for a test that specifically wants one of these calls to\n * answer something else (e.g. a genuine injection flag).\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 * Overrides/extends SIDE_CALL_MARKERS' default answers for a system-prompt substring — checked\n * before the built-in defaults, so a test can make the change reviewer/failure matcher/injection\n * classifier answer something other than \"nothing to see here\" without it consuming a\n * `responses` slot either way. `[marker, rawJsonContent]` pairs, same shape as the built-ins.\n */\n sideResponses?: Array<[string, string]>\n}\n\n/**\n * Known auxiliary semantic-escalation system prompts and their safe, inert default answer — see\n * ScriptedLLMClientScript's doc comment. Kept in sync by hand with each check's own system prompt\n * (review-checker.ts, failure-mode-matcher.ts, trust-tagging.ts, grounding-check.ts, task-completion-check.ts, constraint-check.ts, semantic-compaction.ts, source-reliability.ts, semantic-hypotheses.ts, episodic-digest.ts, memory-consolidation.ts, memory-reviewer.ts) since there's no shared constant\n * to import without a circular dependency back into this package's own public surface.\n */\nexport const SIDE_CALL_MARKERS: Array<[string, string]> = [\n ['You check whether a proposed action genuinely conflicts', '{\"conflict\":false}'],\n ['You match a set of observed symptoms against a curated library', 'null'],\n ['You are a security classifier analyzing untrusted external content', '{\"flagged\":false}'],\n ['You check whether an assistant\\'s reply is faithful to the raw results', '{\"verdict\":\"grounded\"}'],\n ['You judge whether an assistant actually accomplished a task', '{\"done\":true}'],\n ['You condense the earlier part of a conversation', '{\"summary\":\"\"}'],\n ['You judge whether an assistant\\'s reply violates constraints', '{\"violations\":[]}'],\n ['You weigh the reliability of the sources an assistant read', '{\"assessments\":[],\"weighed\":true}'],\n ['You propose competing explanations for an underdetermined request', '{\"hypotheses\":[]}'],\n ['You decide which explanations a set of new observations rules out', '{\"contradicted\":[]}'],\n ['You write a session handoff digest', '{\"digest\":null}'],\n ['You consolidate a small store of remembered facts about a user', '{\"proposals\":[]}'],\n ['You review what a user said across several turns', '{\"ops\":[]}'],\n ['You check proposed memory entries against what a user actually said', '{\"verdicts\":[]}'],\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 = ' 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 = classifyRiskLexical(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 isPlanQuestion: false,\n isUnderdetermined: 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 const system = messages.find((m) => m.role === 'system')?.content ?? ''\n const side = [...(this.script.sideResponses ?? []), ...SIDE_CALL_MARKERS].find(([marker]) => system.includes(marker))\n if (side) return { content: side[1] }\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,13 @@
1
+ import { DEFAULT_LAYER_POLICY_MODE, LayerPolicyMode } from '@buildaharness/harness';
2
+ export type { LayerPolicyMode };
3
+ export { DEFAULT_LAYER_POLICY_MODE };
4
+ export declare function isLayerPolicyMode(v: unknown): v is LayerPolicyMode;
5
+ /** The mode a resolved config means; unset ⇒ the package default (static). */
6
+ export declare function resolveLayerPolicyModeFromConfig(mode: LayerPolicyMode | undefined): LayerPolicyMode;
7
+ /** The single place a resolved `config.layerPolicyMode` becomes "does adaptive behaviour execute?". Shadow does not. */
8
+ export declare function isAdaptivePolicyEnabled(mode: LayerPolicyMode | undefined): boolean;
9
+ /** True when decisions should be computed and recorded (shadow or adaptive). */
10
+ export declare function isPolicyRecordingEnabled(mode: LayerPolicyMode | undefined): boolean;
11
+ /** Unset/empty ⇒ default silently; an unrecognized value ⇒ default with a warning naming `varName`. */
12
+ export declare function normalizeLayerPolicyMode(raw: string | undefined, varName?: string): LayerPolicyMode;
13
+ export declare function resolveLayerPolicyMode(env: NodeJS.ProcessEnv): LayerPolicyMode;