@dungle-scrubs/harness-cli-normalizer 0.6.0 → 0.6.1

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 (212) hide show
  1. package/README.md +18 -6
  2. package/dist/cli/args.d.ts +9 -1
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +31 -26
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/check.d.ts +14 -0
  7. package/dist/cli/check.d.ts.map +1 -1
  8. package/dist/cli/check.js +10 -3
  9. package/dist/cli/check.js.map +1 -1
  10. package/dist/cli/config.d.ts +4 -4
  11. package/dist/cli/config.d.ts.map +1 -1
  12. package/dist/cli/config.js +6 -6
  13. package/dist/cli/config.js.map +1 -1
  14. package/dist/cli/exit-codes.d.ts +0 -1
  15. package/dist/cli/exit-codes.d.ts.map +1 -1
  16. package/dist/cli/exit-codes.js +0 -1
  17. package/dist/cli/exit-codes.js.map +1 -1
  18. package/dist/cli/help.d.ts +2 -2
  19. package/dist/cli/help.d.ts.map +1 -1
  20. package/dist/cli/help.js +6 -2
  21. package/dist/cli/help.js.map +1 -1
  22. package/dist/cli/index.d.ts.map +1 -1
  23. package/dist/cli/index.js +3 -1
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/cli/inspect.d.ts.map +1 -1
  26. package/dist/cli/inspect.js +84 -265
  27. package/dist/cli/inspect.js.map +1 -1
  28. package/dist/cli/plan-turn.d.ts +68 -0
  29. package/dist/cli/plan-turn.d.ts.map +1 -0
  30. package/dist/cli/plan-turn.js +266 -0
  31. package/dist/cli/plan-turn.js.map +1 -0
  32. package/dist/cli/refuse.d.ts.map +1 -1
  33. package/dist/cli/refuse.js +2 -1
  34. package/dist/cli/refuse.js.map +1 -1
  35. package/dist/cli/resolve-harness.d.ts.map +1 -1
  36. package/dist/cli/resolve-harness.js +2 -3
  37. package/dist/cli/resolve-harness.js.map +1 -1
  38. package/dist/cli/run.d.ts.map +1 -1
  39. package/dist/cli/run.js +33 -367
  40. package/dist/cli/run.js.map +1 -1
  41. package/dist/cli/session-json.d.ts +1 -1
  42. package/dist/cli/session-json.d.ts.map +1 -1
  43. package/dist/cli/session-json.js +1 -3
  44. package/dist/cli/session-json.js.map +1 -1
  45. package/dist/cli/session.d.ts.map +1 -1
  46. package/dist/cli/session.js +25 -44
  47. package/dist/cli/session.js.map +1 -1
  48. package/dist/cli/skills-root.d.ts +1 -1
  49. package/dist/cli/skills-root.d.ts.map +1 -1
  50. package/dist/cli/skills-root.js +1 -1
  51. package/dist/cli/skills-root.js.map +1 -1
  52. package/dist/execution/decode.d.ts.map +1 -1
  53. package/dist/execution/decode.js +11 -36
  54. package/dist/execution/decode.js.map +1 -1
  55. package/dist/execution/events.d.ts +4 -1
  56. package/dist/execution/events.d.ts.map +1 -1
  57. package/dist/execution/events.js.map +1 -1
  58. package/dist/execution/failure.d.ts +3 -1
  59. package/dist/execution/failure.d.ts.map +1 -1
  60. package/dist/execution/failure.js +14 -11
  61. package/dist/execution/failure.js.map +1 -1
  62. package/dist/execution/open-session.d.ts +1 -1
  63. package/dist/execution/open-session.d.ts.map +1 -1
  64. package/dist/execution/open-session.js +150 -223
  65. package/dist/execution/open-session.js.map +1 -1
  66. package/dist/execution/stream-turn.d.ts +9 -14
  67. package/dist/execution/stream-turn.d.ts.map +1 -1
  68. package/dist/execution/stream-turn.js +85 -188
  69. package/dist/execution/stream-turn.js.map +1 -1
  70. package/dist/execution/supervisor.d.ts +71 -0
  71. package/dist/execution/supervisor.d.ts.map +1 -0
  72. package/dist/execution/supervisor.js +127 -0
  73. package/dist/execution/supervisor.js.map +1 -0
  74. package/dist/interpretation/argv.d.ts +38 -12
  75. package/dist/interpretation/argv.d.ts.map +1 -1
  76. package/dist/interpretation/argv.js +26 -20
  77. package/dist/interpretation/argv.js.map +1 -1
  78. package/dist/interpretation/content.d.ts +10 -1
  79. package/dist/interpretation/content.d.ts.map +1 -1
  80. package/dist/interpretation/content.js +20 -11
  81. package/dist/interpretation/content.js.map +1 -1
  82. package/dist/interpretation/hints.d.ts +1 -1
  83. package/dist/interpretation/hints.d.ts.map +1 -1
  84. package/dist/interpretation/hints.js +10 -11
  85. package/dist/interpretation/hints.js.map +1 -1
  86. package/dist/interpretation/index.d.ts +0 -1
  87. package/dist/interpretation/index.d.ts.map +1 -1
  88. package/dist/interpretation/index.js +0 -1
  89. package/dist/interpretation/index.js.map +1 -1
  90. package/dist/interpretation/limits.d.ts +2 -5
  91. package/dist/interpretation/limits.d.ts.map +1 -1
  92. package/dist/interpretation/limits.js +8 -62
  93. package/dist/interpretation/limits.js.map +1 -1
  94. package/dist/interpretation/question.d.ts +27 -0
  95. package/dist/interpretation/question.d.ts.map +1 -1
  96. package/dist/interpretation/question.js +28 -0
  97. package/dist/interpretation/question.js.map +1 -1
  98. package/dist/interpretation/refusal.d.ts +10 -5
  99. package/dist/interpretation/refusal.d.ts.map +1 -1
  100. package/dist/interpretation/refusal.js +24 -15
  101. package/dist/interpretation/refusal.js.map +1 -1
  102. package/dist/interpretation/resolve-options.d.ts +36 -2
  103. package/dist/interpretation/resolve-options.d.ts.map +1 -1
  104. package/dist/interpretation/resolve-options.js +38 -21
  105. package/dist/interpretation/resolve-options.js.map +1 -1
  106. package/dist/interpretation/session-input.d.ts +44 -1
  107. package/dist/interpretation/session-input.d.ts.map +1 -1
  108. package/dist/interpretation/session-input.js +59 -4
  109. package/dist/interpretation/session-input.js.map +1 -1
  110. package/dist/interpretation/shape.d.ts +1 -0
  111. package/dist/interpretation/shape.d.ts.map +1 -1
  112. package/dist/interpretation/shape.js +10 -0
  113. package/dist/interpretation/shape.js.map +1 -1
  114. package/dist/interpretation/skills-selection.d.ts +25 -33
  115. package/dist/interpretation/skills-selection.d.ts.map +1 -1
  116. package/dist/interpretation/skills-selection.js +41 -48
  117. package/dist/interpretation/skills-selection.js.map +1 -1
  118. package/dist/interpretation/support.d.ts.map +1 -1
  119. package/dist/interpretation/support.js +23 -8
  120. package/dist/interpretation/support.js.map +1 -1
  121. package/dist/interpretation/tool-selection.d.ts +7 -6
  122. package/dist/interpretation/tool-selection.d.ts.map +1 -1
  123. package/dist/interpretation/tool-selection.js +54 -116
  124. package/dist/interpretation/tool-selection.js.map +1 -1
  125. package/dist/interpretation/tool-vocabulary.d.ts +31 -12
  126. package/dist/interpretation/tool-vocabulary.d.ts.map +1 -1
  127. package/dist/interpretation/tool-vocabulary.js +19 -41
  128. package/dist/interpretation/tool-vocabulary.js.map +1 -1
  129. package/dist/interpretation/turn-options.d.ts +4 -0
  130. package/dist/interpretation/turn-options.d.ts.map +1 -1
  131. package/dist/interpretation/turn-options.js +105 -241
  132. package/dist/interpretation/turn-options.js.map +1 -1
  133. package/dist/interpretation/vocabulary.d.ts +8 -6
  134. package/dist/interpretation/vocabulary.d.ts.map +1 -1
  135. package/dist/interpretation/vocabulary.js +22 -9
  136. package/dist/interpretation/vocabulary.js.map +1 -1
  137. package/dist/knowledge/claude-code.js +3 -3
  138. package/dist/knowledge/claude-code.js.map +1 -1
  139. package/dist/knowledge/codex.d.ts.map +1 -1
  140. package/dist/knowledge/codex.js +31 -7
  141. package/dist/knowledge/codex.js.map +1 -1
  142. package/dist/knowledge/descriptor.d.ts +67 -36
  143. package/dist/knowledge/descriptor.d.ts.map +1 -1
  144. package/dist/knowledge/descriptor.js +49 -15
  145. package/dist/knowledge/descriptor.js.map +1 -1
  146. package/dist/knowledge/matchers.d.ts +12 -0
  147. package/dist/knowledge/matchers.d.ts.map +1 -1
  148. package/dist/knowledge/matchers.js +34 -0
  149. package/dist/knowledge/matchers.js.map +1 -1
  150. package/dist/knowledge/muse.d.ts.map +1 -1
  151. package/dist/knowledge/muse.js +7 -5
  152. package/dist/knowledge/muse.js.map +1 -1
  153. package/dist/knowledge/overrides.d.ts.map +1 -1
  154. package/dist/knowledge/overrides.js +9 -20
  155. package/dist/knowledge/overrides.js.map +1 -1
  156. package/dist/knowledge/pi.d.ts.map +1 -1
  157. package/dist/knowledge/pi.js +8 -6
  158. package/dist/knowledge/pi.js.map +1 -1
  159. package/dist/knowledge/profile.d.ts +1 -0
  160. package/dist/knowledge/profile.d.ts.map +1 -1
  161. package/dist/knowledge/profile.js +1 -0
  162. package/dist/knowledge/profile.js.map +1 -1
  163. package/package.json +1 -1
  164. package/src/cli/args.ts +35 -27
  165. package/src/cli/check.ts +10 -3
  166. package/src/cli/config.ts +12 -11
  167. package/src/cli/exit-codes.ts +0 -2
  168. package/src/cli/help.ts +6 -2
  169. package/src/cli/index.ts +3 -1
  170. package/src/cli/inspect.ts +88 -269
  171. package/src/cli/plan-turn.ts +346 -0
  172. package/src/cli/refuse.ts +2 -1
  173. package/src/cli/resolve-harness.ts +4 -5
  174. package/src/cli/run.ts +36 -399
  175. package/src/cli/session-json.ts +1 -6
  176. package/src/cli/session.ts +34 -44
  177. package/src/cli/skills-root.ts +1 -4
  178. package/src/execution/decode.ts +10 -36
  179. package/src/execution/events.ts +4 -1
  180. package/src/execution/failure.ts +18 -11
  181. package/src/execution/open-session.ts +154 -228
  182. package/src/execution/stream-turn.ts +90 -193
  183. package/src/execution/supervisor.ts +199 -0
  184. package/src/interpretation/argv.ts +62 -34
  185. package/src/interpretation/content.ts +30 -13
  186. package/src/interpretation/hints.ts +2 -12
  187. package/src/interpretation/index.ts +0 -1
  188. package/src/interpretation/limits.ts +14 -75
  189. package/src/interpretation/question.ts +46 -0
  190. package/src/interpretation/refusal.ts +28 -18
  191. package/src/interpretation/resolve-options.ts +85 -32
  192. package/src/interpretation/session-input.ts +88 -5
  193. package/src/interpretation/shape.ts +10 -0
  194. package/src/interpretation/skills-selection.ts +70 -74
  195. package/src/interpretation/support.ts +19 -6
  196. package/src/interpretation/tool-selection.ts +67 -122
  197. package/src/interpretation/tool-vocabulary.ts +48 -53
  198. package/src/interpretation/turn-options.ts +123 -220
  199. package/src/interpretation/vocabulary.ts +15 -10
  200. package/src/knowledge/claude-code.ts +3 -3
  201. package/src/knowledge/codex.ts +31 -7
  202. package/src/knowledge/descriptor.ts +105 -49
  203. package/src/knowledge/matchers.ts +35 -0
  204. package/src/knowledge/muse.ts +7 -5
  205. package/src/knowledge/overrides.ts +17 -32
  206. package/src/knowledge/pi.ts +8 -6
  207. package/src/knowledge/profile.ts +1 -0
  208. package/dist/interpretation/dimensions.d.ts +0 -11
  209. package/dist/interpretation/dimensions.d.ts.map +0 -1
  210. package/dist/interpretation/dimensions.js +0 -4
  211. package/dist/interpretation/dimensions.js.map +0 -1
  212. package/src/interpretation/dimensions.ts +0 -14
