hankweave 0.7.1 → 0.7.3

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.
@@ -9,6 +9,16 @@ import type { Logger } from "./utils.js";
9
9
  export declare class ClaudeExecutableNotFoundError extends Error {
10
10
  constructor(message: string);
11
11
  }
12
+ /**
13
+ * Default stream-inactivity timeout for SDK sessions, in seconds.
14
+ *
15
+ * Unlike the shim binaries (which apply their own internal 120s default), the
16
+ * SDK path historically had NO inactivity bound when shimIdleTimeout was
17
+ * unset — a hung streaming connection would sit silent until the OS killed
18
+ * the socket. SDK messages arrive at message granularity (not per token), so 180s of total silence
19
+ * reliably indicates a dead stream rather than a slow turn.
20
+ */
21
+ export declare const DEFAULT_SDK_IDLE_TIMEOUT_SECONDS = 180;
12
22
  /**
13
23
  * Whether lenient ("legacy") Claude auth is enabled via HW_INTERNAL_CLAUDE_LEGACY_AUTH.
14
24
  *
@@ -107,6 +117,14 @@ export declare class ClaudeAgentSDKManager extends BaseProcessManager {
107
117
  * Write a message to the log file.
108
118
  */
109
119
  private writeToLog;
120
+ /**
121
+ * Write a synthetic error result message to the log and wait for the write
122
+ * to flush. Used when the session dies without the SDK emitting a result
123
+ * (e.g. idle timeout): the log parser picks it up so the runtime gets a
124
+ * classifiable failure reason. The flush matters — emitExit() synchronously
125
+ * re-parses the log file, so a buffered write would be invisible to it.
126
+ */
127
+ private writeSyntheticErrorResult;
110
128
  /**
111
129
  * Kill the Claude Agent SDK session gracefully.
112
130
  * Aborts the query (which triggers SIGTERM on the child via the SDK's abort handler),
@@ -136,6 +154,15 @@ export declare class ClaudeAgentSDKManager extends BaseProcessManager {
136
154
  * Get session ID.
137
155
  */
138
156
  getSessionId(): string | undefined;
157
+ /**
158
+ * Whether the SDK delivered at least one message (the session id is captured
159
+ * from the first message). This is a synchronous, race-free establishment
160
+ * signal: it is set inside the message loop and survives `cleanup()`, so it is
161
+ * still readable on the error path after a query-promise rejection — unlike the
162
+ * log-parser-fed flag in CodonRunner, which may not have parsed the init line
163
+ * yet when the SDK emits "error".
164
+ */
165
+ getSessionEstablished(): boolean;
139
166
  /**
140
167
  * Get synthetic PID (for compatibility with ClaudeProcessManager API).
141
168
  * Note: This is not a real process ID since SDK runs in-process.
@@ -123,21 +123,10 @@ export type CodonRunnerConfig = CodonRunnerConfigWithoutExtension | CodonRunnerC
123
123
  * ensuring they are created together, used together, and cleaned up together.
124
124
  */
125
125
  /**
126
- * Failure reasons that prevent extension
126
+ * Failure reasons that prevent extension.
127
+ * Shared with the runtime's failure-policy machinery (see event-schemas.ts).
127
128
  */
128
- type FailureReason = {
129
- type: "timeout";
130
- retriable: boolean;
131
- } | {
132
- type: "rate-limit";
133
- retriable: boolean;
134
- } | {
135
- type: "api-error";
136
- retriable: boolean;
137
- } | {
138
- type: "unknown";
139
- retriable: boolean;
140
- };
129
+ type FailureReason = import("./types/types.js").FailureReason;
141
130
  /**
142
131
  * Determines whether a codon should extend based on exit conditions.
143
132
  * Pure function for unit testing.
@@ -160,6 +149,7 @@ export declare function shouldExtendCodon(params: {
160
149
  isInterrupted: boolean;
161
150
  failureReason: FailureReason | undefined;
162
151
  isBudgetExceeded?: boolean;
152
+ errorResultReceived?: boolean;
163
153
  }): boolean;
164
154
  export declare class CodonRunner extends TypedEventEmitter<CodonRunnerEvents> {
165
155
  private readonly config;
@@ -172,8 +162,10 @@ export declare class CodonRunner extends TypedEventEmitter<CodonRunnerEvents> {
172
162
  private successResultReceived;
173
163
  private extensionCount;
174
164
  private resultMessageReceived;
165
+ private errorResultReceived;
175
166
  private failureReason;
176
167
  private currentSessionId;
168
+ private systemMessageReceived;
177
169
  constructor(config: CodonRunnerConfig);
178
170
  /**
179
171
  * Check if a model can be run by CodonRunner.
@@ -182,6 +174,7 @@ export declare class CodonRunner extends TypedEventEmitter<CodonRunnerEvents> {
182
174
  * - Anthropic models via ClaudeAgentSDKManager
183
175
  * - Google models via ShimProcessManager (gemini shim)
184
176
  * - OpenAI models via ShimProcessManager (codex shim)
177
+ * - DeepSeek, Pi, and OpenCode models via ShimProcessManager
185
178
  *
186
179
  * @param model - The ModelInfo to check
187
180
  * @returns true if the model can be executed, false otherwise
@@ -263,6 +256,27 @@ export declare class CodonRunner extends TypedEventEmitter<CodonRunnerEvents> {
263
256
  * Get the process ID (if running)
264
257
  */
