@gaunt-sloth/core 2.0.0-beta.3 → 2.0.0-beta.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/dist/core/GthAbstractAgent.d.ts +66 -0
- package/dist/core/GthAbstractAgent.js +185 -1
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +82 -1
- package/dist/core/GthAgentRunner.js +265 -8
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.d.ts +3 -2
- package/dist/core/GthLangChainAgent.js +24 -4
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/refusal.d.ts +54 -1
- package/dist/core/refusal.js +109 -1
- package/dist/core/refusal.js.map +1 -1
- package/dist/core/terminationNotice.d.ts +111 -0
- package/dist/core/terminationNotice.js +209 -0
- package/dist/core/terminationNotice.js.map +1 -0
- package/dist/core/terminationReason.d.ts +271 -0
- package/dist/core/terminationReason.js +405 -0
- package/dist/core/terminationReason.js.map +1 -0
- package/dist/core/types.d.ts +27 -0
- package/dist/core/types.js.map +1 -1
- package/dist/runtime/conversation.d.ts +11 -0
- package/dist/runtime/conversation.js +13 -1
- package/dist/runtime/conversation.js.map +1 -1
- package/dist/runtime/singleShot.d.ts +11 -0
- package/dist/runtime/singleShot.js +21 -1
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/utils/debugDump.d.ts +54 -0
- package/dist/utils/debugDump.js +32 -0
- package/dist/utils/debugDump.js.map +1 -1
- package/package.json +1 -1
package/dist/core/refusal.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Stop/finish-reason detection for the agent run loop — the **metadata feeder** of the
|
|
3
|
+
* [[EXT-159]] termination taxonomy.
|
|
4
|
+
*
|
|
5
|
+
* This module is the one reader of a message's stop/finish reason. It normalizes the per-provider
|
|
6
|
+
* spellings into two shapes: {@link detectRefusal}, whose {@link RefusalInfo} also drives the
|
|
7
|
+
* user-facing notice, and {@link detectStopMetadata}, which classifies the same metadata into the
|
|
8
|
+
* shared taxonomy for every consumer that only needs to know *why the turn ended*. There is
|
|
9
|
+
* deliberately no second reader of `response_metadata` beside it.
|
|
10
|
+
*
|
|
11
|
+
* Its counterpart is the exception classifier in `core/terminationReason.ts`: a reason that arrives
|
|
12
|
+
* as a thrown error is not in `response_metadata` at all, so nothing here can see it.
|
|
3
13
|
*
|
|
4
14
|
* A *successful* model response (HTTP 200) can carry a stop/finish reason that means the model — or
|
|
5
15
|
* the provider's safety system — declined to answer. The content is usually empty, so without this
|
|
@@ -112,6 +122,104 @@ export function detectRefusal(message) {
|
|
|
112
122
|
}
|
|
113
123
|
return null;
|
|
114
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* The provider spellings of "the answer hit the output cap", lower-cased.
|
|
127
|
+
*
|
|
128
|
+
* The same four places {@link detectRefusal} reads carry this one too, spelled per family: OpenAI's
|
|
129
|
+
* `finish_reason: 'length'`, Anthropic's and Bedrock's `stop_reason: 'max_tokens'`, Gemini's
|
|
130
|
+
* `finishReason: 'MAX_TOKENS'`, Ollama's `done_reason: 'length'`. LangChain normalizes none of it.
|
|
131
|
+
*
|
|
132
|
+
* Unlike a refusal, a truncation carries **no provider**: the token does not identify one. Both
|
|
133
|
+
* Anthropic and Gemini spell it `max_tokens` once case is normalised, and both OpenAI and Ollama
|
|
134
|
+
* spell it `length` — so naming a family from the token would be a guess stated as a fact. The raw
|
|
135
|
+
* token goes in `detail`, which is what is actually known.
|
|
136
|
+
*/
|
|
137
|
+
const TRUNCATION_REASONS = new Set([
|
|
138
|
+
'length',
|
|
139
|
+
'max_tokens',
|
|
140
|
+
'maxtokens',
|
|
141
|
+
'model_length',
|
|
142
|
+
]);
|
|
143
|
+
/**
|
|
144
|
+
* Every stop/finish reason token a message carries, lower-cased, from both `response_metadata` and
|
|
145
|
+
* `additional_kwargs` and in both snake and camel case — the same four places {@link detectRefusal}
|
|
146
|
+
* looks, so the two detections can never come to disagree about where the metadata lives.
|
|
147
|
+
*/
|
|
148
|
+
function stopReasonTokens(message) {
|
|
149
|
+
const meta = readField(message, 'response_metadata');
|
|
150
|
+
const kwargs = readField(message, 'additional_kwargs');
|
|
151
|
+
const tokens = [];
|
|
152
|
+
for (const key of ['finish_reason', 'finishReason', 'stop_reason', 'stopReason', 'done_reason']) {
|
|
153
|
+
for (const source of [meta, kwargs, message]) {
|
|
154
|
+
const value = readField(source, key);
|
|
155
|
+
if (typeof value === 'string' && value.length > 0)
|
|
156
|
+
tokens.push(value.toLowerCase());
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return tokens;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* [[EXT-159]] — the provider's own stop/finish reason for a finished message, or `null` when the
|
|
163
|
+
* message carried none.
|
|
164
|
+
*
|
|
165
|
+
* **`null` is a recorded fact, not a missing one.** No `finish_reason` was written to any log
|
|
166
|
+
* anywhere, so a turn that ended without the provider saying why was indistinguishable from one
|
|
167
|
+
* that ended normally. A caller records the `null` as explicitly as it records a token.
|
|
168
|
+
*
|
|
169
|
+
* Built on the SAME reader {@link detectStopMetadata} uses rather than a second one beside it: a
|
|
170
|
+
* separate scan of `response_metadata` is how two readers come to disagree about what the provider
|
|
171
|
+
* said, and the whole point of this node is that the two halves of the classification share one
|
|
172
|
+
* source. The first token wins, in the key order that shared reader walks, and it is lower-cased
|
|
173
|
+
* there — this is diagnostic detail, never the carrier of a classification.
|
|
174
|
+
*/
|
|
175
|
+
export function readStopReasonToken(message) {
|
|
176
|
+
if (!message || typeof message !== 'object')
|
|
177
|
+
return null;
|
|
178
|
+
return stopReasonTokens(message)[0] ?? null;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Inspect a finished model message and return an output-truncation classification when its
|
|
182
|
+
* stop/finish reason says the answer hit the output cap, else `null`.
|
|
183
|
+
*
|
|
184
|
+
* A truncated answer is not a refused one and not an error: the turn returns a *successful*
|
|
185
|
+
* response that simply stops mid-thought. Nothing is surfaced from here — the classification exists
|
|
186
|
+
* so a turn that ended this way is not indistinguishable from one the model finished.
|
|
187
|
+
*/
|
|
188
|
+
export function detectOutputTruncation(message) {
|
|
189
|
+
if (!message || typeof message !== 'object')
|
|
190
|
+
return null;
|
|
191
|
+
for (const token of stopReasonTokens(message)) {
|
|
192
|
+
if (TRUNCATION_REASONS.has(token)) {
|
|
193
|
+
return { category: 'output_truncated', detail: token };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* The metadata feeder, as one call: classify a finished model message's stop/finish reason into the
|
|
200
|
+
* [[EXT-159]] taxonomy, or `null` when it says nothing worth recording.
|
|
201
|
+
*
|
|
202
|
+
* `null` for an ordinary end is deliberate. Only a *terminal and interesting* reason is reported —
|
|
203
|
+
* a refusal or a truncation — because every other end of a turn is classified by the site that
|
|
204
|
+
* actually ends it, and a reader that reported `completed` off a mid-turn tool round would pin the
|
|
205
|
+
* wrong reason before the real one happened.
|
|
206
|
+
*/
|
|
207
|
+
export function detectStopMetadata(message) {
|
|
208
|
+
const refusal = detectRefusal(message);
|
|
209
|
+
if (refusal)
|
|
210
|
+
return classifyRefusal(refusal);
|
|
211
|
+
return detectOutputTruncation(message);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The taxonomy classification a {@link RefusalInfo} stands for.
|
|
215
|
+
*
|
|
216
|
+
* Exported so a call site that already holds the detected {@link RefusalInfo} — because it needs
|
|
217
|
+
* the explanation text for the user-facing notice — records the same classification
|
|
218
|
+
* {@link detectStopMetadata} would, rather than re-deriving the mapping beside it.
|
|
219
|
+
*/
|
|
220
|
+
export function classifyRefusal(info) {
|
|
221
|
+
return { category: 'content_refusal', provider: info.provider, detail: info.reason };
|
|
222
|
+
}
|
|
115
223
|
/**
|
|
116
224
|
* Build the clear, user-facing message shown when the model declines. Framed as the model /
|
|
117
225
|
* provider's own policy decision (not a Gaunt Sloth fault) and stated as terminal — a refusal is
|
package/dist/core/refusal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refusal.js","sourceRoot":"","sources":["../../src/core/refusal.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"refusal.js","sourceRoot":"","sources":["../../src/core/refusal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* [[EXT-159]] — getting the typed termination reason to the person looking at the screen.
|
|
4
|
+
*
|
|
5
|
+
* `core/terminationReason.ts` decides *what* ended a run. This module decides *how that fact is
|
|
6
|
+
* said*, once, for every surface — the Ink TUI, the readline session, the non-interactive verbs,
|
|
7
|
+
* ACP and AG-UI — so a run states why it ended in the same vocabulary wherever it is being watched.
|
|
8
|
+
*
|
|
9
|
+
* **Recording it better is not the deliverable.** Most people never make a debug dump; the ones
|
|
10
|
+
* most likely to hit a silent stop have already quit, taking the live session state with them; and
|
|
11
|
+
* reading a dump is a skill that fails even for a maintainer holding one. So the reason has to
|
|
12
|
+
* reach the SESSION, at the moment it happens, on the surface in front of the user.
|
|
13
|
+
*
|
|
14
|
+
* **The prose is never the carrier.** Every surface commits the {@link GthTerminationReason} value
|
|
15
|
+
* itself and derives the words from it here, so a test — and a consumer — reads the classification
|
|
16
|
+
* structurally instead of matching a sentence. That is also why {@link terminationCode} exists: a
|
|
17
|
+
* user quoting one short token in a bug report hands a maintainer the fact that makes the report
|
|
18
|
+
* tractable, which is precisely what "the agent just stopped" could not.
|
|
19
|
+
*
|
|
20
|
+
* **An ABSENT reason is a signal, not a blank to fill.** The taxonomy's contract is that a turn
|
|
21
|
+
* with no reason is a site nobody classified, which is why an ordinary completion is recorded
|
|
22
|
+
* rather than left empty. Nothing here invents a category, a placeholder or a default for `null`:
|
|
23
|
+
* {@link terminationLogLine} states the absence as an absence, and
|
|
24
|
+
* {@link displayTermination} says nothing to the user, because "we did not classify this" is a
|
|
25
|
+
* defect report aimed at us and not an explanation aimed at them.
|
|
26
|
+
*/
|
|
27
|
+
import type { GthTerminationReason } from '#src/core/terminationReason.js';
|
|
28
|
+
/**
|
|
29
|
+
* The opening of every termination notice title.
|
|
30
|
+
*
|
|
31
|
+
* Exported because two surfaces put the notice into a channel that also carries other messages —
|
|
32
|
+
* ACP's `agent_message`, where the agent speaks about the session — so something has to be able to
|
|
33
|
+
* tell one from the other without transcribing the wording. A consumer keying on this constant
|
|
34
|
+
* moves with the copy; one keying on a copy of the string does not, and finds out when the two have
|
|
35
|
+
* silently disagreed for a while.
|
|
36
|
+
*/
|
|
37
|
+
export declare const TERMINATION_NOTICE_TITLE_PREFIX = "Run ended: ";
|
|
38
|
+
/** A termination reason, rendered for a surface that shows a title and body lines. */
|
|
39
|
+
export interface GthTerminationNotice {
|
|
40
|
+
/**
|
|
41
|
+
* **The carrier.** The classification travels as this value; the strings below are derived from
|
|
42
|
+
* it and are never the only place it exists.
|
|
43
|
+
*/
|
|
44
|
+
reason: GthTerminationReason;
|
|
45
|
+
/** The one line that says what happened. */
|
|
46
|
+
title: string;
|
|
47
|
+
/** The supporting lines: the quotable code, what was seen, and what may help. */
|
|
48
|
+
lines: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The short token a user can quote and a maintainer can act on: the category and the site that
|
|
52
|
+
* classified it.
|
|
53
|
+
*
|
|
54
|
+
* Both halves, always. Several sites share a category and several categories reach one site, so
|
|
55
|
+
* either alone loses the fact that separates two failures which look identical on screen — the
|
|
56
|
+
* standing example being an `empty_response` whose as-is retry has already been spent at
|
|
57
|
+
* `runner.empty-after-fallback` and one where it has not been spent at all.
|
|
58
|
+
*/
|
|
59
|
+
export declare function terminationCode(reason: GthTerminationReason): string;
|
|
60
|
+
/**
|
|
61
|
+
* Is this reason worth telling the person watching?
|
|
62
|
+
*
|
|
63
|
+
* Two categories are not. `completed` is the ordinary end of a turn, and announcing it would put a
|
|
64
|
+
* line under every successful answer. `suspended` is a run that has PAUSED rather than ended — the
|
|
65
|
+
* graph is parked on a tool-approval interrupt and about to continue — so announcing it would
|
|
66
|
+
* report the middle of a working turn as its end.
|
|
67
|
+
*
|
|
68
|
+
* Everything else is announced, deliberately including the ones that look self-explanatory.
|
|
69
|
+
* `cancelled` is announced because a turn cancelled by a stray escape sequence is exactly the shape
|
|
70
|
+
* that gets misattributed to the provider for months ([[TUI-C62]]), and a user who did not knowingly
|
|
71
|
+
* press anything is owed the fact that a cancellation is what happened. `approval_stop` is announced
|
|
72
|
+
* because the gate's own prose explains the DECISION while this states the CLASSIFICATION, which is
|
|
73
|
+
* the half a bug report needs.
|
|
74
|
+
*
|
|
75
|
+
* Announcing is a separate decision from recording: everything is recorded, including the
|
|
76
|
+
* categories this returns `false` for.
|
|
77
|
+
*/
|
|
78
|
+
export declare function shouldAnnounceTermination(reason: GthTerminationReason): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Render a reason for a surface that shows a title and body lines.
|
|
81
|
+
*
|
|
82
|
+
* **The `??` on the label lookup is a runtime floor, and it takes nothing away from the build-time
|
|
83
|
+
* one.** That a 23rd category fails the build comes from the category-label table being a `Record`
|
|
84
|
+
* over the whole union, which this expression cannot weaken. What it covers is the case the type
|
|
85
|
+
* system never saw: a reason that arrived from outside it — read back off an ACP `_meta`, handed
|
|
86
|
+
* over by an embedder, or revived from a dump — whose category is a string the table has no entry
|
|
87
|
+
* for. Without the fallback that renders as the literal word `undefined` in the one line the user
|
|
88
|
+
* is being shown, which is a worse answer than "the cause was not recognised" and is exactly the
|
|
89
|
+
* kind of second failure this module refuses to add to a first one.
|
|
90
|
+
*/
|
|
91
|
+
export declare function terminationNotice(reason: GthTerminationReason): GthTerminationNotice;
|
|
92
|
+
/**
|
|
93
|
+
* One line for the debug log, stating either the whole classification or the absence of one.
|
|
94
|
+
*
|
|
95
|
+
* `null` is the case this function exists for. A turn that ended with nothing classifying it is the
|
|
96
|
+
* taxonomy's own defect signal, and it has to be written down as that — never as `completed`, never
|
|
97
|
+
* as `unknown`, and never omitted, because a missing line is indistinguishable from a session where
|
|
98
|
+
* this was never reached.
|
|
99
|
+
*/
|
|
100
|
+
export declare function terminationLogLine(reason: GthTerminationReason | null): string;
|
|
101
|
+
/**
|
|
102
|
+
* Say why the run ended on a console surface (the readline session and the non-interactive verbs),
|
|
103
|
+
* and write the same fact to the debug log either way.
|
|
104
|
+
*
|
|
105
|
+
* Returns whether anything was shown, so a caller can tell "said nothing because the turn simply
|
|
106
|
+
* finished" from "said nothing because nothing classified it" without re-deriving the rule.
|
|
107
|
+
*
|
|
108
|
+
* Fail-soft in the strongest sense: explaining a failure must never become a second failure, so
|
|
109
|
+
* every path here is wrapped and a throw is swallowed.
|
|
110
|
+
*/
|
|
111
|
+
export declare function displayTermination(reason: GthTerminationReason | null): boolean;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* [[EXT-159]] — getting the typed termination reason to the person looking at the screen.
|
|
4
|
+
*
|
|
5
|
+
* `core/terminationReason.ts` decides *what* ended a run. This module decides *how that fact is
|
|
6
|
+
* said*, once, for every surface — the Ink TUI, the readline session, the non-interactive verbs,
|
|
7
|
+
* ACP and AG-UI — so a run states why it ended in the same vocabulary wherever it is being watched.
|
|
8
|
+
*
|
|
9
|
+
* **Recording it better is not the deliverable.** Most people never make a debug dump; the ones
|
|
10
|
+
* most likely to hit a silent stop have already quit, taking the live session state with them; and
|
|
11
|
+
* reading a dump is a skill that fails even for a maintainer holding one. So the reason has to
|
|
12
|
+
* reach the SESSION, at the moment it happens, on the surface in front of the user.
|
|
13
|
+
*
|
|
14
|
+
* **The prose is never the carrier.** Every surface commits the {@link GthTerminationReason} value
|
|
15
|
+
* itself and derives the words from it here, so a test — and a consumer — reads the classification
|
|
16
|
+
* structurally instead of matching a sentence. That is also why {@link terminationCode} exists: a
|
|
17
|
+
* user quoting one short token in a bug report hands a maintainer the fact that makes the report
|
|
18
|
+
* tractable, which is precisely what "the agent just stopped" could not.
|
|
19
|
+
*
|
|
20
|
+
* **An ABSENT reason is a signal, not a blank to fill.** The taxonomy's contract is that a turn
|
|
21
|
+
* with no reason is a site nobody classified, which is why an ordinary completion is recorded
|
|
22
|
+
* rather than left empty. Nothing here invents a category, a placeholder or a default for `null`:
|
|
23
|
+
* {@link terminationLogLine} states the absence as an absence, and
|
|
24
|
+
* {@link displayTermination} says nothing to the user, because "we did not classify this" is a
|
|
25
|
+
* defect report aimed at us and not an explanation aimed at them.
|
|
26
|
+
*/
|
|
27
|
+
import { display, displayWarning } from '#src/utils/consoleUtils.js';
|
|
28
|
+
import { debugLog } from '#src/utils/debugUtils.js';
|
|
29
|
+
/**
|
|
30
|
+
* What each category is called in a sentence a user reads.
|
|
31
|
+
*
|
|
32
|
+
* An exhaustive `Record` rather than a `switch` with a default: a 23rd category is then a build
|
|
33
|
+
* failure here instead of a run that silently reports the new cause in the old words.
|
|
34
|
+
*/
|
|
35
|
+
const CATEGORY_LABEL = {
|
|
36
|
+
completed: 'the model finished',
|
|
37
|
+
empty_response: 'the model returned nothing',
|
|
38
|
+
content_refusal: 'the model declined to answer',
|
|
39
|
+
output_truncated: 'the answer hit the output limit',
|
|
40
|
+
context_overflow: 'the conversation outgrew the model input window',
|
|
41
|
+
rate_limited: 'the provider rate-limited the request',
|
|
42
|
+
auth_failed: 'the provider rejected the credentials',
|
|
43
|
+
invalid_request: 'the provider rejected the request',
|
|
44
|
+
provider_error: 'the provider failed',
|
|
45
|
+
network_error: 'the connection failed',
|
|
46
|
+
timeout: 'a deadline elapsed',
|
|
47
|
+
cancelled: 'it was cancelled',
|
|
48
|
+
approval_stop: 'the approvals gate stopped it',
|
|
49
|
+
tool_error_budget: 'the tool-error budget ended the run',
|
|
50
|
+
tool_loop_guard: 'the tool-loop guard ended a repeating call',
|
|
51
|
+
interrupt_drain_guard: 'too many tool approvals in one turn',
|
|
52
|
+
tool_error: 'a tool failed',
|
|
53
|
+
suspended: 'it is waiting to be resumed',
|
|
54
|
+
recursion_limit: 'it hit the graph step limit',
|
|
55
|
+
abandoned: 'the client stopped listening',
|
|
56
|
+
unknown: 'the cause was not recognised',
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* What each remedy asks the user to actually do.
|
|
60
|
+
*
|
|
61
|
+
* Exhaustive for the same reason {@link CATEGORY_LABEL} is: the posture table names a remedy, and a
|
|
62
|
+
* remedy nobody worded is a retry hint the user never gets.
|
|
63
|
+
*/
|
|
64
|
+
const REMEDY_LABEL = {
|
|
65
|
+
'reduce-context': 'Send less — clear or compact the conversation, then try again.',
|
|
66
|
+
'back-off': 'Wait a moment, then send the same request again.',
|
|
67
|
+
'change-request': 'Try a different or narrower request.',
|
|
68
|
+
'change-model': 'Try a different model.',
|
|
69
|
+
'fix-credentials': 'Check the API credentials or configuration, then try again.',
|
|
70
|
+
resume: 'Nothing is wrong — the run is parked and can be continued where it stopped.',
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The opening of every termination notice title.
|
|
74
|
+
*
|
|
75
|
+
* Exported because two surfaces put the notice into a channel that also carries other messages —
|
|
76
|
+
* ACP's `agent_message`, where the agent speaks about the session — so something has to be able to
|
|
77
|
+
* tell one from the other without transcribing the wording. A consumer keying on this constant
|
|
78
|
+
* moves with the copy; one keying on a copy of the string does not, and finds out when the two have
|
|
79
|
+
* silently disagreed for a while.
|
|
80
|
+
*/
|
|
81
|
+
export const TERMINATION_NOTICE_TITLE_PREFIX = 'Run ended: ';
|
|
82
|
+
/**
|
|
83
|
+
* The short token a user can quote and a maintainer can act on: the category and the site that
|
|
84
|
+
* classified it.
|
|
85
|
+
*
|
|
86
|
+
* Both halves, always. Several sites share a category and several categories reach one site, so
|
|
87
|
+
* either alone loses the fact that separates two failures which look identical on screen — the
|
|
88
|
+
* standing example being an `empty_response` whose as-is retry has already been spent at
|
|
89
|
+
* `runner.empty-after-fallback` and one where it has not been spent at all.
|
|
90
|
+
*/
|
|
91
|
+
export function terminationCode(reason) {
|
|
92
|
+
return `${reason.category}@${reason.site}`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Is this reason worth telling the person watching?
|
|
96
|
+
*
|
|
97
|
+
* Two categories are not. `completed` is the ordinary end of a turn, and announcing it would put a
|
|
98
|
+
* line under every successful answer. `suspended` is a run that has PAUSED rather than ended — the
|
|
99
|
+
* graph is parked on a tool-approval interrupt and about to continue — so announcing it would
|
|
100
|
+
* report the middle of a working turn as its end.
|
|
101
|
+
*
|
|
102
|
+
* Everything else is announced, deliberately including the ones that look self-explanatory.
|
|
103
|
+
* `cancelled` is announced because a turn cancelled by a stray escape sequence is exactly the shape
|
|
104
|
+
* that gets misattributed to the provider for months ([[TUI-C62]]), and a user who did not knowingly
|
|
105
|
+
* press anything is owed the fact that a cancellation is what happened. `approval_stop` is announced
|
|
106
|
+
* because the gate's own prose explains the DECISION while this states the CLASSIFICATION, which is
|
|
107
|
+
* the half a bug report needs.
|
|
108
|
+
*
|
|
109
|
+
* Announcing is a separate decision from recording: everything is recorded, including the
|
|
110
|
+
* categories this returns `false` for.
|
|
111
|
+
*/
|
|
112
|
+
export function shouldAnnounceTermination(reason) {
|
|
113
|
+
return reason.category !== 'completed' && reason.category !== 'suspended';
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Render a reason for a surface that shows a title and body lines.
|
|
117
|
+
*
|
|
118
|
+
* **The `??` on the label lookup is a runtime floor, and it takes nothing away from the build-time
|
|
119
|
+
* one.** That a 23rd category fails the build comes from the category-label table being a `Record`
|
|
120
|
+
* over the whole union, which this expression cannot weaken. What it covers is the case the type
|
|
121
|
+
* system never saw: a reason that arrived from outside it — read back off an ACP `_meta`, handed
|
|
122
|
+
* over by an embedder, or revived from a dump — whose category is a string the table has no entry
|
|
123
|
+
* for. Without the fallback that renders as the literal word `undefined` in the one line the user
|
|
124
|
+
* is being shown, which is a worse answer than "the cause was not recognised" and is exactly the
|
|
125
|
+
* kind of second failure this module refuses to add to a first one.
|
|
126
|
+
*/
|
|
127
|
+
export function terminationNotice(reason) {
|
|
128
|
+
const lines = [`Reason code: ${terminationCode(reason)}`];
|
|
129
|
+
const seen = [];
|
|
130
|
+
if (reason.provider)
|
|
131
|
+
seen.push(`provider ${reason.provider}`);
|
|
132
|
+
if (reason.detail)
|
|
133
|
+
seen.push(`reported ${reason.detail}`);
|
|
134
|
+
if (seen.length > 0)
|
|
135
|
+
lines.push(`What was seen: ${seen.join(', ')}.`);
|
|
136
|
+
lines.push(retryAdvice(reason));
|
|
137
|
+
return {
|
|
138
|
+
reason,
|
|
139
|
+
title: `${TERMINATION_NOTICE_TITLE_PREFIX}${CATEGORY_LABEL[reason.category] ?? CATEGORY_LABEL.unknown}`,
|
|
140
|
+
lines,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The retry advice for a reason, read off the posture the taxonomy already decided.
|
|
145
|
+
*
|
|
146
|
+
* Read from the reason's own posture fields rather than re-deriving one from the category, so this
|
|
147
|
+
* cannot come to disagree with the single posture table the taxonomy exists to keep.
|
|
148
|
+
*/
|
|
149
|
+
function retryAdvice(reason) {
|
|
150
|
+
if (reason.retryableAsIs)
|
|
151
|
+
return 'Sending the same request again may work.';
|
|
152
|
+
if (reason.retryableAfterRemedy && reason.remedy)
|
|
153
|
+
return REMEDY_LABEL[reason.remedy];
|
|
154
|
+
return 'Sending the same request again will not help.';
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* One line for the debug log, stating either the whole classification or the absence of one.
|
|
158
|
+
*
|
|
159
|
+
* `null` is the case this function exists for. A turn that ended with nothing classifying it is the
|
|
160
|
+
* taxonomy's own defect signal, and it has to be written down as that — never as `completed`, never
|
|
161
|
+
* as `unknown`, and never omitted, because a missing line is indistinguishable from a session where
|
|
162
|
+
* this was never reached.
|
|
163
|
+
*/
|
|
164
|
+
export function terminationLogLine(reason) {
|
|
165
|
+
if (!reason) {
|
|
166
|
+
return ('EXT-159 termination: UNCLASSIFIED — no site classified how this turn ended. ' +
|
|
167
|
+
'An absent reason means a termination site we missed, not a turn that went well.');
|
|
168
|
+
}
|
|
169
|
+
const parts = [
|
|
170
|
+
`category=${reason.category}`,
|
|
171
|
+
`site=${reason.site}`,
|
|
172
|
+
`source=${reason.source}`,
|
|
173
|
+
`retryableAsIs=${reason.retryableAsIs}`,
|
|
174
|
+
`retryableAfterRemedy=${reason.retryableAfterRemedy}`,
|
|
175
|
+
];
|
|
176
|
+
if (reason.remedy)
|
|
177
|
+
parts.push(`remedy=${reason.remedy}`);
|
|
178
|
+
if (reason.provider)
|
|
179
|
+
parts.push(`provider=${reason.provider}`);
|
|
180
|
+
if (reason.detail)
|
|
181
|
+
parts.push(`detail=${reason.detail}`);
|
|
182
|
+
return `EXT-159 termination: ${parts.join(' ')}`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Say why the run ended on a console surface (the readline session and the non-interactive verbs),
|
|
186
|
+
* and write the same fact to the debug log either way.
|
|
187
|
+
*
|
|
188
|
+
* Returns whether anything was shown, so a caller can tell "said nothing because the turn simply
|
|
189
|
+
* finished" from "said nothing because nothing classified it" without re-deriving the rule.
|
|
190
|
+
*
|
|
191
|
+
* Fail-soft in the strongest sense: explaining a failure must never become a second failure, so
|
|
192
|
+
* every path here is wrapped and a throw is swallowed.
|
|
193
|
+
*/
|
|
194
|
+
export function displayTermination(reason) {
|
|
195
|
+
try {
|
|
196
|
+
debugLog(terminationLogLine(reason));
|
|
197
|
+
if (!reason || !shouldAnnounceTermination(reason))
|
|
198
|
+
return false;
|
|
199
|
+
const notice = terminationNotice(reason);
|
|
200
|
+
displayWarning(notice.title);
|
|
201
|
+
for (const line of notice.lines)
|
|
202
|
+
display(` ${line}`);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=terminationNotice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminationNotice.js","sourceRoot":"","sources":["../../src/core/terminationNotice.ts"],"names":[],"mappings":"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"}
|