@buildaharness/aielia 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/action-approval-service.d.ts +2 -0
- package/dist/ask-draft.d.ts +22 -0
- package/dist/assistant-options.d.ts +35 -0
- package/dist/assistant-session.d.ts +1 -1
- package/dist/{bin-C_hgDdSf.js → bin-CCwDFxDe.js} +145 -258
- package/dist/bin-CCwDFxDe.js.map +1 -0
- package/dist/cli-config.d.ts +2 -2
- package/dist/cli-session.d.ts +2 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1 -1
- package/dist/commands.d.ts +32 -0
- package/dist/config-keys.d.ts +43 -0
- package/dist/doctor-checks.d.ts +1 -2
- package/dist/fact-extraction.d.ts +4 -0
- package/dist/goal-graph-store.d.ts +6 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +199 -148
- package/dist/index.js.map +1 -1
- package/dist/llm-client-factory.d.ts +13 -0
- package/dist/memory-service.d.ts +2 -0
- package/dist/model-pricing.d.ts +12 -9
- package/dist/node-display-names.d.ts +1 -1
- package/dist/project-command.d.ts +16 -0
- package/dist/{provider-setup-wX2IBw2X.js → provider-setup-Cy9dwqrd.js} +13504 -13034
- package/dist/provider-setup-Cy9dwqrd.js.map +1 -0
- package/dist/proxy-health.d.ts +8 -0
- package/dist/redact-url.d.ts +2 -0
- package/dist/reply-audit.d.ts +2 -0
- package/dist/response-service.d.ts +10 -0
- package/dist/run-explain.d.ts +58 -0
- package/dist/{tui-app-C0J6HRzv.js → tui-app-Dt72u8vZ.js} +11 -3
- package/dist/{tui-app-C0J6HRzv.js.map → tui-app-Dt72u8vZ.js.map} +1 -1
- package/dist/turn-cost.d.ts +12 -0
- package/dist/turn-presentation.d.ts +86 -0
- package/dist/undo-action-command.d.ts +12 -0
- package/dist/undo-plan.d.ts +13 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/dist/bin-C_hgDdSf.js.map +0 -1
- package/dist/provider-setup-wX2IBw2X.js.map +0 -1
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'\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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"}
|
|
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 decide whether a user\\'s request to an assistant with file and shell tools fully determines the', '{\"ambiguous\":false,\"question\":\"\"}'],\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":["d","m"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ILLMClient } from '@buildaharness/runtime';
|
|
2
|
+
import { AssistantConfig } from './config.js';
|
|
3
|
+
export interface LlmClientFactoryOptions {
|
|
4
|
+
/** Passed to every HTTP client. The desktop app supplies Tauri's fetch, because its webview CSP blocks the global one. */
|
|
5
|
+
fetchImpl?: typeof fetch;
|
|
6
|
+
/**
|
|
7
|
+
* Builds the surface's own claude-cli client (the CLI's file/shell/web/action tools, desktop's Tauri bridge).
|
|
8
|
+
* Return undefined where `claude -p` cannot run (a plain browser tab) and the factory falls back to the proxy.
|
|
9
|
+
*/
|
|
10
|
+
claudeCli: () => ILLMClient | undefined;
|
|
11
|
+
}
|
|
12
|
+
/** The one switch over the five backends. Only the claude-cli client and `fetch` differ per surface, and both come in through the options. */
|
|
13
|
+
export declare function createLlmClient(config: AssistantConfig, { fetchImpl, claudeCli }: LlmClientFactoryOptions): ILLMClient;
|
package/dist/memory-service.d.ts
CHANGED
|
@@ -47,6 +47,8 @@ export interface AuditEntry {
|
|
|
47
47
|
undoes?: number;
|
|
48
48
|
/** M6: the user erased this fact from history (`/memory archive forget`); before/after were removed and the entry can no longer be undone. */
|
|
49
49
|
erased?: boolean;
|
|
50
|
+
/** Export only: the images of a forgotten fact are left out of `/memory export`; the stored log still has them so `/memory undo` works. */
|
|
51
|
+
withheld?: boolean;
|
|
50
52
|
/** M5: entries applied together (one consolidation proposal) share a group; undoing one undoes the group. */
|
|
51
53
|
group?: string;
|
|
52
54
|
/** M5: position the pre-image held in the durable list, so undo restores order exactly. */
|
package/dist/model-pricing.d.ts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Static, approximate per-model pricing — a fallback
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* update the numbers below if Anthropic's published pricing changes. The Claude CLI backend
|
|
8
|
-
* needs no such table; its costUsd comes from Claude's own real accounting (see
|
|
9
|
-
* claude-cli-prompt.ts's parseClaudeCliOutput).
|
|
2
|
+
* Static, approximate per-model pricing — a fallback for every backend whose response carries
|
|
3
|
+
* token counts but no dollar figure. Anthropic and OpenAI responses report tokens only; OpenRouter
|
|
4
|
+
* (usage.cost) and the Claude CLI (total_cost_usd) report a real cost, which callers prefer over
|
|
5
|
+
* this table. This table is NOT billing-accurate: it ignores batch pricing, long-context tiers and
|
|
6
|
+
* any rate change since it was last updated — update the numbers below when published pricing changes.
|
|
10
7
|
*/
|
|
11
8
|
export interface ModelPriceUsd {
|
|
12
9
|
inputPerMTok: number;
|
|
13
10
|
outputPerMTok: number;
|
|
11
|
+
/** Rate for input tokens served from the provider's prompt cache; absent = same as inputPerMTok. */
|
|
12
|
+
cachedInputPerMTok?: number;
|
|
14
13
|
}
|
|
15
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Returns undefined (rather than a misleading number) when the model is unset or not a recognized one — callers must not silently treat "no estimate" as "free".
|
|
16
|
+
* `cachedInputTokens` follows OpenAI's convention (a subset of inputTokens, billed at the cached rate); it is ignored for models with no cached rate.
|
|
17
|
+
*/
|
|
16
18
|
export declare function estimateCostUsd(model: string | undefined, usage: {
|
|
17
19
|
inputTokens: number;
|
|
18
20
|
outputTokens: number;
|
|
21
|
+
cachedInputTokens?: number;
|
|
19
22
|
}): number | undefined;
|
|
@@ -20,7 +20,7 @@ export interface WhyChainItem {
|
|
|
20
20
|
* Collapses a turn's raw layerActivity (one event per layer per main-loop iteration — see
|
|
21
21
|
* harness-runtime.ts's onLayerActivity doc comment) down to just the layers that fired, in the
|
|
22
22
|
* order they fired, merging consecutive re-fires of the same layer (back-to-back loop
|
|
23
|
-
* iterations) into a single link.
|
|
23
|
+
* iterations) into a single link. Used by run-explain.ts, which both the CLI's `/why` and chat-ui's `/why` print, so both
|
|
24
24
|
* surfaces summarize a turn identically instead of drifting apart.
|
|
25
25
|
*/
|
|
26
26
|
export declare function buildWhyChain(layerActivity: LayerActivityEvent[]): WhyChainItem[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/project [name]` for every surface: no argument shows the active project, `clear` reverts to the workspace
|
|
3
|
+
* default, anything else switches to that name. Pure: the surface persists the config change and shows the message.
|
|
4
|
+
*/
|
|
5
|
+
export type ProjectCommand = {
|
|
6
|
+
kind: 'show';
|
|
7
|
+
message: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'set';
|
|
10
|
+
project: string;
|
|
11
|
+
message: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'clear';
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function parseProjectCommand(args: readonly string[], currentProject: string | undefined): ProjectCommand;
|