265
258
  getPid(): number | undefined;
259
+ /**
260
+ * The classified failure reason for the current attempt, if one was derived
261
+ * from an error result message or a transient mid-stream crash. The runtime
262
+ * adopts this on the exit path when it has no result-message-derived reason of
263
+ * its own, so `onFailure: retry` can act on SDK crashes that never produced a
264
+ * terminal result.
265
+ */
266
+ getFailureReason(): FailureReason | undefined;
267
+ /**
268
+ * Whether the agent session was established (first system/init message
269
+ * observed) before any failure. Used to gate the retriability of unrecognized
270
+ * crashes: a process that dies before establishing a session, with text we
271
+ * don't recognize as a transient API error, is a local setup failure, not a
272
+ * transient one.
273
+ *
274
+ * ORs the log-parser-fed flag with the SDK manager's synchronous signal: the
275
+ * SDK emits "error" after cleanup() without force-parsing the log, so the
276
+ * parser-fed flag can lag a genuine establishment — `getSessionEstablished()`
277
+ * (derived from the session id captured in the message loop) closes that race.
278
+ */
279
+ getSystemMessageReceived(): boolean;
266
280
  /**
267
281
  * Get the prompt frontmatter (if any was parsed from the prompt file)
268
282
  */
@@ -13,4 +13,16 @@ export interface ModelValidationResult {
13
13
  /** Type of match that was found (if any) */
14
14
  matchType?: "exact" | "exact-with-inferred-provider" | "fuzzy";
15
15
  }
16
+ /**
17
+ * Validates a model string against the LLM registry and CodonRunner capabilities.
18
+ *
19
+ * Validation steps:
20
+ * 1. Resolve the model using LLMProviderRegistry.resolveModel()
21
+ * 2. Check if the resolved model can be run by CodonRunner.canRun()
22
+ *
23
+ * @param model - The model string to validate (e.g., "sonnet", "gemini-2.0-flash-exp")
24
+ * @param registry - The LLMProviderRegistry instance to use for resolution
25
+ * @param providerId - Optional provider ID to narrow the search (e.g., "anthropic", "google")
26
+ * @returns ModelValidationResult with validation outcome
27
+ */
16
28
  export declare function validateModel(model: string, registry: LlmProviderRegistry, providerId?: string): ModelValidationResult;
package/dist/config.d.ts CHANGED
@@ -10,6 +10,7 @@ export declare const TIMEOUTS: {
10
10
  readonly LOG_PARSER_DELAY_MS: 100;
11
11
  readonly CODON_CLEANUP_DELAY_MS: 100;
12
12
  readonly SELF_TEST_TIMEOUT_MS: 30000 | 60000;
13
+ readonly SHUTDOWN_WATCHDOG_MS: 30000;
13
14
  };
14
15
  /** Maximum allowed shimIdleTimeout in seconds */
15
16
  export declare const SHIM_IDLE_TIMEOUT_MAX_SECONDS = 1800;
@@ -40633,6 +40634,13 @@ export interface HankweaveConfig extends Omit<Required<RuntimeConfig>, "model" |
40633
40634
  isResuming: boolean;
40634
40635
  /** How data is linked (symlink or copy) */
40635
40636
  linkType: "symlink" | "copy";
40637
+ /**
40638
+ * True when launched with --headless: no interactive TUI/client is driving
40639
+ * the run. Used to decide that a retriable failure under onFailure:"abort"
40640
+ * must fail-and-shutdown rather than park in "stay-active" (which would hang
40641
+ * forever waiting for a client that will never connect).
40642
+ */
40643
+ headless?: boolean;
40636
40644
  /** Array of codon configurations to execute */
40637
40645
  codons: CodonConfig[];
40638
40646
  /** Path to a replay source directory (for replaying execution from JSONL logs without real LLM calls) */
@@ -1,4 +1,5 @@
1
1
  import type { LlmProviderRegistry } from "./llm/llm-provider-registry.js";
2
+ import type { ModelInfo } from "./llm/models-dev-schema.js";
2
3
  import { TypedEventEmitter } from "./typed-event-emitter.js";
3
4
  import type { TokenUsage } from "./types/types.js";
4
5
  import type { Logger } from "./utils.js";
