@buildaharness/aielia 0.3.2 → 0.3.4
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 +1 -1
- package/dist/agent-loop.d.ts +9 -1
- package/dist/assistant-session.d.ts +4 -4
- package/dist/assistant-types.d.ts +8 -0
- package/dist/assistant.d.ts +39 -0
- package/dist/{bin-CqkqT6KK.js → bin-CdVTAe7j.js} +136 -34
- package/dist/bin-CdVTAe7j.js.map +1 -0
- package/dist/cli-session.d.ts +12 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +22 -0
- package/dist/first-run.d.ts +3 -0
- package/dist/goal-graph-flag.d.ts +44 -0
- package/dist/goal-graph-reconcile.d.ts +55 -0
- package/dist/goal-graph-service.d.ts +48 -0
- package/dist/goal-graph-store.d.ts +485 -0
- package/dist/goal-graph-suggest-flag.d.ts +34 -0
- package/dist/goal-identity-matcher.d.ts +21 -0
- package/dist/goal-thread-identity.d.ts +32 -0
- package/dist/goal-thread-scheduler.d.ts +78 -0
- package/dist/harness-bridge.d.ts +17 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +86 -71
- package/dist/live-steering-channel.d.ts +31 -0
- package/dist/memory-service.d.ts +10 -0
- package/dist/next-step-proposer.d.ts +52 -0
- package/dist/plan-approval-service.d.ts +1 -1
- package/dist/plan-drafting-service.d.ts +1 -1
- package/dist/{cli-session-BiX2DOni.js → provider-setup-CPbB5y_V.js} +4820 -304
- package/dist/provider-setup-CPbB5y_V.js.map +1 -0
- package/dist/provider-setup.d.ts +51 -0
- package/dist/response-service.d.ts +18 -2
- package/dist/review-checker.d.ts +10 -0
- package/dist/scope-urgency-classifier.d.ts +29 -0
- package/dist/semantic-criterion-coverage.d.ts +10 -0
- package/dist/supervisor-flag.d.ts +20 -0
- package/dist/{tui-app-4_TrzESz.js → tui-app-DeyZ4Pr9.js} +3 -3
- package/dist/{tui-app-4_TrzESz.js.map → tui-app-DeyZ4Pr9.js.map} +1 -1
- package/dist/turn-interpreter.d.ts +12 -0
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
- package/dist/bin-CqkqT6KK.js.map +0 -1
- package/dist/cli-session-BiX2DOni.js.map +0 -1
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ silently starting over.
|
|
|
99
99
|
|
|
100
100
|
Net effect: one real LLM call per ordinary turn, zero for a blocked one, and
|
|
101
101
|
every layer of the harness touched on the ones that do run — matching what
|
|
102
|
-
[
|
|
102
|
+
[myaielia.com/harness-comparison](https://myaielia.com/harness-comparison)
|
|
103
103
|
calls out as missing from Hermes Agent, Kilo Code, and OpenClaw: none of them
|
|
104
104
|
ships a formal Control State resolver *and* a Reviewer/output gate together.
|
|
105
105
|
<!--
|
package/dist/agent-loop.d.ts
CHANGED
|
@@ -196,6 +196,14 @@ export declare class AgentLoop {
|
|
|
196
196
|
onToolStep?: (step: AssistantToolStep) => void;
|
|
197
197
|
onUsage?: (usage: TokenUsage) => void;
|
|
198
198
|
riskHint?: TurnIntentClassification['riskLevel'];
|
|
199
|
+
/**
|
|
200
|
+
* Mid-turn steering (goalGraphMode): messages the user sent while this turn runs that the
|
|
201
|
+
* scope×urgency classifier judged apply to the answer being produced right now. Called once
|
|
202
|
+
* per iteration, immediately before the LLM call; anything returned is spliced in as a user
|
|
203
|
+
* turn so the model itself applies it — no lexical matching, unlike the harness's
|
|
204
|
+
* caller-constraint output contract.
|
|
205
|
+
*/
|
|
206
|
+
takeSteeringNotes?: () => string[];
|
|
199
207
|
}): (toolCtx: ToolExecutorContext) => Promise<unknown>;
|
|
200
208
|
/**
|
|
201
209
|
* R3 of the internal plan: the wiring-level counterpart to
|
|
@@ -208,7 +216,7 @@ export declare class AgentLoop {
|
|
|
208
216
|
* back whatever got pushed to it once the harness run using this proposer has finished (mirrors
|
|
209
217
|
* `ToolLoopResult`'s `sources` field on the flag-OFF path).
|
|
210
218
|
*/
|
|
211
|
-
createOneLoopProposer(sessionId: string, transcript: ChatMessage[], userMessage: string, systemPrompt: string, onToken?: (token: string) => void, onToolStep?: (step: AssistantToolStep) => void, onUsage?: (usage: TokenUsage) => void, riskHint?: TurnIntentClassification['riskLevel']): {
|
|
219
|
+
createOneLoopProposer(sessionId: string, transcript: ChatMessage[], userMessage: string, systemPrompt: string, onToken?: (token: string) => void, onToolStep?: (step: AssistantToolStep) => void, onUsage?: (usage: TokenUsage) => void, riskHint?: TurnIntentClassification['riskLevel'], takeSteeringNotes?: () => string[]): {
|
|
212
220
|
proposer: (toolCtx: ToolExecutorContext) => Promise<unknown>;
|
|
213
221
|
sources: AssistantSource[];
|
|
214
222
|
};
|
|
@@ -99,8 +99,8 @@ export declare class AssistantSession {
|
|
|
99
99
|
* isn't set.
|
|
100
100
|
*/
|
|
101
101
|
recordSpend(sessionId: string, usage: TokenUsage | undefined): Promise<void>;
|
|
102
|
-
/** Read-only — null means plan mode has never been entered (or was already exited/cancelled) for this session. */
|
|
103
|
-
getPlanModeState(sessionId: string): Promise<PlanModeState | null>;
|
|
102
|
+
/** Read-only — null means plan mode has never been entered (or was already exited/cancelled) for this session (or this `threadId`, once Phase 5's per-thread rescoping applies — see `planModeKey`). */
|
|
103
|
+
getPlanModeState(sessionId: string, threadId?: string): Promise<PlanModeState | null>;
|
|
104
104
|
/**
|
|
105
105
|
* The one place `planMode.active` ever flips true — a fresh `draftId` per entry, even if a
|
|
106
106
|
* prior drafting session was cancelled without ever reaching approval, so a stale draftId can
|
|
@@ -108,9 +108,9 @@ export declare class AssistantSession {
|
|
|
108
108
|
* a new state, same as a fresh entry) — callers that only ever call this once per drafting
|
|
109
109
|
* session (P3's future auto-trigger) never observe that.
|
|
110
110
|
*/
|
|
111
|
-
enterPlanMode(sessionId: string): Promise<PlanModeState>;
|
|
111
|
+
enterPlanMode(sessionId: string, threadId?: string): Promise<PlanModeState>;
|
|
112
112
|
/** Clears `planMode.active` — the only two designed callers are an explicit cancel phrase (PlanDraftingService) and P2's future approval resolution. */
|
|
113
|
-
exitPlanMode(sessionId: string): Promise<void>;
|
|
113
|
+
exitPlanMode(sessionId: string, threadId?: string): Promise<void>;
|
|
114
114
|
/** The session's conversation transcript, oldest first — same array `turn()` reads/appends to. Used by `/export`. */
|
|
115
115
|
getTranscript(sessionId: string): Promise<ChatMessage[]>;
|
|
116
116
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NextStepSuggestion } from './next-step-proposer.js';
|
|
1
2
|
import { RiskState, LayerActivityEvent, AskQuestion } from '@buildaharness/harness';
|
|
2
3
|
import { TokenUsage } from '@buildaharness/runtime';
|
|
3
4
|
import { AnswerClaim } from './answer-claim.js';
|
|
@@ -63,6 +64,13 @@ export interface AssistantTurnResult {
|
|
|
63
64
|
*/
|
|
64
65
|
status: 'ok' | 'needs_approval' | 'escalated' | 'needs_clarification' | 'needs_plan_approval';
|
|
65
66
|
reply: string | null;
|
|
67
|
+
/**
|
|
68
|
+
* Up to three concrete next steps proposed after a full (non-trivial, `ok`) turn, most confident
|
|
69
|
+
* first — for the front end to show under the reply. Advisory only: never executed, never
|
|
70
|
+
* persisted from here. Present only when the caller enabled `goalGraphSuggestMode`
|
|
71
|
+
* (PersonalAssistantOptions) and the proposer returned at least one; absent otherwise.
|
|
72
|
+
*/
|
|
73
|
+
nextSteps?: NextStepSuggestion[];
|
|
66
74
|
reason?: string;
|
|
67
75
|
/** See ProposerKind. Always set (defaults to 'posthoc'), on every status. */
|
|
68
76
|
proposerKind?: ProposerKind;
|
package/dist/assistant.d.ts
CHANGED
|
@@ -16,11 +16,15 @@ import { PlanRecord } from './plan-store.js';
|
|
|
16
16
|
import { OneLoopMode } from './one-loop-flag.js';
|
|
17
17
|
import { AskMode } from './ask-mode-flag.js';
|
|
18
18
|
import { PlanRolloutMode } from './plan-mode-flag.js';
|
|
19
|
+
import { GoalGraphSuggestMode } from './goal-graph-suggest-flag.js';
|
|
20
|
+
import { GoalGraphState } from './goal-graph-service.js';
|
|
21
|
+
import { LiveSteeringChannel } from './live-steering-channel.js';
|
|
19
22
|
import { DebugLogEntry } from './debug-log.js';
|
|
20
23
|
import { AssistantTurnResult, AssistantProgress } from './assistant-types.js';
|
|
21
24
|
export type { MemorySummary, MemoryExport, PendingFact } from './memory-service.js';
|
|
22
25
|
export type { FactCategory } from './turn-intent-classifier.js';
|
|
23
26
|
export type { IndexedMessage, TranscriptSearchHit } from './assistant-session.js';
|
|
27
|
+
export type { GoalGraphState, GoalThreadView, GoalThreadVisibility, GoalTaskSummary } from './goal-graph-service.js';
|
|
24
28
|
export type { BatchBudgetState } from './agent-loop.js';
|
|
25
29
|
export { trimmedAverage, nextItemBudget } from './agent-loop.js';
|
|
26
30
|
export type { AssistantSource } from './assistant-source.js';
|
|
@@ -105,6 +109,21 @@ export interface TurnOptions {
|
|
|
105
109
|
seedFailures: number;
|
|
106
110
|
onInjected?: () => void;
|
|
107
111
|
};
|
|
112
|
+
/**
|
|
113
|
+
* Phase 4 of plans/hierarchical_goal_tree_and_steering_plan.html (R1/R4) — the session-scoped
|
|
114
|
+
* LiveSteeringChannel a caller (cli.ts/App.tsx, Phase 3) built for mid-task steering. When
|
|
115
|
+
* present, `turn()` wraps it in a scope×urgency-classifying UpdateChannel
|
|
116
|
+
* (goal-graph-reconcile.ts) and threads it into harnessBridge.run() so queued messages are
|
|
117
|
+
* absorbed at the harness's own checkCallerUpdates iteration boundary instead of waiting for
|
|
118
|
+
* the turn to finish. PersonalAssistant never reads goalGraphMode itself (same convention as
|
|
119
|
+
* oneLoopMode/askMode/planMode) — the caller decides whether to pass this at all; when absent,
|
|
120
|
+
* behavior is exactly today's (INV-43). Whatever this turn's harness run doesn't get around to
|
|
121
|
+
* classifying (it ends before the queue drains, or never calls harnessBridge.run() at all — a
|
|
122
|
+
* trivial turn) is pushed back onto this same channel in a `finally`, so the caller's own
|
|
123
|
+
* post-turn fallback drain (built in Phase 3) still picks it up — R4's "never silently drop"
|
|
124
|
+
* holds either way.
|
|
125
|
+
*/
|
|
126
|
+
steeringChannel?: LiveSteeringChannel;
|
|
108
127
|
}
|
|
109
128
|
export interface PersonalAssistantOptions {
|
|
110
129
|
llmClient: ILLMClient;
|
|
@@ -207,6 +226,16 @@ export interface PersonalAssistantOptions {
|
|
|
207
226
|
* call resolveOneLoopMode(process.env) and pass the result here.
|
|
208
227
|
*/
|
|
209
228
|
oneLoopMode?: OneLoopMode;
|
|
229
|
+
/**
|
|
230
|
+
* R7 (Tier 1.5) — next-step suggestions, one bounded LLM call each: after every full
|
|
231
|
+
* (non-trivial, `ok`) turn, up to three options are attached to the result as `nextSteps` for
|
|
232
|
+
* the front end to show under the reply; and when a GoalThread finishes its last task, its own
|
|
233
|
+
* suggestions are persisted on the thread for `/goals`. Defaults to 'disabled' — like
|
|
234
|
+
* oneLoopMode/askMode/planMode, the front end resolves the flag (cli.ts / App.tsx pass
|
|
235
|
+
* `config.goalGraphSuggestMode`, which defaults to enabled), so a library or benchmark caller
|
|
236
|
+
* that never asks for suggestions makes no extra call and sees no new field (INV-43).
|
|
237
|
+
*/
|
|
238
|
+
goalGraphSuggestMode?: GoalGraphSuggestMode;
|
|
210
239
|
/**
|
|
211
240
|
* Q2 of the internal plan — global-flag control point (tier 1 of
|
|
212
241
|
* INV-29) for the ask-question mechanism. Undefined (the default) falls back to
|
|
@@ -269,6 +298,8 @@ export declare class PersonalAssistant {
|
|
|
269
298
|
* the internal plan phase B1.
|
|
270
299
|
*/
|
|
271
300
|
private lastProposerKind;
|
|
301
|
+
/** See PersonalAssistantOptions.goalGraphSuggestMode. */
|
|
302
|
+
private readonly goalGraphSuggestMode;
|
|
272
303
|
private readonly memoryService;
|
|
273
304
|
private readonly session;
|
|
274
305
|
private readonly agentLoop;
|
|
@@ -330,6 +361,14 @@ export declare class PersonalAssistant {
|
|
|
330
361
|
* all). Returns `null` when no plan record exists for this session yet.
|
|
331
362
|
*/
|
|
332
363
|
getPlanState(sessionId: string): Promise<PlanRecord | null>;
|
|
364
|
+
/**
|
|
365
|
+
* Phase 7 of plans/hierarchical_goal_tree_and_steering_plan.html (R5, "Visibility") — the shared
|
|
366
|
+
* read surface behind the CLI's `/goals` and chat-ui's GoalsPanel, analogous to `searchTranscript`
|
|
367
|
+
* above. A pure query over whatever Phases 4-5 already populate in this session's
|
|
368
|
+
* `GoalGraphRecord`; never mints or mutates one. Returns the empty state for a session with no
|
|
369
|
+
* goal graph yet, same "nothing to show" convention `getGoalGraphState` itself documents.
|
|
370
|
+
*/
|
|
371
|
+
getGoalGraphState(sessionId: string): Promise<GoalGraphState>;
|
|
333
372
|
/**
|
|
334
373
|
* P9 of the internal plan — the lightweight plan-sketch delegate: a
|
|
335
374
|
* cheaper "just go research and draft an approach" path than plan mode's durable task-graph
|
|
@@ -8,7 +8,7 @@ import { homedir, tmpdir } from "node:os";
|
|
|
8
8
|
import { join, dirname, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
10
|
import { OPENROUTER_DEFAULT_MODEL, OPENAI_DEFAULT_MODEL, ANTHROPIC_DEFAULT_MODEL, InMemoryReminderStore, FileSystemAdapter, FileSystemExperienceStore, LLMClient, OpenAICompatibleLLMClient, OPENROUTER_EXTRA_HEADERS, OPENROUTER_BASE_URL, OPENAI_BASE_URL, AnthropicLLMClient } from "@buildaharness/runtime";
|
|
11
|
-
import {
|
|
11
|
+
import { G as buildClaudePrompt, aT as stripJsonCodeFence, a as ALREADY_STAGED_ACTION_TOOL, aS as stagedActionInput, aE as parseClaudeCliOutput, aU as stripMcpToolPrefix, P as PROVIDER_SETUP, Q as cleanApiKey, J as checkApiKeyFormat, a0 as envOverridesFromProcessEnv, aJ as resolveConfig, aQ as shouldLaunchTuiApp, aW as testApiKey, aY as validateConfig, c as ConfigValidationError, ai as formatSpendCapStatus, r as PersonalAssistant, ap as isGoalGraphSuggestEnabled, ao as isGoalGraphEnabled, B as braveSearch, X as createSmtpSender, V as createResendSender, p as LiveSteeringChannel, af as formatNextSteps, K as classifyError, ay as nodeToLayer, L as LAYER_DISPLAY_NAME, ax as nodeDisplayName, a1 as estimateCostUsd, a6 as formatConfigListing, an as isConfigKey, C as CONFIG_KEYS, E as ENV_VAR_FOR_CONFIG_KEY, aF as parseConfigValue, d as ConfigValueParseError, a8 as formatDoctorReport, a7 as formatCostSummary, aa as formatGoalGraphState, ah as formatSearchResults, al as formatUndoLogListing, Y as defaultExportFilename, ak as formatTranscriptMarkdown, aj as formatStatus, ab as formatHelp, m as LAYER_ORDER, H as buildWhyChain, n as LAYER_SHORT_CODE, Z as defaultMemoryExportFilename, ac as formatMemoryExport, ad as formatMemoryPendingOutcome, ae as formatMemorySummary } from "./provider-setup-CPbB5y_V.js";
|
|
12
12
|
import { spawn } from "node:child_process";
|
|
13
13
|
import { createServer, connect } from "node:net";
|
|
14
14
|
import { createRequire } from "node:module";
|
|
@@ -66,7 +66,7 @@ function createNodeFsBackend() {
|
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
-
const CLI_VERSION = "0.3.
|
|
69
|
+
const CLI_VERSION = "0.3.4";
|
|
70
70
|
function seaCacheDir(version, home = homedir()) {
|
|
71
71
|
return join(home, ".buildaharness", "personal-assistant", "sea-cache", version);
|
|
72
72
|
}
|
|
@@ -650,6 +650,7 @@ function resolveNonInteractiveApprovalMode(env) {
|
|
|
650
650
|
console.error(`[warning] ASSISTANT_NON_INTERACTIVE_APPROVAL="${raw}" is not "decline" or "require-tty" — ignoring.`);
|
|
651
651
|
return void 0;
|
|
652
652
|
}
|
|
653
|
+
const MAX_KEY_ATTEMPTS = 3;
|
|
653
654
|
function alreadyConfigured(deps) {
|
|
654
655
|
const { persisted, overriddenKeys } = deps;
|
|
655
656
|
if (persisted.llmBackend !== void 0) return true;
|
|
@@ -659,50 +660,94 @@ function alreadyConfigured(deps) {
|
|
|
659
660
|
}
|
|
660
661
|
return false;
|
|
661
662
|
}
|
|
662
|
-
const PROVIDER_CHOICES = {
|
|
663
|
-
"1": { backend: "anthropic", label: "Anthropic (Claude) — needs an sk-ant-… key" },
|
|
664
|
-
"2": { backend: "openai", label: "OpenAI — needs an sk-… key" },
|
|
665
|
-
"3": { backend: "openrouter", label: "OpenRouter — needs an sk-or-… key" }
|
|
666
|
-
};
|
|
667
663
|
async function maybeRunFirstRunSetup(deps) {
|
|
668
|
-
const { configStore, persisted, isInteractive, ask, detectClaudeCli, log } = deps;
|
|
664
|
+
const { configStore, persisted, isInteractive, ask, detectClaudeCli, log, testKey } = deps;
|
|
669
665
|
if (alreadyConfigured(deps)) return persisted;
|
|
670
666
|
if (!isInteractive) return persisted;
|
|
671
667
|
log("");
|
|
672
|
-
log("Welcome to Aielia
|
|
673
|
-
log("
|
|
668
|
+
log("Welcome to Aielia! 👋 Let’s get you set up — it takes about a minute.");
|
|
669
|
+
log("");
|
|
670
|
+
log("Aielia needs an AI model to think with. Pick where that comes from below.");
|
|
671
|
+
log("(You can change this any time with /config, or press Ctrl+C to skip.)");
|
|
674
672
|
log("");
|
|
675
673
|
const patch = {};
|
|
676
674
|
if (await detectClaudeCli()) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
);
|
|
675
|
+
log("✓ Claude is already running on this computer (Claude Code is installed and signed in).");
|
|
676
|
+
log(" That means you can start right now — no API key, no extra account, nothing to paste.");
|
|
677
|
+
log("");
|
|
678
|
+
const useClaude = await ask("Use your Claude login? (Y/n — Enter means yes) ");
|
|
680
679
|
if (useClaude === "" || useClaude.toLowerCase().startsWith("y")) {
|
|
681
680
|
patch.llmBackend = "claude-cli";
|
|
682
681
|
await configStore.save(patch);
|
|
683
682
|
log("");
|
|
684
|
-
log('✓
|
|
683
|
+
log('✓ All set — using your Claude login. Try "what time zone is Tokyo in?", then');
|
|
685
684
|
log(' "send an email to my boss saying I quit" to see the approval gate.');
|
|
686
685
|
log("");
|
|
687
686
|
return { ...persisted, ...patch };
|
|
688
687
|
}
|
|
688
|
+
log("");
|
|
689
|
+
} else {
|
|
690
|
+
log("Tip: if you already use Claude Code, install it and sign in (run `claude` once), then");
|
|
691
|
+
log("start Aielia again — you can use it with no API key. Otherwise, pick a provider below.");
|
|
692
|
+
log("");
|
|
689
693
|
}
|
|
690
|
-
log("
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
+
log("Which AI provider do you have (or want to use)?");
|
|
695
|
+
log("");
|
|
696
|
+
PROVIDER_SETUP.forEach((p, i) => {
|
|
697
|
+
log(` ${i + 1}) ${p.name}`);
|
|
698
|
+
log(` ${p.blurb}`);
|
|
699
|
+
});
|
|
700
|
+
log("");
|
|
701
|
+
const choice = (await ask(`Type 1-${PROVIDER_SETUP.length} and press Enter (or just Enter to skip): `)).trim();
|
|
702
|
+
const picked = PROVIDER_SETUP[Number(choice) - 1];
|
|
694
703
|
if (!picked) {
|
|
695
704
|
log("");
|
|
696
|
-
log('Skipped.
|
|
697
|
-
log("on :8787).
|
|
698
|
-
log("/config set
|
|
705
|
+
log('Skipped. Until you set a provider, Aielia will try the "proxy" backend (needs');
|
|
706
|
+
log("@buildaharness/proxy running on :8787). To set one up later, run:");
|
|
707
|
+
log(" /config set llmBackend <anthropic|openai|openrouter>");
|
|
708
|
+
log(" /config set apiKey <your key>");
|
|
699
709
|
log("");
|
|
700
710
|
return persisted;
|
|
701
711
|
}
|
|
702
|
-
|
|
712
|
+
log("");
|
|
713
|
+
log(`To connect ${picked.name}, you need an “API key” — a password-like code that lets Aielia use your account.`);
|
|
714
|
+
log("Here’s how to get one:");
|
|
715
|
+
log("");
|
|
716
|
+
picked.steps.forEach((step, i) => log(` ${i + 1}. ${step}`));
|
|
717
|
+
if (picked.safety) {
|
|
718
|
+
log("");
|
|
719
|
+
log(` ${picked.safety.title}:`);
|
|
720
|
+
picked.safety.steps.forEach((step, i) => log(` ${i + 1}. ${step}`));
|
|
721
|
+
log(` (${picked.safety.urlLabel})`);
|
|
722
|
+
}
|
|
723
|
+
log("");
|
|
724
|
+
log(`The key starts with "${picked.keyPrefix}". Paste it below when you have it.`);
|
|
725
|
+
log("");
|
|
726
|
+
let key = "";
|
|
727
|
+
for (let attempt = 1; attempt <= MAX_KEY_ATTEMPTS; attempt++) {
|
|
728
|
+
const candidate = cleanApiKey(await ask("Paste your API key (or just Enter to skip): "));
|
|
729
|
+
if (candidate === "") break;
|
|
730
|
+
const formatProblem = checkApiKeyFormat(picked.backend, candidate);
|
|
731
|
+
if (formatProblem) {
|
|
732
|
+
log(` ✗ ${formatProblem}`);
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
if (testKey) {
|
|
736
|
+
log(" Checking your key…");
|
|
737
|
+
const result = await testKey(picked.backend, candidate);
|
|
738
|
+
if (result.status === "invalid") {
|
|
739
|
+
log(` ✗ ${result.message}`);
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
if (result.status === "unverified") log(` ! ${result.message}`);
|
|
743
|
+
}
|
|
744
|
+
key = candidate;
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
703
747
|
if (!key) {
|
|
704
748
|
log("");
|
|
705
|
-
log("No key entered
|
|
749
|
+
log("No working key entered, so nothing was saved. Run Aielia again to retry, or use");
|
|
750
|
+
log("/config set llmBackend " + picked.backend + " then /config set apiKey <key>");
|
|
706
751
|
log("");
|
|
707
752
|
return persisted;
|
|
708
753
|
}
|
|
@@ -710,8 +755,9 @@ async function maybeRunFirstRunSetup(deps) {
|
|
|
710
755
|
patch.apiKey = key;
|
|
711
756
|
await configStore.save(patch);
|
|
712
757
|
log("");
|
|
713
|
-
log(`✓
|
|
714
|
-
log("
|
|
758
|
+
log(`✓ You’re all set — using ${picked.name}. Say hello!`);
|
|
759
|
+
log(" Your key is saved on this computer in ~/.buildaharness/personal-assistant/config.json");
|
|
760
|
+
log(" (plain text, like a .env file) — don’t share that file.");
|
|
715
761
|
log("");
|
|
716
762
|
return { ...persisted, ...patch };
|
|
717
763
|
}
|
|
@@ -1088,6 +1134,10 @@ async function buildAssistant(config, { backend, dataDir, remindersFile }) {
|
|
|
1088
1134
|
// chat-ui, and the Tauri desktop build all decide this the same way. Undefined here means
|
|
1089
1135
|
// PersonalAssistant falls back to DEFAULT_ONE_LOOP_MODE.
|
|
1090
1136
|
oneLoopMode: config.oneLoopMode,
|
|
1137
|
+
// R7: turn-end next-step options and DONE-thread suggestions. Undefined config means the
|
|
1138
|
+
// package default (enabled); PersonalAssistant itself defaults to disabled, so this is where
|
|
1139
|
+
// the front end opts in.
|
|
1140
|
+
goalGraphSuggestMode: isGoalGraphSuggestEnabled(config.goalGraphSuggestMode) ? "enabled" : "disabled",
|
|
1091
1141
|
// P11 of the internal plan — resolved through the same shared config
|
|
1092
1142
|
// seam as oneLoopMode above (ASSISTANT_PLAN_MODE / `/config set planMode`). Undefined here
|
|
1093
1143
|
// means PersonalAssistant falls back to DEFAULT_PLAN_MODE ('legacy').
|
|
@@ -1095,6 +1145,7 @@ async function buildAssistant(config, { backend, dataDir, remindersFile }) {
|
|
|
1095
1145
|
});
|
|
1096
1146
|
}
|
|
1097
1147
|
async function runCli(options = {}) {
|
|
1148
|
+
console.log("Aielia is alpha software — expect rough edges and breaking changes. It uses AI models and can make mistakes; verify anything important.\n");
|
|
1098
1149
|
const dataDir = options.dataDir ?? defaultDataDir;
|
|
1099
1150
|
const configStore = options.configStore ?? defaultConfigStore;
|
|
1100
1151
|
const backend = options.backend ?? defaultBackend;
|
|
@@ -1123,7 +1174,8 @@ async function runCli(options = {}) {
|
|
|
1123
1174
|
ask: firstRunAsk,
|
|
1124
1175
|
detectClaudeCli: options.detectClaudeCli ?? (async () => (await checkClaudeCli(process.env.CLAUDE_PATH ?? "claude")).ok),
|
|
1125
1176
|
log: (line) => outputStream.write(`${line}
|
|
1126
|
-
`)
|
|
1177
|
+
`),
|
|
1178
|
+
testKey: options.firstRunTestKey ?? ((backend2, key) => testApiKey(backend2, key))
|
|
1127
1179
|
});
|
|
1128
1180
|
} finally {
|
|
1129
1181
|
setupRl == null ? void 0 : setupRl.close();
|
|
@@ -1495,6 +1547,12 @@ ${formatMemoryPendingOutcome("forgotten", outcome)}
|
|
|
1495
1547
|
const hits = await assistant.searchTranscript(query);
|
|
1496
1548
|
console.log(`
|
|
1497
1549
|
${formatSearchResults(hits, query)}
|
|
1550
|
+
`);
|
|
1551
|
+
}
|
|
1552
|
+
async function handleGoals() {
|
|
1553
|
+
const state = await assistant.getGoalGraphState("cli");
|
|
1554
|
+
console.log(`
|
|
1555
|
+
${formatGoalGraphState(state)}
|
|
1498
1556
|
`);
|
|
1499
1557
|
}
|
|
1500
1558
|
async function printCost() {
|
|
@@ -1733,7 +1791,15 @@ Unrecognized input "${token}".
|
|
|
1733
1791
|
if (!streamedAnyTokens) writeProgress(progress);
|
|
1734
1792
|
},
|
|
1735
1793
|
onToken: writeToken,
|
|
1736
|
-
onToolStep: writeToolStep
|
|
1794
|
+
onToolStep: writeToolStep,
|
|
1795
|
+
// Phase 4 — real intra-turn absorption via checkCallerUpdates, replacing Phase 3's
|
|
1796
|
+
// drain-as-follow-up-turns fallback in dispatchOne's `finally` below (kept there, but
|
|
1797
|
+
// only as a backstop: turn() re-enqueues anything it didn't get around to classifying
|
|
1798
|
+
// this turn — e.g. a trivial turn that never calls harnessBridge.run() at all — back onto
|
|
1799
|
+
// this same channel, see TurnOptions.steeringChannel's doc comment). Passed unconditionally:
|
|
1800
|
+
// with goalGraphMode off, routeMessage (below) never enqueues into steeringChannel, so it's
|
|
1801
|
+
// always empty and this resolves to exactly today's behavior (INV-43).
|
|
1802
|
+
steeringChannel
|
|
1737
1803
|
});
|
|
1738
1804
|
if (!streamedAnyTokens) clearProgress();
|
|
1739
1805
|
if (result.status === "needs_approval" && result.pendingActionId) {
|
|
@@ -1803,16 +1869,20 @@ Unrecognized input "${token}".
|
|
|
1803
1869
|
const contradictionNotice = result.contradictionNotice ? `
|
|
1804
1870
|
|
|
1805
1871
|
${result.contradictionNotice}` : "";
|
|
1872
|
+
lastNextSteps = result.nextSteps && result.nextSteps.length > 0 ? result.nextSteps : void 0;
|
|
1873
|
+
const nextStepsBlock = lastNextSteps ? `
|
|
1874
|
+
|
|
1875
|
+
${formatNextSteps(lastNextSteps)}` : "";
|
|
1806
1876
|
if (streamedAnyTokens) {
|
|
1807
1877
|
const pausedNoteText = result.pausedNote ? `
|
|
1808
1878
|
|
|
1809
1879
|
${result.pausedNote}` : "";
|
|
1810
|
-
process.stdout.write(`${pausedNoteText}${riskSuffix}${sourcesHint}${planHint}${contradictionNotice}
|
|
1880
|
+
process.stdout.write(`${pausedNoteText}${riskSuffix}${sourcesHint}${planHint}${contradictionNotice}${nextStepsBlock}
|
|
1811
1881
|
|
|
1812
1882
|
`);
|
|
1813
1883
|
} else {
|
|
1814
1884
|
console.log(`
|
|
1815
|
-
Aielia>${riskSuffix} ${result.reply}${sourcesHint}${planHint}${contradictionNotice}
|
|
1885
|
+
Aielia>${riskSuffix} ${result.reply}${sourcesHint}${planHint}${contradictionNotice}${nextStepsBlock}
|
|
1816
1886
|
`);
|
|
1817
1887
|
}
|
|
1818
1888
|
} catch (err) {
|
|
@@ -1986,6 +2056,9 @@ ${assistant.getActiveProject() || "(none)"}
|
|
|
1986
2056
|
}
|
|
1987
2057
|
await handleConfigCommand(["set", "activeProject", args.join(" ")]);
|
|
1988
2058
|
}
|
|
2059
|
+
let turnInProgress = false;
|
|
2060
|
+
let lastNextSteps;
|
|
2061
|
+
const steeringChannel = new LiveSteeringChannel();
|
|
1989
2062
|
const commands = {
|
|
1990
2063
|
"/why": () => printWhy(),
|
|
1991
2064
|
"/layers": () => printLayers(),
|
|
@@ -2000,6 +2073,7 @@ ${assistant.getActiveProject() || "(none)"}
|
|
|
2000
2073
|
"/undo-action": (args) => handleUndoAction(args),
|
|
2001
2074
|
"/memory": (args) => handleMemory(args),
|
|
2002
2075
|
"/search": (args) => handleSearch(args),
|
|
2076
|
+
"/goals": () => handleGoals(),
|
|
2003
2077
|
"/model": (args) => handleModel(args),
|
|
2004
2078
|
"/project": (args) => handleProject(args),
|
|
2005
2079
|
"/cost": () => printCost(),
|
|
@@ -2008,13 +2082,29 @@ ${assistant.getActiveProject() || "(none)"}
|
|
|
2008
2082
|
"/checkpoint": (args) => handleCheckpoint(args)
|
|
2009
2083
|
};
|
|
2010
2084
|
async function dispatchOne(message) {
|
|
2085
|
+
const pick = /^[1-3]$/.test(message) && lastNextSteps ? lastNextSteps[Number(message) - 1] : void 0;
|
|
2086
|
+
lastNextSteps = void 0;
|
|
2087
|
+
if (pick) {
|
|
2088
|
+
console.log(` → ${pick.description}`);
|
|
2089
|
+
message = pick.description;
|
|
2090
|
+
}
|
|
2011
2091
|
const [token, ...args] = message.split(/\s+/);
|
|
2012
2092
|
const handler = commands[token];
|
|
2013
2093
|
if (handler) {
|
|
2014
2094
|
await handler(args);
|
|
2015
2095
|
return;
|
|
2016
2096
|
}
|
|
2017
|
-
|
|
2097
|
+
turnInProgress = true;
|
|
2098
|
+
try {
|
|
2099
|
+
await handleTurn(message);
|
|
2100
|
+
} finally {
|
|
2101
|
+
turnInProgress = false;
|
|
2102
|
+
if (isGoalGraphEnabled(config.goalGraphMode)) {
|
|
2103
|
+
for (const event of steeringChannel.poll()) {
|
|
2104
|
+
void enqueue(event.message);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2018
2108
|
}
|
|
2019
2109
|
let dispatchQueue = Promise.resolve();
|
|
2020
2110
|
function enqueue(message) {
|
|
@@ -2024,20 +2114,32 @@ ${assistant.getActiveProject() || "(none)"}
|
|
|
2024
2114
|
dispatchQueue = result;
|
|
2025
2115
|
return result;
|
|
2026
2116
|
}
|
|
2117
|
+
function isKnownCommand(message) {
|
|
2118
|
+
const [token] = message.split(/\s+/);
|
|
2119
|
+
return token in commands;
|
|
2120
|
+
}
|
|
2121
|
+
function routeMessage(message) {
|
|
2122
|
+
if (isGoalGraphEnabled(config.goalGraphMode) && turnInProgress && !isKnownCommand(message)) {
|
|
2123
|
+
steeringChannel.enqueue(message);
|
|
2124
|
+
console.log("\n[queued — the current turn is still running; this will be taken into account once it finishes]\n");
|
|
2125
|
+
return Promise.resolve();
|
|
2126
|
+
}
|
|
2127
|
+
return enqueue(message);
|
|
2128
|
+
}
|
|
2027
2129
|
rl.on("line", (line) => {
|
|
2028
2130
|
const message = line.trim();
|
|
2029
2131
|
if (!message) {
|
|
2030
2132
|
rl.prompt();
|
|
2031
2133
|
return;
|
|
2032
2134
|
}
|
|
2033
|
-
void
|
|
2135
|
+
void routeMessage(message).finally(() => rl.prompt());
|
|
2034
2136
|
});
|
|
2035
2137
|
rl.resume();
|
|
2036
2138
|
return {
|
|
2037
2139
|
dispatchLine: async (line) => {
|
|
2038
2140
|
const message = line.trim();
|
|
2039
2141
|
if (!message) return;
|
|
2040
|
-
await
|
|
2142
|
+
await routeMessage(message);
|
|
2041
2143
|
},
|
|
2042
2144
|
close: () => rl.close(),
|
|
2043
2145
|
getStatusIndicators: async () => {
|
|
@@ -2080,7 +2182,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2080
2182
|
});
|
|
2081
2183
|
}
|
|
2082
2184
|
if (shouldLaunchTuiApp(config.tuiMode ?? "disabled", Boolean(process.stdout.isTTY), Boolean(process.stdin.isTTY))) {
|
|
2083
|
-
const { runTuiApp } = await import("./tui-app-
|
|
2185
|
+
const { runTuiApp } = await import("./tui-app-DeyZ4Pr9.js");
|
|
2084
2186
|
await runTuiApp();
|
|
2085
2187
|
return;
|
|
2086
2188
|
}
|
|
@@ -2104,4 +2206,4 @@ export {
|
|
|
2104
2206
|
PLAN_LINE_PREFIX as P,
|
|
2105
2207
|
runCli as r
|
|
2106
2208
|
};
|
|
2107
|
-
//# sourceMappingURL=bin-
|
|
2209
|
+
//# sourceMappingURL=bin-CdVTAe7j.js.map
|