@aragon-agent/cli 0.6.23 → 0.6.25
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/CHANGELOG.md +1835 -1786
- package/README.md +2578 -2544
- package/dist/agent/controller.d.ts +60 -3
- package/dist/agent/controller.js +111 -6
- package/dist/agent/conversation-reset.d.ts +91 -0
- package/dist/agent/conversation-reset.js +66 -0
- package/dist/agent/model-probe-limits.d.ts +39 -0
- package/dist/agent/model-probe-limits.js +25 -0
- package/dist/agent/model-probe.d.ts +104 -0
- package/dist/agent/model-probe.js +391 -0
- package/dist/agent/model-profile-settings.js +29 -2
- package/dist/agent/reducer.js +45 -13
- package/dist/commands/builtins.js +101 -37
- package/dist/commands/clear-command.d.ts +27 -0
- package/dist/commands/clear-command.js +107 -0
- package/dist/commands/registry.d.ts +20 -1
- package/dist/compaction/child.d.ts +15 -0
- package/dist/compaction/child.js +14 -3
- package/dist/compaction/compactor.js +1 -0
- package/dist/compaction/context-command.js +10 -1
- package/dist/compaction/meter.d.ts +9 -0
- package/dist/compaction/meter.js +16 -1
- package/dist/compaction/pressure.d.ts +7 -0
- package/dist/compaction/pressure.js +5 -0
- package/dist/compaction/types.d.ts +11 -0
- package/dist/compaction/wiring.js +1 -0
- package/dist/config/cli-commands.d.ts +1 -1
- package/dist/config/cli-commands.js +12 -0
- package/dist/config/load.js +24 -6
- package/dist/config/model-profile-resolution.d.ts +8 -0
- package/dist/config/model-profile-resolution.js +13 -6
- package/dist/config/model-profile-store.d.ts +4 -2
- package/dist/config/model-profiles.d.ts +21 -0
- package/dist/config/model-profiles.js +28 -2
- package/dist/config/schema.d.ts +61 -0
- package/dist/config/schema.js +36 -0
- package/dist/session/clear-archive.d.ts +51 -0
- package/dist/session/clear-archive.js +160 -0
- package/dist/team/model.d.ts +88 -0
- package/dist/team/model.js +136 -0
- package/dist/team/report.d.ts +11 -0
- package/dist/team/report.js +24 -1
- package/dist/team/subagent.js +37 -21
- package/dist/team/task-tool.d.ts +25 -6
- package/dist/team/task-tool.js +15 -9
- package/dist/todo/types.d.ts +5 -3
- package/dist/ui/App.js +44 -7
- package/dist/ui/entries/CompactionCard.d.ts +5 -4
- package/dist/ui/entries/CompactionCard.js +5 -4
- package/dist/ui/overlays/HelpOverlay.js +8 -2
- package/dist/ui/overlays/ModelDiscoveryList.d.ts +23 -0
- package/dist/ui/overlays/ModelDiscoveryList.js +161 -0
- package/dist/ui/overlays/ModelProfileEditor.d.ts +18 -1
- package/dist/ui/overlays/ModelProfileEditor.js +117 -54
- package/dist/ui/overlays/ModelProfilePicker.d.ts +14 -2
- package/dist/ui/overlays/ModelProfilePicker.js +51 -10
- package/dist/ui/overlays/QuestionOverlay.js +47 -6
- package/dist/ui/overlays/SettingsScreen.d.ts +17 -0
- package/dist/ui/overlays/SettingsScreen.js +152 -18
- package/dist/ui/overlays/model-probe-view.d.ts +128 -0
- package/dist/ui/overlays/model-probe-view.js +225 -0
- package/dist/ui/overlays/model-profile-state.d.ts +62 -5
- package/dist/ui/overlays/model-profile-state.js +121 -14
- package/dist/ui/overlays/use-model-probe.d.ts +30 -0
- package/dist/ui/overlays/use-model-probe.js +109 -0
- package/package.json +2 -2
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `resolveSubagentModel` — THE single authority on which model a team child
|
|
3
|
+
* runs (subagent-model-and-question-controls §3.3).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/team/**` is inside the glyph scanner's scope.
|
|
6
|
+
*
|
|
7
|
+
* EVERY CONSUMER READS THE CHILD'S MODEL THROUGH THIS FUNCTION AND NOWHERE
|
|
8
|
+
* ELSE — `createSubagent`, the `task` report header, the settings root row and
|
|
9
|
+
* `/team model` — following the `resolveFastTier` precedent one directory over:
|
|
10
|
+
* four call sites each recomputing "which model do children run on?" from raw
|
|
11
|
+
* config keys is exactly how the report ends up naming a model the children
|
|
12
|
+
* silently fell back from.
|
|
13
|
+
*
|
|
14
|
+
* IT IS RE-RUN ON EVERY DISPATCH AND EVERY RENDER, NOT ONCE (rule 8 / RV-3 of
|
|
15
|
+
* the fast tier, inherited here): `team.model`, the fast tier and the profile
|
|
16
|
+
* library are all LIVE keys a settings save, `/fast off` or `/team model` can
|
|
17
|
+
* rewrite mid-session. A resolution captured at construction would be a
|
|
18
|
+
* resolution describing a session that no longer exists.
|
|
19
|
+
*
|
|
20
|
+
* A CHILD IS NEVER LEFT WITHOUT A MODEL (D-4). Every failure mode — fast tier
|
|
21
|
+
* off, profile deleted, profile key missing — degrades to the MAIN connection
|
|
22
|
+
* with a `fallbackReason`, so a dispatch cannot fail because its model setting
|
|
23
|
+
* went stale. The reason is disclosed, never swallowed: the report header and
|
|
24
|
+
* the settings summary line both render it.
|
|
25
|
+
*/
|
|
26
|
+
import type { ModelRef, ThinkingLevel } from '@aragon-agent/core';
|
|
27
|
+
import type { ModelProfile, ModelRole } from '../config/model-profiles.js';
|
|
28
|
+
import type { CliConfig } from '../config/schema.js';
|
|
29
|
+
/** Which model children run on. `main` is the default and the historical behavior. */
|
|
30
|
+
export type SubagentModelMode = 'main' | 'fast' | 'profile';
|
|
31
|
+
/** Why a non-main request degraded to the main model (D-4 disclosure). */
|
|
32
|
+
export type SubagentFallbackReason =
|
|
33
|
+
/** `team.model='fast'` but `resolveFastTier` is not ok (off, unconfigured, no key). */
|
|
34
|
+
'fast_unavailable'
|
|
35
|
+
/** `modelProfileId` is absent from the library (deleted, or never existed). */
|
|
36
|
+
| 'profile_missing'
|
|
37
|
+
/** The entry exists but carries no key of its own and the shared layer has none. */
|
|
38
|
+
| 'profile_no_key';
|
|
39
|
+
/** In-memory resolution; recomputed per dispatch/render, never persisted (§6.3). */
|
|
40
|
+
export interface SubagentModelResolution {
|
|
41
|
+
/** The user's requested mode, verbatim from config (UI highlight reads this). */
|
|
42
|
+
mode: SubagentModelMode;
|
|
43
|
+
/** The mode actually in effect; `'main'` whenever a fallback fired. */
|
|
44
|
+
effective: SubagentModelMode;
|
|
45
|
+
/** The connection children will use. On fallback, exactly the main connection. */
|
|
46
|
+
ref: ModelRef;
|
|
47
|
+
/** main/profile take `config.thinkingLevel`; fast takes `config.fast.thinkingLevel`. */
|
|
48
|
+
thinkingLevel: ThinkingLevel;
|
|
49
|
+
/** The role controller key resolution uses: main/profile `'main'`, fast `'fast'`. */
|
|
50
|
+
keyRole: ModelRole;
|
|
51
|
+
/** The profile entry a `profile` request hit, dedicated key included; else null. */
|
|
52
|
+
profile: ModelProfile | null;
|
|
53
|
+
/** Present only when `effective !== mode`. */
|
|
54
|
+
fallbackReason?: SubagentFallbackReason;
|
|
55
|
+
}
|
|
56
|
+
/** The `AgentController.hasApiKey` shape, injected so there is one key resolver. */
|
|
57
|
+
export type HasApiKey = (providerId: string, role?: ModelRole) => boolean;
|
|
58
|
+
/** The lead-layer key resolver `resolveSubagentKey` falls back to. */
|
|
59
|
+
export type SharedKeyResolver = (providerId: string, role: ModelRole) => string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the model every child of the NEXT dispatch will run on.
|
|
62
|
+
*
|
|
63
|
+
* Pure, synchronous, no IO — testable as a matrix over config fixtures.
|
|
64
|
+
*
|
|
65
|
+
* @param config The live effective config. Read fresh; never snapshot it.
|
|
66
|
+
* @param hasKey `AgentController.hasApiKey`, which already resolves the
|
|
67
|
+
* config-file key, the provider environment variable and the one-shot
|
|
68
|
+
* `--api-key` override. Injected rather than reimplemented for the reason
|
|
69
|
+
* `resolveFastTier` records: a second key resolver is a second answer to
|
|
70
|
+
* "why does it say no key?".
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolveSubagentModel(config: CliConfig, hasKey: HasApiKey): SubagentModelResolution;
|
|
73
|
+
/**
|
|
74
|
+
* The key a CHILD resolves for a provider, profile-aware (§3.3 / I-4).
|
|
75
|
+
*
|
|
76
|
+
* A profile's dedicated key is visible ONLY here: it never reaches env,
|
|
77
|
+
* `config.apiKeys` or a log line. While the resolution's profile is bound and
|
|
78
|
+
* the provider matches it, the entry key takes priority over the shared layer;
|
|
79
|
+
* every other provider (and every non-profile mode) resolves through the
|
|
80
|
+
* lead's own resolver at `resolution.keyRole` — which is what keeps a `fast`
|
|
81
|
+
* child entitled to the fast-role key and a `main` child to the main one.
|
|
82
|
+
*
|
|
83
|
+
* @param config The live config (carried for parity with the resolver pair).
|
|
84
|
+
* @param resolution A fresh `resolveSubagentModel` output.
|
|
85
|
+
* @param providerId The provider the child needs a key for.
|
|
86
|
+
* @param shared The lead-layer resolver (`controller.resolveKey`).
|
|
87
|
+
*/
|
|
88
|
+
export declare function resolveSubagentKey(config: CliConfig, resolution: SubagentModelResolution, providerId: string, shared: SharedKeyResolver): string | undefined;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `resolveSubagentModel` — THE single authority on which model a team child
|
|
3
|
+
* runs (subagent-model-and-question-controls §3.3).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/team/**` is inside the glyph scanner's scope.
|
|
6
|
+
*
|
|
7
|
+
* EVERY CONSUMER READS THE CHILD'S MODEL THROUGH THIS FUNCTION AND NOWHERE
|
|
8
|
+
* ELSE — `createSubagent`, the `task` report header, the settings root row and
|
|
9
|
+
* `/team model` — following the `resolveFastTier` precedent one directory over:
|
|
10
|
+
* four call sites each recomputing "which model do children run on?" from raw
|
|
11
|
+
* config keys is exactly how the report ends up naming a model the children
|
|
12
|
+
* silently fell back from.
|
|
13
|
+
*
|
|
14
|
+
* IT IS RE-RUN ON EVERY DISPATCH AND EVERY RENDER, NOT ONCE (rule 8 / RV-3 of
|
|
15
|
+
* the fast tier, inherited here): `team.model`, the fast tier and the profile
|
|
16
|
+
* library are all LIVE keys a settings save, `/fast off` or `/team model` can
|
|
17
|
+
* rewrite mid-session. A resolution captured at construction would be a
|
|
18
|
+
* resolution describing a session that no longer exists.
|
|
19
|
+
*
|
|
20
|
+
* A CHILD IS NEVER LEFT WITHOUT A MODEL (D-4). Every failure mode — fast tier
|
|
21
|
+
* off, profile deleted, profile key missing — degrades to the MAIN connection
|
|
22
|
+
* with a `fallbackReason`, so a dispatch cannot fail because its model setting
|
|
23
|
+
* went stale. The reason is disclosed, never swallowed: the report header and
|
|
24
|
+
* the settings summary line both render it.
|
|
25
|
+
*/
|
|
26
|
+
import { resolveFastTier } from '../fast/resolve.js';
|
|
27
|
+
function mainRef(config) {
|
|
28
|
+
return {
|
|
29
|
+
providerId: config.provider,
|
|
30
|
+
modelId: config.model,
|
|
31
|
+
...(config.baseUrl ? { baseUrl: config.baseUrl } : {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function fallbackToMain(config, mode, fallbackReason) {
|
|
35
|
+
// THE ALWAYS-USABLE ANSWER (D-4): whatever the user asked for that could not
|
|
36
|
+
// resolve, the caller still gets a working connection — the same provider,
|
|
37
|
+
// model, base URL and thinking level the lead itself runs on.
|
|
38
|
+
return {
|
|
39
|
+
mode,
|
|
40
|
+
effective: 'main',
|
|
41
|
+
ref: mainRef(config),
|
|
42
|
+
thinkingLevel: config.thinkingLevel,
|
|
43
|
+
keyRole: 'main',
|
|
44
|
+
profile: null,
|
|
45
|
+
fallbackReason,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the model every child of the NEXT dispatch will run on.
|
|
50
|
+
*
|
|
51
|
+
* Pure, synchronous, no IO — testable as a matrix over config fixtures.
|
|
52
|
+
*
|
|
53
|
+
* @param config The live effective config. Read fresh; never snapshot it.
|
|
54
|
+
* @param hasKey `AgentController.hasApiKey`, which already resolves the
|
|
55
|
+
* config-file key, the provider environment variable and the one-shot
|
|
56
|
+
* `--api-key` override. Injected rather than reimplemented for the reason
|
|
57
|
+
* `resolveFastTier` records: a second key resolver is a second answer to
|
|
58
|
+
* "why does it say no key?".
|
|
59
|
+
*/
|
|
60
|
+
export function resolveSubagentModel(config, hasKey) {
|
|
61
|
+
// `clampTeamConfig` guarantees the enum on every read and write path, so the
|
|
62
|
+
// default `'main'` here is belt-and-braces rather than a real branch.
|
|
63
|
+
const mode = config.team.model ?? 'main';
|
|
64
|
+
if (mode === 'fast') {
|
|
65
|
+
const tier = resolveFastTier(config, hasKey);
|
|
66
|
+
if (tier.ok) {
|
|
67
|
+
return {
|
|
68
|
+
mode,
|
|
69
|
+
effective: 'fast',
|
|
70
|
+
ref: tier.ref,
|
|
71
|
+
thinkingLevel: config.fast.thinkingLevel,
|
|
72
|
+
keyRole: 'fast',
|
|
73
|
+
profile: null,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return fallbackToMain(config, mode, 'fast_unavailable');
|
|
77
|
+
}
|
|
78
|
+
if (mode === 'profile') {
|
|
79
|
+
// A missing library, a missing id and a dangling id are one outcome
|
|
80
|
+
// (D-4): degrade to main and say so. Reference EXISTENCE is deliberately
|
|
81
|
+
// not validated in `clampTeamConfig` — resolution is where it belongs.
|
|
82
|
+
const id = config.team.modelProfileId;
|
|
83
|
+
const entry = id
|
|
84
|
+
? config.modelProfiles?.entries.find((candidate) => candidate.id === id)
|
|
85
|
+
: undefined;
|
|
86
|
+
if (!entry)
|
|
87
|
+
return fallbackToMain(config, mode, 'profile_missing');
|
|
88
|
+
// The dedicated entry key wins; without one the shared layer must have a
|
|
89
|
+
// key for the entry's provider, or the child could not authenticate at all.
|
|
90
|
+
if (!entry.apiKey && !hasKey(entry.provider, 'main')) {
|
|
91
|
+
return fallbackToMain(config, mode, 'profile_no_key');
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
mode,
|
|
95
|
+
effective: 'profile',
|
|
96
|
+
ref: {
|
|
97
|
+
providerId: entry.provider,
|
|
98
|
+
modelId: entry.model,
|
|
99
|
+
...(entry.baseUrl ? { baseUrl: entry.baseUrl } : {}),
|
|
100
|
+
},
|
|
101
|
+
thinkingLevel: config.thinkingLevel,
|
|
102
|
+
keyRole: 'main',
|
|
103
|
+
profile: entry,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
mode: 'main',
|
|
108
|
+
effective: 'main',
|
|
109
|
+
ref: mainRef(config),
|
|
110
|
+
thinkingLevel: config.thinkingLevel,
|
|
111
|
+
keyRole: 'main',
|
|
112
|
+
profile: null,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The key a CHILD resolves for a provider, profile-aware (§3.3 / I-4).
|
|
117
|
+
*
|
|
118
|
+
* A profile's dedicated key is visible ONLY here: it never reaches env,
|
|
119
|
+
* `config.apiKeys` or a log line. While the resolution's profile is bound and
|
|
120
|
+
* the provider matches it, the entry key takes priority over the shared layer;
|
|
121
|
+
* every other provider (and every non-profile mode) resolves through the
|
|
122
|
+
* lead's own resolver at `resolution.keyRole` — which is what keeps a `fast`
|
|
123
|
+
* child entitled to the fast-role key and a `main` child to the main one.
|
|
124
|
+
*
|
|
125
|
+
* @param config The live config (carried for parity with the resolver pair).
|
|
126
|
+
* @param resolution A fresh `resolveSubagentModel` output.
|
|
127
|
+
* @param providerId The provider the child needs a key for.
|
|
128
|
+
* @param shared The lead-layer resolver (`controller.resolveKey`).
|
|
129
|
+
*/
|
|
130
|
+
export function resolveSubagentKey(config, resolution, providerId, shared) {
|
|
131
|
+
void config; // parity parameter: the pair reads one config, the key does not need it
|
|
132
|
+
if (resolution.profile && providerId === resolution.ref.providerId) {
|
|
133
|
+
return resolution.profile.apiKey ?? shared(providerId, 'main');
|
|
134
|
+
}
|
|
135
|
+
return shared(providerId, resolution.keyRole);
|
|
136
|
+
}
|
package/dist/team/report.d.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* because of one HTTP 429.
|
|
22
22
|
*/
|
|
23
23
|
import type { ModelCost } from '@aragon-agent/core';
|
|
24
|
+
import type { SubagentModelResolution } from './model.js';
|
|
24
25
|
import type { DispatchOutcome, SubagentRun } from './types.js';
|
|
25
26
|
export interface ReportOptions {
|
|
26
27
|
/** The lead's model cost table, so the header can state real spend (R-5).
|
|
@@ -37,6 +38,16 @@ export interface ReportOptions {
|
|
|
37
38
|
* rule the fast tier already follows everywhere else).
|
|
38
39
|
*/
|
|
39
40
|
fastCost?: ModelCost;
|
|
41
|
+
/**
|
|
42
|
+
* The dispatch-time subagent model resolution (subagent-model §3.4).
|
|
43
|
+
*
|
|
44
|
+
* ABSENT under the default `team.model: 'main'` mode is what keeps the
|
|
45
|
+
* parity report byte-identical (I-2): the disclosure line exists only when
|
|
46
|
+
* children did NOT run the main model, and a resolution that degraded
|
|
47
|
+
* (fast tier off, profile deleted, profile key missing) says so instead of
|
|
48
|
+
* silently renaming the model the children fell back to (D-4).
|
|
49
|
+
*/
|
|
50
|
+
subagentModel?: SubagentModelResolution;
|
|
40
51
|
}
|
|
41
52
|
/**
|
|
42
53
|
* Cut `text` to at most `maxBytes` WITHOUT splitting a UTF-8 sequence.
|
package/dist/team/report.js
CHANGED
|
@@ -106,8 +106,31 @@ function buildHeader(outcome, options) {
|
|
|
106
106
|
`(${counts}) in ${formatDuration(durationMs)}.`
|
|
107
107
|
: `Team dispatch: ${outcome.runs.length} of ${outcome.runs.length} subagents finished ` +
|
|
108
108
|
`(${counts}) in ${formatDuration(durationMs)}.`);
|
|
109
|
+
// THE PRICE OF THE MODEL CHILDREN ACTUALLY RAN (subagent-model §3.4). The
|
|
110
|
+
// default main flow always supplies a table, so the ternary's last branch
|
|
111
|
+
// is the legacy byte-identical shape; an UNPRICED fast or profile child
|
|
112
|
+
// model says `pricing unknown` - unknown pricing is not zero pricing, and
|
|
113
|
+
// `$0.00` on a dispatch that is spending money is the C-11 / RV-4 lie.
|
|
114
|
+
const spend = options.cost
|
|
115
|
+
? formatCost(computeCost(outcome.usage, options.cost))
|
|
116
|
+
: options.subagentModel && options.subagentModel.effective !== 'main'
|
|
117
|
+
? 'pricing unknown'
|
|
118
|
+
: formatCost(computeCost(outcome.usage, options.cost));
|
|
109
119
|
lines.push(`Tokens: in ${formatTokens(outcome.usage.inputTokens)}, out ` +
|
|
110
|
-
`${formatTokens(outcome.usage.outputTokens)}. Cost: ${
|
|
120
|
+
`${formatTokens(outcome.usage.outputTokens)}. Cost: ${spend}.`);
|
|
121
|
+
// THE MODEL DISCLOSURE LINE (D-4), immediately after the spend it explains.
|
|
122
|
+
// Rendered only when children did not run the main model, so the default
|
|
123
|
+
// report stays byte-identical (I-2) and a degraded request says WHICH mode
|
|
124
|
+
// was asked for and WHY it fell back rather than quietly renaming it.
|
|
125
|
+
const subagent = options.subagentModel;
|
|
126
|
+
if (subagent && subagent.effective !== 'main') {
|
|
127
|
+
lines.push(`Children ran on ${subagent.ref.providerId}:${subagent.ref.modelId} ` +
|
|
128
|
+
`(subagent model: ${subagent.effective}).`);
|
|
129
|
+
}
|
|
130
|
+
else if (subagent && subagent.fallbackReason) {
|
|
131
|
+
lines.push(`Subagent model ${subagent.mode} unavailable (${subagent.fallbackReason}); ` +
|
|
132
|
+
`children ran on the main model.`);
|
|
133
|
+
}
|
|
111
134
|
for (const conflict of findFileConflicts(outcome.runs)) {
|
|
112
135
|
const who = conflict.labels.map((l) => `"${l}"`).join(' and ');
|
|
113
136
|
lines.push(`WARNING: agents ${who} both wrote ${conflict.path} - review before trusting either.`);
|
package/dist/team/subagent.js
CHANGED
|
@@ -18,6 +18,7 @@ import { createBuiltinTools } from '../tools/index.js';
|
|
|
18
18
|
import { pickActivityArgs, sanitizeActivity } from './activity.js';
|
|
19
19
|
import { TEAM_LIMITS, TEAM_SUBAGENT_TOOL_NAMES } from './limits.js';
|
|
20
20
|
import { isRetryableStreamError } from './retry.js';
|
|
21
|
+
import { resolveSubagentKey, resolveSubagentModel } from './model.js';
|
|
21
22
|
import { makeTeamSend, makeTeamWait, withMailboxTail } from './comm-tools.js';
|
|
22
23
|
import { buildSubagentBlock } from './prompt.js';
|
|
23
24
|
/** The default factory: a real `Agent` from `@aragon-agent/core`. */
|
|
@@ -162,25 +163,34 @@ export function createSubagent(spec, deps, hooks) {
|
|
|
162
163
|
let agentRef = null;
|
|
163
164
|
const getAgent = () => agentRef;
|
|
164
165
|
const tools = buildSubagentTools(spec, deps, getAgent);
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
166
|
+
// WHICH MODEL A CHILD RUNS IS A USER SETTING, RESOLVED FROM THE
|
|
167
|
+
// DISPATCH-TIME CONFIG through the one authority (subagent-model §3.4).
|
|
168
|
+
// `team.model: 'main'` - the default - is the historical main-agent parity
|
|
169
|
+
// exactly: same provider, model, base URL and thinking level as the lead.
|
|
170
|
+
// `'fast'` / `'profile'` are the user's explicit, visible, reversible cost
|
|
171
|
+
// switches, and EVERY failure mode (tier off, profile deleted, profile key
|
|
172
|
+
// missing) degrades back to the main connection with a disclosed reason
|
|
173
|
+
// rather than failing the dispatch (D-4). The MODEL itself still cannot
|
|
174
|
+
// choose: `normalizeSubagentSpecs` drops a stale `model` key (D-1).
|
|
170
175
|
//
|
|
171
176
|
// `maxTokens` inherits `deps.config.maxTokens` below - the same ambition the
|
|
172
|
-
// lead sends, clamped by
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
177
|
+
// lead sends, clamped by Core's `resolveOutputTokens` against the CHILD
|
|
178
|
+
// model's own ceiling, so an inherited large value degrades to the right
|
|
179
|
+
// number rather than to an HTTP 400.
|
|
180
|
+
const resolution = resolveSubagentModel(deps.config, (id, role) => Boolean(deps.getApiKey(id, role)));
|
|
181
|
+
const model = resolution.ref;
|
|
182
|
+
// THE CHILD'S OWN KEY CLOSURE, PROFILE-AWARE (§3.3 / I-4). A bound profile's
|
|
183
|
+
// dedicated key is visible ONLY inside this closure - never in env, config
|
|
184
|
+
// `apiKeys` or a log line - while every other provider resolves through the
|
|
185
|
+
// lead's resolver at `resolution.keyRole`, which is what keeps a fast child
|
|
186
|
+
// entitled to the fast-role key and a main child to the main one.
|
|
187
|
+
const childGetApiKey = (id) => resolveSubagentKey(deps.config, resolution, id, (providerId, role) => deps.getApiKey(providerId, role));
|
|
188
|
+
// The boolean twin, for the child's compaction summarizer (V-4): the child's
|
|
189
|
+
// own resolution first, the lead layer by role second. Forwarding only
|
|
190
|
+
// `deps.getApiKey` would hide a profile-dedicated key from `resolveSummarizer`
|
|
191
|
+
// and silently disable the child's compaction - the exact gap the req-level
|
|
192
|
+
// resolvers on `ChildContextManagerRequest` exist to close.
|
|
193
|
+
const childHasKey = (id, role) => Boolean(childGetApiKey(id) ?? deps.getApiKey(id, role));
|
|
184
194
|
// HOISTED TO A LOCAL, AND THAT IS WHAT MAKES W3 POSSIBLE AT ALL. This was built
|
|
185
195
|
// INLINE inside the `agentFactory({...})` argument, so there was nothing for the
|
|
186
196
|
// child manager's `getSystemPrompt` accessor to close over - and the accessor
|
|
@@ -216,7 +226,12 @@ export function createSubagent(spec, deps, hooks) {
|
|
|
216
226
|
const contextManager = deps.contextManagerFor?.({
|
|
217
227
|
label: spec.label,
|
|
218
228
|
model,
|
|
219
|
-
|
|
229
|
+
// keyRole (V-4): a fast child's summarizer resolves its main choice with
|
|
230
|
+
// role='fast', so a fast-only key is visible to it; profile keys reach it
|
|
231
|
+
// through the req-level resolvers below instead.
|
|
232
|
+
role: resolution.keyRole,
|
|
233
|
+
getApiKey: childGetApiKey,
|
|
234
|
+
hasKey: childHasKey,
|
|
220
235
|
getMessages: () => agentRef?.state.messages ?? [],
|
|
221
236
|
getSystemPrompt: () => systemPrompt,
|
|
222
237
|
onCompacted: () => {
|
|
@@ -227,12 +242,13 @@ export function createSubagent(spec, deps, hooks) {
|
|
|
227
242
|
systemPrompt,
|
|
228
243
|
model,
|
|
229
244
|
tools,
|
|
230
|
-
thinkingLevel:
|
|
245
|
+
thinkingLevel: resolution.thinkingLevel,
|
|
231
246
|
// The LEAD's registry instance, REUSED (P2-2). It holds adapter state, and
|
|
232
247
|
// constructing five of them per dispatch is pure waste.
|
|
233
248
|
providerRegistry: deps.providerRegistry,
|
|
234
|
-
// The
|
|
235
|
-
|
|
249
|
+
// The child's own profile-aware closure, so a settings-screen key edit
|
|
250
|
+
// reaches children too and a bound profile's key reaches ONLY them.
|
|
251
|
+
getApiKey: childGetApiKey,
|
|
236
252
|
...(deps.config.maxTokens !== undefined ? { maxTokens: deps.config.maxTokens } : {}),
|
|
237
253
|
timeouts: {
|
|
238
254
|
toolTimeout: deps.config.toolTimeoutMs,
|
package/dist/team/task-tool.d.ts
CHANGED
|
@@ -20,14 +20,17 @@
|
|
|
20
20
|
* in the schema would behave differently depending on whether `ajv`, an OPTIONAL
|
|
21
21
|
* dependency of core, happened to install.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
* model
|
|
25
|
-
*
|
|
26
|
-
* a delegation is never a downgrade
|
|
27
|
-
*
|
|
23
|
+
* WHICH MODEL A CHILD RUNS IS THE USER'S SETTING, NOT THE MODEL'S (D-1):
|
|
24
|
+
* `team.model` defaults to the lead's own connection (main-agent parity) and
|
|
25
|
+
* can name the fast tier or a profile; there is still no per-child `model`
|
|
26
|
+
* property, so a delegation is never a self-downgrade. Resolution lives in
|
|
27
|
+
* `team/model.ts` and is re-read from the LIVE config at every dispatch; the
|
|
28
|
+
* fast tier otherwise reaches children the way it reaches the lead
|
|
29
|
+
* (compaction summaries, per-child reviews).
|
|
28
30
|
*/
|
|
29
31
|
import { type AgentTool } from '@aragon-agent/core';
|
|
30
32
|
import type { ModelCost } from '@aragon-agent/core';
|
|
33
|
+
import type { SubagentModelResolution } from './model.js';
|
|
31
34
|
import type { TeamRuntime } from './runtime.js';
|
|
32
35
|
export interface TaskToolDeps {
|
|
33
36
|
runtime: TeamRuntime;
|
|
@@ -37,8 +40,24 @@ export interface TaskToolDeps {
|
|
|
37
40
|
/** Whether a usable key exists for the active provider right now (P2-3). */
|
|
38
41
|
hasApiKey: () => boolean;
|
|
39
42
|
activeProvider: () => string;
|
|
40
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* The cost table of the model children ACTUALLY run (subagent-model §3.4).
|
|
45
|
+
*
|
|
46
|
+
* The controller resolves `team.model` live and prices `modelCost` at the
|
|
47
|
+
* EFFECTIVE model's table: with the default `main` mode this is exactly the
|
|
48
|
+
* lead's own table, so the report's spend line is byte-identical to the
|
|
49
|
+
* parity behavior; with `fast` / `profile` it is that model's table, and an
|
|
50
|
+
* UNPRICED one yields `undefined` so the header says `pricing unknown`
|
|
51
|
+
* rather than fabricating `$0.00` (C-11 / RV-4).
|
|
52
|
+
*/
|
|
41
53
|
modelCost: () => ModelCost | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* The dispatch-time subagent model resolution, for the report's disclosure
|
|
56
|
+
* line (D-4): rendered ONLY when children did not run the main model, and
|
|
57
|
+
* carrying the fallback reason when they could not. OPTIONAL so existing
|
|
58
|
+
* construction sites compile; absent simply omits the line.
|
|
59
|
+
*/
|
|
60
|
+
subagentModel?: () => SubagentModelResolution;
|
|
42
61
|
/**
|
|
43
62
|
* The FAST tier's cost table, so the report's supervisor spend line states
|
|
44
63
|
* real spend (subagent-overseer-v2 D-7 / R-P1-2). OPTIONAL and returning
|
package/dist/team/task-tool.js
CHANGED
|
@@ -20,11 +20,13 @@
|
|
|
20
20
|
* in the schema would behave differently depending on whether `ajv`, an OPTIONAL
|
|
21
21
|
* dependency of core, happened to install.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
* model
|
|
25
|
-
*
|
|
26
|
-
* a delegation is never a downgrade
|
|
27
|
-
*
|
|
23
|
+
* WHICH MODEL A CHILD RUNS IS THE USER'S SETTING, NOT THE MODEL'S (D-1):
|
|
24
|
+
* `team.model` defaults to the lead's own connection (main-agent parity) and
|
|
25
|
+
* can name the fast tier or a profile; there is still no per-child `model`
|
|
26
|
+
* property, so a delegation is never a self-downgrade. Resolution lives in
|
|
27
|
+
* `team/model.ts` and is re-read from the LIVE config at every dispatch; the
|
|
28
|
+
* fast tier otherwise reaches children the way it reaches the lead
|
|
29
|
+
* (compaction summaries, per-child reviews).
|
|
28
30
|
*/
|
|
29
31
|
import { errorResult, textResult } from '@aragon-agent/core';
|
|
30
32
|
import { TEAM_LIMITS } from './limits.js';
|
|
@@ -39,8 +41,9 @@ const TEAM_OFF_REFUSAL = 'Team mode is off for this session. Do this work yourse
|
|
|
39
41
|
*/
|
|
40
42
|
const TASK_DESCRIPTION = 'Run several independent subagents in parallel and get one combined report. Each ' +
|
|
41
43
|
'subagent is a fresh agent with the same tools and working directory but NO memory of ' +
|
|
42
|
-
'this conversation - its prompt must be self-contained. Each runs on the
|
|
43
|
-
'
|
|
44
|
+
'this conversation - its prompt must be self-contained. Each runs on the model the ' +
|
|
45
|
+
'user configured for subagents (the main model by default). Use this when a task ' +
|
|
46
|
+
'splits ' +
|
|
44
47
|
'into 2 or more parts that do not depend on each other\'s output, especially reading or ' +
|
|
45
48
|
'searching several areas at once. Do NOT use it when one part needs another part\'s ' +
|
|
46
49
|
'result (do those yourself, in order), when two parts would edit the same file, or for ' +
|
|
@@ -120,13 +123,16 @@ export function createTaskTool(deps) {
|
|
|
120
123
|
// A cancellation or a partial result rendered as a tool FAILURE invites
|
|
121
124
|
// the model to retry the whole fan-out, which is the most expensive
|
|
122
125
|
// possible reaction (the same reasoning as `ask_user`'s cancelled
|
|
123
|
-
// shape, and D-14 one level up).
|
|
124
|
-
//
|
|
126
|
+
// shape, and D-14 one level up). `modelCost` is priced at the model
|
|
127
|
+
// children ACTUALLY ran - the lead's own table under the default
|
|
128
|
+
// `main` mode - so the total stays one honest price.
|
|
125
129
|
const cost = deps.modelCost();
|
|
126
130
|
const fastCost = deps.fastModelCost?.();
|
|
131
|
+
const subagentModel = deps.subagentModel?.();
|
|
127
132
|
return textResult(buildDispatchReport(outcome, {
|
|
128
133
|
...(cost ? { cost } : {}),
|
|
129
134
|
...(fastCost ? { fastCost } : {}),
|
|
135
|
+
...(subagentModel ? { subagentModel } : {}),
|
|
130
136
|
}));
|
|
131
137
|
},
|
|
132
138
|
};
|
package/dist/todo/types.d.ts
CHANGED
|
@@ -42,9 +42,11 @@ export interface TodoSnapshot {
|
|
|
42
42
|
/**
|
|
43
43
|
* Why a list went away.
|
|
44
44
|
*
|
|
45
|
-
* `user` - `/todo clear`, or `/clear
|
|
46
|
-
* which is I-2's standing exception;
|
|
47
|
-
*
|
|
45
|
+
* `user` - `/todo clear`, or `/clear screen` (the screen-only form). Both are
|
|
46
|
+
* an explicit user instruction, which is I-2's standing exception;
|
|
47
|
+
* neither touches `messages`.
|
|
48
|
+
* `reset` - `/clear` (or its alias `/reset`) through `clearMessages()`, or a
|
|
49
|
+
* `/resume` whose file carried no list (§3.13).
|
|
48
50
|
* `turn` - a new TUI task started, or a legacy caller began a turn after
|
|
49
51
|
* every item was completed.
|
|
50
52
|
* `stale` - unfinished, and unwritten for more than `TODO_LIMITS.staleTurns`
|
package/dist/ui/App.js
CHANGED
|
@@ -55,6 +55,7 @@ import { publishExitSnapshot } from './exit-snapshot.js';
|
|
|
55
55
|
import { OverlayFrame } from './layout/OverlayFrame.js';
|
|
56
56
|
import { helpRows } from './overlays/HelpOverlay.js';
|
|
57
57
|
import { ModelPicker } from './overlays/ModelPicker.js';
|
|
58
|
+
import { createModelProbe } from '../agent/model-probe.js';
|
|
58
59
|
import { SettingsScreen, compactionSettingsFrom, fastSettingsFrom, } from './overlays/SettingsScreen.js';
|
|
59
60
|
import { ConfirmDialog } from './overlays/ConfirmDialog.js';
|
|
60
61
|
import { QuestionOverlay } from './overlays/QuestionOverlay.js';
|
|
@@ -1388,6 +1389,12 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1388
1389
|
requestCopy: copyCoordinator.requestCopy,
|
|
1389
1390
|
controller,
|
|
1390
1391
|
state: stateRef.current,
|
|
1392
|
+
// THE ARCHIVE'S ONLY WAY TO SEE THE SCREEN (P0-1 / §3.7), and a thunk for a
|
|
1393
|
+
// reason `state` above illustrates: `state` is a snapshot of this render,
|
|
1394
|
+
// while `/clear` interrupts a run and then waits for it to unwind, so the last
|
|
1395
|
+
// tool cards of the abandoned run are appended AFTER the command starts. A
|
|
1396
|
+
// value here would archive the screen minus the part the user was watching.
|
|
1397
|
+
transcriptEntries: () => stateRef.current.entries,
|
|
1391
1398
|
dispatch: (action) => {
|
|
1392
1399
|
if (action.type === 'resetConversation' || action.type === 'restoreEntries') {
|
|
1393
1400
|
metrics.current = createRunMetrics();
|
|
@@ -1535,12 +1542,25 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1535
1542
|
};
|
|
1536
1543
|
submitRef.current = submitMessage;
|
|
1537
1544
|
const executeSlashInput = async (raw) => {
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1545
|
+
// A CONVERSATION SWITCH OWNS THE QUEUE FOR THE WHOLE COMMAND (B-6 / §3.7).
|
|
1546
|
+
//
|
|
1547
|
+
// The guard used to be raised only when a compaction happened to be busy,
|
|
1548
|
+
// which left the one window this feature is about unwatched: `/clear` on an
|
|
1549
|
+
// IDLE session took the flag nowhere, and `drainCompactionQueue` was still
|
|
1550
|
+
// free to `continue()` a message queued in the previous conversation into
|
|
1551
|
+
// the one the user had just started. Whether there is work to cancel is a
|
|
1552
|
+
// separate question, so the two are now separate values: `switching` decides
|
|
1553
|
+
// the lock, `switchWhileBusy` decides the cancellation.
|
|
1554
|
+
//
|
|
1555
|
+
// `/resume` is still refused mid-run by `handleSubmit` below, so for it the
|
|
1556
|
+
// lock only ever matters while idle - that is the minimum this invariant
|
|
1557
|
+
// asks for, not a contradiction.
|
|
1558
|
+
const switching = /^\s*\/(clear|reset|resume)(?:\s|$)/.test(raw);
|
|
1559
|
+
const switchWhileBusy = switching && Boolean(controller.isCompactionBusy?.());
|
|
1560
|
+
if (switching)
|
|
1541
1561
|
switchingConversation.current = true;
|
|
1562
|
+
if (switchWhileBusy)
|
|
1542
1563
|
await controller.cancelCompaction();
|
|
1543
|
-
}
|
|
1544
1564
|
// Retain a recoverable command before any asynchronous handler can fail.
|
|
1545
1565
|
const safe = redactText(raw, getSecrets());
|
|
1546
1566
|
recordPrompt(safe);
|
|
@@ -1553,7 +1573,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1553
1573
|
notify('error', `Command failed: ${redactText(reason, getSecrets())}\nInput: ${safe}`);
|
|
1554
1574
|
}
|
|
1555
1575
|
finally {
|
|
1556
|
-
if (
|
|
1576
|
+
if (switching) {
|
|
1557
1577
|
switchingConversation.current = false;
|
|
1558
1578
|
drainCompactionQueue.current();
|
|
1559
1579
|
}
|
|
@@ -1563,7 +1583,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1563
1583
|
const trimmed = raw.trimStart();
|
|
1564
1584
|
const slash = trimmed.startsWith('/') && !trimmed.startsWith('//');
|
|
1565
1585
|
if (slash && (interactionPhase.current === 'running' || controller.isRunning()) &&
|
|
1566
|
-
/^\/
|
|
1586
|
+
/^\/resume(?:\s|$)/.test(trimmed)) {
|
|
1567
1587
|
return { accepted: false,
|
|
1568
1588
|
reason: 'Interrupt the current run before switching conversations.' };
|
|
1569
1589
|
}
|
|
@@ -2050,6 +2070,18 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2050
2070
|
const overlay = state.overlay;
|
|
2051
2071
|
const settingsDraft = useMemo(() => overlay === 'settings'
|
|
2052
2072
|
? controller.createModelSettingsDraft() : undefined, [controller, overlay]);
|
|
2073
|
+
// ONE PROBE PER APP (model-profile-connection-test 5 row 9): the port needs the
|
|
2074
|
+
// controller's provider registry, which the overlay has no business owning.
|
|
2075
|
+
// Built only while the settings overlay is open, so no other screen pays for it.
|
|
2076
|
+
const probe = useMemo(() => {
|
|
2077
|
+
if (overlay !== 'settings')
|
|
2078
|
+
return undefined;
|
|
2079
|
+
// A test double or an embedded host may predate this port; absent means the
|
|
2080
|
+
// two probe rows read `(unavailable)` rather than the screen crashing.
|
|
2081
|
+
if (typeof controller.getProviderRegistry !== 'function')
|
|
2082
|
+
return undefined;
|
|
2083
|
+
return createModelProbe({ registry: controller.getProviderRegistry() });
|
|
2084
|
+
}, [controller, overlay]);
|
|
2053
2085
|
const hasKey = controller.hasApiKey();
|
|
2054
2086
|
const empty = state.entries.length === 0;
|
|
2055
2087
|
// RENDER SCOPE, so the chip and the composer hint agree with what the Ctrl+C
|
|
@@ -2144,13 +2176,18 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2144
2176
|
// `auto`, not an empty string: an empty field reads as "unset", which
|
|
2145
2177
|
// is indistinguishable from AUTO and is not a state this can be in.
|
|
2146
2178
|
maxTokens: cfg.maxTokens ? String(cfg.maxTokens) : 'auto',
|
|
2179
|
+
// BOTH HALVES, seeded AND read back (§6.3): the sibling comment above
|
|
2180
|
+
// records what omitting either one costs. `auto` mirrors the row's
|
|
2181
|
+
// empty-field reading; a bound Main profile hides the row entirely
|
|
2182
|
+
// and the profile editor seeds from the entry instead.
|
|
2183
|
+
contextWindow: cfg.contextWindow === null ? 'auto' : String(cfg.contextWindow),
|
|
2147
2184
|
apiKey: '',
|
|
2148
2185
|
logLevel: cfg.log.level,
|
|
2149
2186
|
// The four fast rows, seeded through the pure mapper so the screen and
|
|
2150
2187
|
// the save handler cannot disagree about what `(inherit)` means.
|
|
2151
2188
|
...fastSettingsFrom(cfg.fast),
|
|
2152
2189
|
...compactionSettingsFrom(cfg.compaction),
|
|
2153
|
-
}, apiKeys: cfg.apiKeys, maxRows: overlayMaxRows, cols: cols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, draft: settingsDraft, onProfileSave: handleProfileSave, onReload: () => controller.createModelSettingsDraft(), onClose: () => dispatch({ type: 'setOverlay', overlay: null }),
|
|
2190
|
+
}, apiKeys: cfg.apiKeys, maxRows: overlayMaxRows, cols: cols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, draft: settingsDraft, probe: probe, onProfileSave: handleProfileSave, onReload: () => controller.createModelSettingsDraft(), onClose: () => dispatch({ type: 'setOverlay', overlay: null }),
|
|
2154
2191
|
// Read from `cfg` (render scope, re-read every frame), so `/retry off`
|
|
2155
2192
|
// while the screen is open shows the new value on the next paint.
|
|
2156
2193
|
retrySummary: cfg.retry.enabled && cfg.retry.maxRetries > 0 ? String(cfg.retry.maxRetries) : 'off' })) : overlay === 'confirm' && confirmState ? (_jsx(ConfirmDialog, { isActive: !tooSmall, state: confirmState, maxRows: overlayMaxRows, cols: cols, theme: theme, caps: caps, onClose: closeConfirm })) : overlay === 'question' && humanRequest?.kind === 'questions' ? (_jsx(QuestionOverlay, { isActive: !tooSmall, questions: humanRequest.questions, maxRows: overlayMaxRows, cols: cols, theme: theme, caps: caps, onSubmit: (answers) => resolveHuman({ kind: 'answers', answers, cancelled: false }) })) : overlay === 'plan' && humanRequest?.kind === 'plan' ? (_jsx(PlanReviewOverlay, { isActive: !tooSmall, plan: humanRequest.plan, maxRows: overlayMaxRows, cols: cols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, onVerdict: handlePlanVerdict })) : null;
|
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
* kind — without it the card renders a hint that does nothing and expands an
|
|
15
15
|
* older tool card instead (C-15 / P1-8 / AC-21).
|
|
16
16
|
*
|
|
17
|
-
* EVERY STRING DESCRIBES THE HISTORY, NEVER THE SCREEN (§6.4 / P2-9).
|
|
18
|
-
* empties the transcript view but not `messages`, so this card
|
|
19
|
-
*
|
|
20
|
-
* "above" or "earlier in this transcript".
|
|
17
|
+
* EVERY STRING DESCRIBES THE HISTORY, NEVER THE SCREEN (§6.4 / P2-9).
|
|
18
|
+
* `/clear screen` empties the transcript view but not `messages`, so this card
|
|
19
|
+
* can legitimately report a message count the visible rows do not add up to.
|
|
20
|
+
* Nothing here says "above" or "earlier in this transcript". `/clear` itself is
|
|
21
|
+
* the conversation boundary now: it drops the history and this card with it.
|
|
21
22
|
*/
|
|
22
23
|
import React from 'react';
|
|
23
24
|
import type { Theme } from '../theme.js';
|
|
@@ -15,10 +15,11 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
15
15
|
* kind — without it the card renders a hint that does nothing and expands an
|
|
16
16
|
* older tool card instead (C-15 / P1-8 / AC-21).
|
|
17
17
|
*
|
|
18
|
-
* EVERY STRING DESCRIBES THE HISTORY, NEVER THE SCREEN (§6.4 / P2-9).
|
|
19
|
-
* empties the transcript view but not `messages`, so this card
|
|
20
|
-
*
|
|
21
|
-
* "above" or "earlier in this transcript".
|
|
18
|
+
* EVERY STRING DESCRIBES THE HISTORY, NEVER THE SCREEN (§6.4 / P2-9).
|
|
19
|
+
* `/clear screen` empties the transcript view but not `messages`, so this card
|
|
20
|
+
* can legitimately report a message count the visible rows do not add up to.
|
|
21
|
+
* Nothing here says "above" or "earlier in this transcript". `/clear` itself is
|
|
22
|
+
* the conversation boundary now: it drops the history and this card with it.
|
|
22
23
|
*/
|
|
23
24
|
import React from 'react';
|
|
24
25
|
import { Box, Text } from 'ink';
|