@@ -2,7 +2,14 @@ import { randomUUID } from "node:crypto";
2
2
  import { createInterface } from "node:readline/promises";
3
3
  import { nodeRunnerDeps } from "../execution/node-deps.js";
4
4
  import { CLOSE_GRACE_MS, openSession } from "../execution/open-session.js";
5
+ import {
6
+ composeAnswer,
7
+ isQuestionMode,
8
+ QUESTION_MODES,
9
+ type QuestionMode,
10
+ } from "../interpretation/question.js";
5
11
  import { ArgvRefusalError } from "../interpretation/refusal.js";
12
+ import type { BehaviorTier } from "../interpretation/resolve-options.js";
6
13
  import type { HarnessDescriptor } from "../knowledge/descriptor.js";
7
14
  import { defaultDescriptors } from "../knowledge/overrides.js";
8
15
  import { createRenderState, renderEvent } from "./render.js";
@@ -22,7 +29,7 @@ export const session = async (harnessName: string, rawArgs: string[]): Promise<v
22
29
  .map((d) => d.name);
23
30
  const err = new ArgvRefusalError({
24
31
  issue: "no-session-mode",
25
- harness: harnessName as "claude",
32
+ harness: h.name,
26
33
  supported,
27
34
  detail: `session mode is available on ${supported.join(", ")}; ${harnessName} declares no persistent headless session`,
28
35
  });