@@ -48,12 +49,14 @@ export interface CostTrackerEvents extends Record<string, unknown[]> {
48
49
  * Owned by CodonRunner. The runtime subscribes to its events.
49
50
  */
50
51
  export declare class CostTracker extends TypedEventEmitter<CostTrackerEvents> {
51
- private readonly modelId;
52
52
  private readonly llmRegistry;
53
53
  private readonly logger;
54
54
  private runningCost;
55
55
  private runningTokens;
56
- constructor(modelId: string | undefined, llmRegistry: LlmProviderRegistry, logger: Logger);
56
+ private readonly executableModelId;
57
+ private readonly pricingModelId;
58
+ constructor(model: ModelInfo | undefined, llmRegistry: LlmProviderRegistry, logger: Logger);
59
+ private static getPricingModelId;
57
60
  /**
58
61
  * Process usage from an assistant message. Calculates incremental cost,
59
62
  * accumulates running totals, and emits costIncremented.
@@ -0,0 +1,78 @@
1
+ import type { FailureReason } from "./types/types.js";
2
+ /**
3
+ * Pure decision core of the runtime's failure policy
4
+ * (HankweaveRuntime.resolveFailurePolicy delegates here; extracted for unit
5
+ * testing, mirroring shouldExtendCodon in codon-runner.ts).
6
+ *
7
+ * - abort: retriable errors leave the server active for manual retry,
8
+ * permanent ones shut the run down.
9
+ * - retry: retriable errors retry until maxAttempts is exhausted; permanent
10
+ * errors shut down immediately.
11
+ * - ignore: always continue to the next codon.
12
+ */
13
+ export declare function resolveFailureAction(params: {
14
+ onFailure: "abort" | "retry" | "ignore";
15
+ retriable: boolean;
16
+ attempts: number;
17
+ maxAttempts: number;
18
+ }): "shutdown" | "stay-active" | "retry" | "continue";
19
+ /**
20
+ * Classify API error text into a FailureReason with a retriability verdict.
21
+ *
22
+ * This is the single source of truth for deciding whether an API-level error
23
+ * is transient (worth retrying under onFailure: "retry") or permanent.
24
+ * Both the runtime's result-message handling and CodonRunner's extension
25
+ * logic must use this — divergent heuristics in those two places previously
26
+ * caused transient socket drops to abort runs that were configured to retry
27
+ *
28
+ * Classification policy:
29
+ * - Billing/credit/quota and auth errors are permanent: retrying burns
30
+ * attempts on a hopeless request.
31
+ * - Invalid-request (400) errors are permanent: the same request will fail
32
+ * the same way.
33
+ * - Transport faults (socket closed/reset, connection errors), timeouts,
34
+ * rate limits, and server-side errors (5xx, overloaded) are transient.
35
+ * - Unrecognized error text defaults to RETRIABLE: retries are bounded by
36
+ * retryConfig.maxAttempts, so a wrong "retriable" costs one extra attempt,
37
+ * while a wrong "non-retriable" kills the whole run. This default is gated on
38
+ * `opts.sessionEstablished`: when the caller knows no session was ever
39
+ * established (the process failed before its first message), unrecognized text
40
+ * is a local setup failure and is classified NON-retriable instead. The known
41
+ * transient/permanent patterns above are unaffected by the gate.
42
+ */
43
+ export declare function classifyApiErrorText(text: string, opts?: {
44
+ sessionEstablished?: boolean;
45
+ }): FailureReason;
46
+ /**
47
+ * Synthesize a failure reason for a codon that ended in a FAILED state without
48
+ * any classified reason of its own (no error result message, no transient-crash
49
+ * classification from the runner).
50
+ *
51
+ * This is the single convergence point for every process manager when a process
52
+ * exits without reporting a usable outcome:
53
+ * - An early/pre-init shim exit on an API error returns a non-zero code with no
54
+ * result message (gemini shim.ts:593 `!systemEmitted`; pi index.ts:452;
55
+ * opencode shim.ts:462/486 and binary/arg-resolution failures).
56
+ * - The Claude Agent SDK or any child process can crash without a terminal
57
+ * result.
58
+ *
59
+ * Retriability is gated on session establishment (`sessionEstablished`):
60
+ * - established then exited with no result → plausibly transient, RETRIABLE
61
+ * (bounded by retryConfig.maxAttempts), mirroring classifyApiErrorText's
62
+ * policy for unrecognized errors.
63
+ * - never established (the process exited before its first message) → a local
64
+ * setup/binary/resume-resolution failure that retrying cannot fix →
65
+ * NON-retriable. Defaults to true so callers that don't supply the signal
66
+ * keep the original retriable behavior.
67
+ *
68
+ * Returns undefined (no synthesis — preserve the existing non-retriable default)
69
+ * when the failure is intentional or owned by another mechanism:
70
+ * - force-stop: the user asked to stop; retrying would fight that intent.
71
+ * - context-exceeded: the loop/termination logic owns this outcome, not retry.
72
+ */
73
+ export declare function synthesizeMissingFailureReason(params: {
74
+ isForceStopping: boolean;
75
+ isContextExceeded: boolean;
76
+ exitCode: number;
77
+ sessionEstablished?: boolean;
78
+ }): FailureReason | undefined;