@@ -37,7 +44,7 @@ export const session = async (harnessName: string, rawArgs: string[]): Promise<v
37
44
  return;
38
45
  }
39
46
 
40
- const { parseCommonFlags } = await import("./args.js");
47
+ const { parseCommonFlags, resumeIdOf } = await import("./args.js");
41
48
  let parsed: ReturnType<typeof parseCommonFlags>;
42
49
  try {
43
50
  parsed = parseCommonFlags(rawArgs);
@@ -57,23 +64,17 @@ export const session = async (harnessName: string, rawArgs: string[]): Promise<v
57
64
  }
58
65
 
59
66
  const values = parsed.values as Record<string, unknown>;
60
- // --resume and --session-id are aliases for one concept: resume an existing
61
- // session. Passing both is refused, matching hcn run's parseRunExtra shape
62
- // (src/cli/args.ts) rather than inventing a second parser.
63
- if (values.resume !== undefined && values["session-id"] !== undefined) {
64
- const { ArgvRefusalError: AliasError } = await import("../interpretation/refusal.js");
65
- const err = new AliasError({
66
- issue: "mutually-exclusive-options",
67
- harness: h.name,
68
- supported: ["--resume or --session-id, not both (--session-id is an alias for --resume)"],
69
- detail: "both --resume and --session-id given",
70
- });
71
- const { refusalOf: aliasRefusalOf, refuse: aliasRefuse } = await import("./refuse.js");
72
- aliasRefuse(aliasRefusalOf(err), jsonMode, "closed");
67
+ // --resume and --session-id are aliases for one concept; the one check
68
+ // every command calls decides whether both were given.
69
+ let resumeId: string | undefined;
70
+ try {
71
+ resumeId = resumeIdOf(values);
72
+ } catch (err) {
73
+ if (!(err instanceof ArgvRefusalError)) throw err;
74
+ const { refusalOf, refuse } = await import("./refuse.js");
75
+ refuse(refusalOf(err), jsonMode, "closed");
73
76
  return;
74
77
  }
75
- const resumeId =
76
- (values.resume as string | undefined) ?? (values["session-id"] as string | undefined);
77
78
  // The two flags are aliases for "use this session". Whether that means
78
79
  // RESUME is decided by the store, not by which spelling was typed: an id
79
80
  // that exists is resumed (resumeFlag rendered), an id that does not exist
@@ -107,18 +108,18 @@ export const session = async (harnessName: string, rawArgs: string[]): Promise<v
107
108
  const provider = values.provider as string | undefined;
108
109
  const effort = values.effort as string | undefined;
109
110
 
110
- // question mode precedence arg > project > user > default (ask)
111
+ // question mode: the arg is validated through the one predicate, then
112
+ // precedence resolves through the one owner (RFC-02 change 6).
111
113
  const rawArgMode = values.questions !== undefined ? String(values.questions) : undefined;
112
- if (rawArgMode !== undefined && !["ask", "assume", "none"].includes(rawArgMode)) {
114
+ if (rawArgMode !== undefined && !isQuestionMode(rawArgMode)) {
113
115
  const { refuse, refusalOf } = await import("./refuse.js");
114
- const { ArgvRefusalError } = await import("../interpretation/refusal.js");
115
116
  refuse(
116
117
  refusalOf(
117
118
  new ArgvRefusalError({
118
119
  issue: "invalid-option-value",
119
120
  harness: h.name,
120
121
  option: "questions",
121
- supported: ["ask", "assume", "none"],
122
+ supported: [...QUESTION_MODES],
122
123
  detail: rawArgMode,
123
124
  }),
124
125
  ),
@@ -127,27 +128,19 @@ export const session = async (harnessName: string, rawArgs: string[]): Promise<v
127
128
  );
128
129
  return;
129
130
  }
130
- let questionMode: import("../interpretation/question.js").QuestionMode;
131
- let questionTier: "arg" | "project-config" | "user-config" | "default";
131
+ let questionMode: QuestionMode;
132
+ let questionTier: BehaviorTier;
132
133
  try {
133
134
  const { loadUserConfig, loadProjectConfig } = await import("./config.js");
134
- const user = loadUserConfig()?.config as { questions?: string } | undefined;
135
- const project = loadProjectConfig()?.config as { questions?: string } | undefined;
136
- const userMode = user?.questions;
137
- const projectMode = project?.questions;
138
- if (rawArgMode !== undefined) {
139
- questionMode = rawArgMode as import("../interpretation/question.js").QuestionMode;
140
- questionTier = "arg";
141
- } else if (projectMode !== undefined) {
142
- questionMode = projectMode as import("../interpretation/question.js").QuestionMode;
143
- questionTier = "project-config";
144
- } else if (userMode !== undefined) {
145
- questionMode = userMode as import("../interpretation/question.js").QuestionMode;
146
- questionTier = "user-config";
147
- } else {
148
- questionMode = "ask";
149
- questionTier = "default";
150
- }
135
+ const { resolveBehavior } = await import("../interpretation/resolve-options.js");
136
+ const behavior = resolveBehavior(
137
+ {
138
+ questions: rawArgMode !== undefined && isQuestionMode(rawArgMode) ? rawArgMode : undefined,
139
+ },
140
+ { user: loadUserConfig()?.config, project: loadProjectConfig()?.config },
141
+ );
142
+ questionMode = behavior.questions.value;
143
+ questionTier = behavior.questions.tier;
151
144
  } catch (configErr) {
152
145
  process.stderr.write(`config error: ${(configErr as Error).message}\n`);
153
146
  if (jsonMode) {
@@ -382,10 +375,7 @@ export const session = async (harnessName: string, rawArgs: string[]): Promise<v
382
375
  answer = a;
383
376
  }
384
377
  }
385
- handle.send({
386
- id: `you-${++sendCount}`,
387
- text: `The user answered the question: "${q.question}" with: ${answer}. Continue accordingly.`,
388
- });
378
+ handle.send({ id: `you-${++sendCount}`, text: composeAnswer(q.question, answer) });
389
379
  // Drain the answer turn BEFORE prompting again - the pump's
390
380
  // backpressure stalls the harness until the turn iterable is
391
381
  // consumed (verified live: menu answered, you-prompt rendered, no
@@ -31,10 +31,7 @@ export const listKnownSkills = (): string[] => {
31
31
  });
32
32
  };
33
33
 
34
- export const resolveSkillNames = (
35
- names: readonly string[],
36
- harness: HarnessName = "claude",
37
- ): string[] => {
34
+ export const resolveSkillNames = (names: readonly string[], harness: HarnessName): string[] => {
38
35
  const root = skillsRoot();
39
36
  const known = new Set(listKnownSkills());
40
37
  const unknown = names.filter((n) => !known.has(n));
@@ -10,7 +10,7 @@ import { decodeIdentity } from "../interpretation/identity.js";
10
10
  import { detectLimitInLine } from "../interpretation/limits.js";
11
11
  import type { HarnessDescriptor, StreamingGranularity } from "../knowledge/descriptor.js";
12
12
  import type { HarnessEvent } from "./events.js";
13
- import { failureFromBudget } from "./failure.js";
13
+ import { failureFromBudget, failureFromLimit } from "./failure.js";
14
14
 
15
15
  export interface DecodeState {
16
16
  lastSeenId: string | null;
@@ -99,47 +99,21 @@ export const decodeParsed = (
99
99
  }
100
100
  }
101
101
 
102
- // Content (message/token/tool/error/budget) is per-harness; identity
103
- // above is descriptor-driven. contentEventsOf dispatches by harness
104
- // name. budget is not a HarnessEvent kind and must not leak out.
102
+ // Content (message/token/tool/error/budget/limit) is per-harness;
103
+ // identity above is descriptor-driven. contentEventsOf dispatches by
104
+ // harness name inside interpretation. budget and limit are not
105
+ // HarnessEvent kinds of their own here: both become failures.
105
106
  for (const content of contentEventsOf(h.name, raw)) {
106
107
  if (content.kind === "budget") {
107
108
  events.push({ kind: "failure", ...failureFromBudget(content.detail) });
109
+ } else if (content.kind === "limit") {
110
+ events.push({
111
+ kind: "failure",
112
+ ...failureFromLimit(content.code, content.detail, content.resetsAt),
113
+ });
108
114
  } else {
109
115
  events.push(content);
110
116
  }
111
117
  }
112
-
113
- // claude's rate_limit_event: only non-"allowed" statuses are failures.
114
- // overageStatus is deliberately not classified - it is a separate
115
- // billing signal, not a rate limit.
116
- if (h.name === "claude" && typeof (raw as Record<string, unknown>).type === "string") {
117
- const rec = raw as Record<string, unknown>;
118
- if (rec.type === "rate_limit_event") {
119
- const info = rec.rate_limit_info as Record<string, unknown> | undefined;
120
- const status = info?.status as string | undefined;
121
- if (status !== undefined && status !== "allowed") {
122
- const rawResets = info?.resetsAt;
123
- let resetsAt: number | undefined;
124
- if (typeof rawResets === "number" && Number.isFinite(rawResets) && rawResets > 0) {
125
- // Arithmetic conversion: resetsAt is seconds, we need milliseconds. No wall clock read.
126
- resetsAt = rawResets * 1000;
127
- if (!Number.isFinite(resetsAt) || resetsAt <= 0) resetsAt = undefined;
128
- }
129
- // Push a failure event directly - this is a structured record, not a wall scan
130
- const failure: import("./failure.js").FailureSummary = {
131
- class: "rate-limit",
132
- retryable: true,
133
- message: `Rate limit hit (rate_limit_event status=${status}) - retry after backoff or route to another provider`,
134
- code: "rate-limit" as const,
135
- ...(resetsAt !== undefined ? { resetsAt } : {}),
136
- };
137
- events.push({
138
- kind: "failure",
139
- ...failure,
140
- } as unknown as import("./events.js").HarnessEvent);
141
- }
142
- }
143
- }
144
118
  return events;
145
119
  };
@@ -15,6 +15,7 @@
15
15
  * concatenate both.
16
16
  */
17
17
  import type { CapabilityResult } from "../interpretation/capabilities.js";
18
+ import type { QuestionMode } from "../interpretation/question.js";
18
19
  import type { LimitCode } from "../knowledge/descriptor.js";
19
20
  import type { FailureSummary } from "./failure.js";
20
21
 
@@ -29,7 +30,9 @@ export type ExitCause =
29
30
  * SUCCESSFUL turn (process exit 0); the caller resumes with the answer. */
30
31
  | "awaiting-input";
31
32
 
32
- export type EscalationMode = "ask" | "assume" | "none";
33
+ /** The question mode the turn ran under - the one vocabulary question.ts
34
+ * owns, recorded on done per RFC-01. */
35
+ export type EscalationMode = QuestionMode;
33
36
  export type EscalationDetection = "block" | "malformed" | "none";
34
37
  export interface EscalationRecord {
35
38
  readonly mode: EscalationMode;
@@ -103,7 +103,7 @@ const messageFor = (cls: FailureClass, detail?: string): string => {
103
103
  }; /** D11: the run outlived its caller-set wall-clock budget. */
104
104
  export const failureFromTimeout = (): FailureSummary => ({
105
105
  class: "timeout",
106
- retryable: false,
106
+ retryable: retryableOf("timeout"),
107
107
  message: messageFor("timeout"),
108
108
  });
109
109
 
@@ -114,7 +114,7 @@ export const failureFromNative = (
114
114
  stderrTail: readonly string[],
115
115
  ): FailureSummary => ({
116
116
  class: "native",
117
- retryable: false,
117
+ retryable: retryableOf("native"),
118
118
  message: messageFor(
119
119
  "native",
120
120
  stderrTail.slice(-3).join(" | ").slice(0, 512) || `exit ${nativeExitCode}`,
@@ -122,7 +122,13 @@ export const failureFromNative = (
122
122
  nativeExitCode: nativeExitCode ?? undefined,
123
123
  });
124
124
 
125
- export const failureFromLimit = (code: LimitCode): FailureSummary => {
125
+ /** A limit, from a wall phrasing (detail defaults to the code) or from a
126
+ * structured record that names its status and, sometimes, a reset time. */
127
+ export const failureFromLimit = (
128
+ code: LimitCode,
129
+ detail: string = code,
130
+ resetsAt?: number,
131
+ ): FailureSummary => {
126
132
  const cls: FailureClass =
127
133
  code === "rate-limit"
128
134
  ? "rate-limit"
@@ -132,14 +138,15 @@ export const failureFromLimit = (code: LimitCode): FailureSummary => {
132
138
  return {
133
139
  class: cls,
134
140
  retryable: retryableOf(cls),
135
- message: messageFor(cls, code),
136
- code: code as LimitCode,
141
+ message: messageFor(cls, detail),
142
+ code,
143
+ ...(resetsAt !== undefined ? { resetsAt } : {}),
137
144
  };
138
145
  };
139
146
 
140
147
  export const failureFromAuth = (kind: AuthFailureKind): FailureSummary => ({
141
148
  class: "auth",
142
- retryable: true,
149
+ retryable: retryableOf("auth"),
143
150
  message: messageFor("auth", kind),
144
151
  authKind: kind,
145
152
  });
@@ -157,25 +164,25 @@ export const failureFromTerminalError = (h: HarnessDescriptor, message: string):
157
164
 
158
165
  export const failureFromTask = (detail?: string): FailureSummary => ({
159
166
  class: "task",
160
- retryable: false,
167
+ retryable: retryableOf("task"),
161
168
  message: messageFor("task", detail),
162
169
  });
163
170
 
164
171
  export const failureFromBudget = (detail?: string): FailureSummary => ({
165
172
  class: "budget",
166
- retryable: false,
173
+ retryable: retryableOf("budget"),
167
174
  message: messageFor("budget", detail),
168
175
  });
169
176
 
170
177
  export const failureFromTransport = (detail?: string): FailureSummary => ({
171
178
  class: "transport",
172
- retryable: true,
179
+ retryable: retryableOf("transport"),
173
180
  message: messageFor("transport", detail),
174
181
  });
175
182
 
176
183
  export const failureFromUnavailable = (detail?: string): FailureSummary => ({
177
184
  class: "unavailable",
178
- retryable: true,
185
+ retryable: retryableOf("unavailable"),
179
186
  message: messageFor("unavailable", detail),
180
187
  });
181
188
 
@@ -189,7 +196,7 @@ export const failureFromRejected = (opts: {
189
196
  detail?: string;
190
197
  }): FailureSummary => ({
191
198
  class: "rejected",
192
- retryable: false,
199
+ retryable: retryableOf("rejected"),
193
200
  // D8: hint first, support list second - prose order matches the
194
201
  // structured fields so an agent scanning the message hits the
195
202
  // stay-on-harness suggestion before the switch temptation.