@deepseek-harness-tui/dsh-tui 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/bin/dsh-tui.js +42 -1
- package/cordis.patch.yml +10 -0
- package/lib/types/commands.js +1 -1
- package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
- package/lib/types/components/GoalTodoPanel.js +6 -1
- package/lib/types/components/HistorySearchDialog.d.ts.map +1 -1
- package/lib/types/components/HistorySearchDialog.js +5 -1
- package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
- package/lib/types/components/LoadedContextPanel.js +1 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +5 -1
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +52 -74
- package/lib/types/components/SearchBox.d.ts +4 -0
- package/lib/types/components/SearchBox.d.ts.map +1 -1
- package/lib/types/components/SearchBox.js +101 -2
- package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts +4 -1
- package/lib/types/components/Spinner/SpinnerAnimationRow.d.ts.map +1 -1
- package/lib/types/components/Spinner/SpinnerAnimationRow.js +10 -5
- package/lib/types/components/WorkingSpinner.d.ts +3 -1
- package/lib/types/components/WorkingSpinner.d.ts.map +1 -1
- package/lib/types/components/WorkingSpinner.js +2 -2
- package/lib/types/components/design-system/ThemedText.d.ts +9 -3
- package/lib/types/components/design-system/ThemedText.d.ts.map +1 -1
- package/lib/types/components/design-system/ThemedText.js +3 -5
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +52 -8
- package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
- package/lib/types/components/questions/PlanReviewPanel.js +12 -3
- package/lib/types/components/sessions/SessionListRow.d.ts +26 -0
- package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -0
- package/lib/types/components/sessions/SessionListRow.js +35 -0
- package/lib/types/components/sessions/SessionPreview.d.ts +25 -0
- package/lib/types/components/sessions/SessionPreview.d.ts.map +1 -0
- package/lib/types/components/sessions/SessionPreview.js +60 -0
- package/lib/types/dsh-adapter/channel.d.ts +19 -4
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +76 -82
- package/lib/types/dsh-adapter/compat/index.d.ts +1 -1
- package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/index.js +1 -1
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts +8 -0
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/sessionLog.js +1 -1
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +10 -1
- package/lib/types/dsh-adapter/packaged-presets.d.ts +22 -0
- package/lib/types/dsh-adapter/packaged-presets.d.ts.map +1 -0
- package/lib/types/dsh-adapter/packaged-presets.js +93 -0
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +53 -7
- package/lib/types/dsh-adapter/presets.d.ts +16 -0
- package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/presets.js +23 -0
- package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
- package/lib/types/dsh-adapter/providerWizard.js +3 -1
- package/lib/types/dsh-adapter/sessions/digest.d.ts +29 -0
- package/lib/types/dsh-adapter/sessions/digest.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/digest.js +224 -0
- package/lib/types/dsh-adapter/sessions/frames.d.ts +100 -0
- package/lib/types/dsh-adapter/sessions/frames.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/frames.js +246 -0
- package/lib/types/dsh-adapter/sessions/header.d.ts +50 -0
- package/lib/types/dsh-adapter/sessions/header.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/header.js +62 -0
- package/lib/types/dsh-adapter/sessions/index.d.ts +17 -0
- package/lib/types/dsh-adapter/sessions/index.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/index.js +15 -0
- package/lib/types/dsh-adapter/sessions/list.d.ts +41 -0
- package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/list.js +213 -0
- package/lib/types/dsh-adapter/sessions/store.d.ts +48 -0
- package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/store.js +164 -0
- package/lib/types/dsh-adapter/sessions/types.d.ts +142 -0
- package/lib/types/dsh-adapter/sessions/types.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessions/types.js +14 -0
- package/lib/types/i18n.d.ts +150 -22
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +45 -9
- package/lib/types/ink/components/Text.d.ts +9 -1
- package/lib/types/ink/components/Text.d.ts.map +1 -1
- package/lib/types/ink/components/Text.js +7 -6
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +4 -2
- package/lib/types/ink/log-update.d.ts.map +1 -1
- package/lib/types/ink/log-update.js +76 -9
- package/lib/types/ink/parse-keypress.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.js +4 -1
- package/lib/types/ink/terminal.d.ts +40 -4
- package/lib/types/ink/terminal.d.ts.map +1 -1
- package/lib/types/ink/terminal.js +57 -5
- package/lib/types/ink/termio/tokenize.d.ts +6 -0
- package/lib/types/ink/termio/tokenize.d.ts.map +1 -1
- package/lib/types/ink/termio/tokenize.js +18 -4
- package/lib/types/screens/Chat.d.ts +3 -2
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +104 -160
- package/lib/types/screens/SessionBrowser.d.ts +37 -0
- package/lib/types/screens/SessionBrowser.d.ts.map +1 -0
- package/lib/types/screens/SessionBrowser.js +452 -0
- package/lib/types/sessionHistory.d.ts +0 -8
- package/lib/types/sessionHistory.d.ts.map +1 -1
- package/lib/types/sessions/format.d.ts +117 -0
- package/lib/types/sessions/format.d.ts.map +1 -0
- package/lib/types/sessions/format.js +244 -0
- package/lib/types/sessions/view.d.ts +125 -0
- package/lib/types/sessions/view.d.ts.map +1 -0
- package/lib/types/sessions/view.js +234 -0
- package/lib/types/update.d.ts +21 -7
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +66 -16
- package/lib/types/utils/paths.d.ts.map +1 -1
- package/lib/types/utils/paths.js +4 -1
- package/package.json +37 -9
- package/presets/liangshen/.dsh-tui-managed.json +5 -0
- package/presets/liangshen/agent.cordis.yml +389 -0
- package/presets/liangshen/compaction-epoch.mjs +81 -0
- package/presets/liangshen/custom-bash.mjs +126 -0
- package/presets/liangshen/instruction-hint.mjs +181 -0
- package/presets/liangshen/preset.yml +3 -0
- package/presets/liangshen/skill-search.mjs +142 -0
- package/presets/liangshen/tool-bootstrap.mjs +300 -0
- package/lib/types/components/ResumePicker.d.ts +0 -26
- package/lib/types/components/ResumePicker.d.ts.map +0 -1
- package/lib/types/components/ResumePicker.js +0 -50
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anchored tool bootstrap — keep the FIRST model request on the Minimal
|
|
3
|
+
* preset's REAL tool schema (persistent `bash` + `str_replace_editor`), free
|
|
4
|
+
* of auto-injected workspace/skill context, then expose the complete assembled
|
|
5
|
+
* catalog once the session has produced its first durable tool call.
|
|
6
|
+
*
|
|
7
|
+
* The phase is derived from durable session events, so resume and reload
|
|
8
|
+
* preserve it. This preset configures `promoteOn: 'tool-call'`: text-only
|
|
9
|
+
* replies keep the bootstrap catalog, while the request after the first tool
|
|
10
|
+
* call sees every assembled tool at once. Other promotion modes remain
|
|
11
|
+
* supported for derived presets.
|
|
12
|
+
*
|
|
13
|
+
* First-request conditions established by the reproduction work (issues #6
|
|
14
|
+
* and #11, 2026-08-15):
|
|
15
|
+
*
|
|
16
|
+
* 1. Tool schema. The API-visible first-request catalog decides whether the
|
|
17
|
+
* session anchors on the Minimal trajectory. At the adapter-default
|
|
18
|
+
* maxTokens (256000 on the official endpoint) the Minimal tool pair —
|
|
19
|
+
* persistent `bash` + `str_replace_editor` — anchored 5/5 runs with zero
|
|
20
|
+
* `let me` first-lines, while every standard-family schema (pwsh/read,
|
|
21
|
+
* pwsh only, sandboxed bash/read) fell into standard-like behavior
|
|
22
|
+
* (11/11). Bootstrap therefore exposes exactly the Minimal pair, not
|
|
23
|
+
* Standard's `pwsh`/`read`.
|
|
24
|
+
*
|
|
25
|
+
* 2. Output budget. On the official endpoint the first request's `max_tokens`
|
|
26
|
+
* also dominated the trajectory anchor at 1024 (`We need` style in 26/32
|
|
27
|
+
* runs against 0/5 at 256000, independent of tool descriptions). The
|
|
28
|
+
* Minimal tool schema, however, anchors at 256000 WITHOUT any cap, and the
|
|
29
|
+
* cap's delivery depends on the profile package's `prepareCall` behavior
|
|
30
|
+
* (it reaches the request on the 0.1.0-rc.5 source checkout; a prebuilt
|
|
31
|
+
* rc.6-reporting profile package observed in issue #11 overwrote it with
|
|
32
|
+
* `adapterDefaults.maxTokens`). `bootstrapMaxTokens` is therefore OPT-IN:
|
|
33
|
+
* leave it unset to run the Minimal schema at the adapter default, or set
|
|
34
|
+
* it to cap the first request. When set, the cap is stripped after
|
|
35
|
+
* promotion — the next request's seed proposal carries the previous
|
|
36
|
+
* header's maxTokens forward, so the release must be explicit.
|
|
37
|
+
*
|
|
38
|
+
* 3. Injected reminders. dsh-agent-instructions and dsh-tool-skill inject
|
|
39
|
+
* workspace instructions (AGENTS.md) and the skill catalog into the first
|
|
40
|
+
* step as user messages whenever such content exists. With the skill
|
|
41
|
+
* catalog present the anchor did not reproduce at all (0/9); without it
|
|
42
|
+
* the same request reproduces at ~81%. Both message kinds are therefore
|
|
43
|
+
* stripped during bootstrap and allowed again after promotion. The
|
|
44
|
+
* stripped set is configurable via `suppressedContextSources` (default
|
|
45
|
+
* `['skill-catalog', 'agent-instructions']`); an explicitly empty array
|
|
46
|
+
* disables the context filter while keeping the tool bootstrap. A
|
|
47
|
+
* user-initiated skill gesture (`skill-invocation`) is NOT in the default
|
|
48
|
+
* set: it is not an automatic injection, and stripping it would lose the
|
|
49
|
+
* skill content once the gesture scrolls out of the per-step claim.
|
|
50
|
+
*
|
|
51
|
+
* POST-PROMOTION: return the complete assembled catalog without a resident or
|
|
52
|
+
* discovery phase. This matches the two-stage harness experiment: exact
|
|
53
|
+
* Minimal schemas first, then the full capability surface in one transition.
|
|
54
|
+
*
|
|
55
|
+
* COMPACTION (local addition): a compaction rewrites the whole surface, so the
|
|
56
|
+
* first post-compaction request is a "second first request". Promotion is
|
|
57
|
+
* epoch-aware (see compaction-epoch.mjs): after `compaction/end` the session
|
|
58
|
+
* falls back to the controlled phase. With this preset's omitted
|
|
59
|
+
* `compactionTools`, that surface is the exact bootstrap pair until a NEW
|
|
60
|
+
* durable tool call exists past the boundary.
|
|
61
|
+
*
|
|
62
|
+
* Robustness:
|
|
63
|
+
* - Promotion decisions are memoized per session id for this process; the
|
|
64
|
+
* durable event scan runs once per session per process, then O(1).
|
|
65
|
+
* - `includeSubagents: true` applies the same anchor/promotion cycle to
|
|
66
|
+
* delegated agents instead of giving them the full catalog immediately.
|
|
67
|
+
* - A missing bootstrap tool degrades to the full catalog with a one-time
|
|
68
|
+
* warning instead of throwing, so a composition drift can never brick
|
|
69
|
+
* every request of a session.
|
|
70
|
+
* - The pre-step context filter degrades to "keep everything" on failure:
|
|
71
|
+
* a filter bug must never eat the user's context.
|
|
72
|
+
* - Invalid config (bad tool lists, unknown `promoteOn`, malformed
|
|
73
|
+
* `suppressedContextSources`, non-positive `bootstrapMaxTokens`) fails at
|
|
74
|
+
* apply time, i.e. at preset mount, where it is visible and fixable.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
import { createEpochPromotion } from './compaction-epoch.mjs'
|
|
78
|
+
|
|
79
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
80
|
+
export const name = 'anchored-tool-bootstrap'
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Deliberately NO inject list: the listeners only touch services at event
|
|
84
|
+
* time. Applying without an inject — combined with this row being FIRST in
|
|
85
|
+
* agent.cordis.yml — registers the plugin before dsh-agent-instructions and
|
|
86
|
+
* dsh-tool-skill, and waterfall after-next transforms apply in reverse
|
|
87
|
+
* registration order, so the first-request strip below is the LAST transform.
|
|
88
|
+
* With an inject here those plugins register first and re-inject their
|
|
89
|
+
* messages after the strip. The pre-step listener additionally registers with
|
|
90
|
+
* `prepend: true` so the strip stays the outermost transform even against
|
|
91
|
+
* host-plane listeners and future row reordering.
|
|
92
|
+
*/
|
|
93
|
+
export const inject = []
|
|
94
|
+
|
|
95
|
+
/** Durable session event types that count as a promotion signal per mode. */
|
|
96
|
+
const PROMOTE_EVENTS = {
|
|
97
|
+
'tool-call': ['tool/call'],
|
|
98
|
+
'assistant-message': ['assistant/message'],
|
|
99
|
+
either: ['tool/call', 'assistant/message'],
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Every config key this plugin accepts — anything else is a typo. */
|
|
103
|
+
const ALLOWED_KEYS = new Set(['bootstrapTools', 'promoteOn', 'bootstrapMaxTokens', 'suppressedContextSources', 'compactionTools', 'includeSubagents'])
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Context sources stripped from the first request by default. Both are
|
|
107
|
+
* automatic `agent/pre-step` injections: the available-skills reminder
|
|
108
|
+
* (`skill-catalog`) and the AGENTS.md/CLAUDE.md workspace digest
|
|
109
|
+
* (`agent-instructions`). True Minimal mounts neither plugin.
|
|
110
|
+
*/
|
|
111
|
+
const DEFAULT_SUPPRESSED_SOURCES = ['skill-catalog', 'agent-instructions']
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The default first-request catalog: the OFFICIAL Minimal preset's exact tool
|
|
115
|
+
* pair — the persistent `bash` shell and `str_replace_editor`. Issue #11
|
|
116
|
+
* measured this schema anchoring 5/5 at the adapter-default maxTokens while
|
|
117
|
+
* every standard-family schema failed 11/11.
|
|
118
|
+
*/
|
|
119
|
+
const DEFAULT_BOOTSTRAP_TOOLS = ['bash', 'str_replace_editor']
|
|
120
|
+
|
|
121
|
+
function stringList(value, field) {
|
|
122
|
+
if (!Array.isArray(value) || value.length === 0 || value.some((item) => typeof item !== 'string' || item.length === 0)) {
|
|
123
|
+
throw new TypeError(`${name}: ${field} must be a non-empty array of non-empty strings`)
|
|
124
|
+
}
|
|
125
|
+
return [...new Set(value)]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function stringListOrEmpty(value, field) {
|
|
129
|
+
if (value === undefined) return []
|
|
130
|
+
return stringList(value, field)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parsePromoteOn(value) {
|
|
134
|
+
if (value === undefined || value === 'either') return PROMOTE_EVENTS.either
|
|
135
|
+
if (value === 'tool-call' || value === 'assistant-message') return PROMOTE_EVENTS[value]
|
|
136
|
+
throw new TypeError(`${name}: promoteOn must be one of "tool-call", "assistant-message", "either"; got ${JSON.stringify(value)}`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Validate the suppressed context sources. Unlike the bootstrap tool lists,
|
|
141
|
+
* an explicitly empty array is meaningful: it disables the context filter
|
|
142
|
+
* while keeping the tool bootstrap.
|
|
143
|
+
*/
|
|
144
|
+
function sourceList(value, field, fallback) {
|
|
145
|
+
if (value === undefined) return new Set(fallback)
|
|
146
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || item.length === 0)) {
|
|
147
|
+
throw new TypeError(`${name}: ${field} must be an array of non-empty strings`)
|
|
148
|
+
}
|
|
149
|
+
return new Set(value)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Validate the optional first-request output cap. `undefined` means NO cap:
|
|
154
|
+
* the Minimal tool schema anchors at the adapter-default maxTokens, and the
|
|
155
|
+
* cap's delivery is profile-package dependent (see the header note), so it is
|
|
156
|
+
* opt-in rather than the default.
|
|
157
|
+
*/
|
|
158
|
+
function optionalPositiveInt(value, field) {
|
|
159
|
+
if (value === undefined) return undefined
|
|
160
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
161
|
+
throw new TypeError(`${name}: ${field} must be a positive safe integer`)
|
|
162
|
+
}
|
|
163
|
+
return value
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function optionalBoolean(value, field) {
|
|
167
|
+
if (value === undefined) return false
|
|
168
|
+
if (typeof value !== 'boolean') throw new TypeError(`${name}: ${field} must be a boolean`)
|
|
169
|
+
return value
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Register the per-session bootstrap filters. */
|
|
173
|
+
export function apply(ctx, config) {
|
|
174
|
+
const source = config === undefined ? {} : config
|
|
175
|
+
if (typeof source !== 'object' || source === null || Array.isArray(source)) {
|
|
176
|
+
throw new TypeError(`${name}: config must be an object`)
|
|
177
|
+
}
|
|
178
|
+
const unknown = Object.keys(source).filter((key) => !ALLOWED_KEYS.has(key))
|
|
179
|
+
if (unknown.length > 0) {
|
|
180
|
+
throw new TypeError(
|
|
181
|
+
`${name}: unknown config key(s) ${unknown.join(', ')} — allowed keys: ${[...ALLOWED_KEYS].sort().join(', ')}`,
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
const bootstrapTools = stringList(source.bootstrapTools, 'bootstrapTools')
|
|
185
|
+
const promoteEvents = parsePromoteOn(source.promoteOn)
|
|
186
|
+
const bootstrapMaxTokens = optionalPositiveInt(source.bootstrapMaxTokens, 'bootstrapMaxTokens')
|
|
187
|
+
const includeSubagents = optionalBoolean(source.includeSubagents, 'includeSubagents')
|
|
188
|
+
const suppressedSources = sourceList(source.suppressedContextSources, 'suppressedContextSources', DEFAULT_SUPPRESSED_SOURCES)
|
|
189
|
+
// Core work set exposed after a compaction, before re-promotion. Empty
|
|
190
|
+
// means "no compaction recovery catalog": the session stays on the
|
|
191
|
+
// bootstrap pair until a new promotion signal.
|
|
192
|
+
const compactionTools = stringListOrEmpty(source.compactionTools, 'compactionTools')
|
|
193
|
+
|
|
194
|
+
const promotion = createEpochPromotion(promoteEvents, { includeSubagents })
|
|
195
|
+
ctx.on('session/event', (session, event) => promotion.observe(session, event))
|
|
196
|
+
|
|
197
|
+
let warned = false
|
|
198
|
+
const warnOnce = (message) => {
|
|
199
|
+
if (warned) return
|
|
200
|
+
warned = true
|
|
201
|
+
try {
|
|
202
|
+
ctx.logger.warn(message)
|
|
203
|
+
} catch {
|
|
204
|
+
// Logger unavailable — the guard exists only to avoid spamming.
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Narrow the assembled catalog to a keep-set; validate required names. */
|
|
209
|
+
const keepTools = (assembled, keep, missingAllowsFullCatalog) => {
|
|
210
|
+
const available = new Set(assembled.tools.map((tool) => tool.name))
|
|
211
|
+
const missing = [...keep].filter((toolName) => !available.has(toolName))
|
|
212
|
+
if (missing.length > 0) {
|
|
213
|
+
warnOnce(
|
|
214
|
+
`${name}: expected every phase tool; missing=${JSON.stringify(missing)} — `
|
|
215
|
+
+ (missingAllowsFullCatalog ? 'bootstrap disabled, full catalog exposed' : 'continuing with what is available'),
|
|
216
|
+
)
|
|
217
|
+
if (missingAllowsFullCatalog) return assembled
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
...assembled,
|
|
221
|
+
tools: assembled.tools.filter((tool) => keep.has(tool.name)),
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
ctx.on('system-prompt/assemble', async (_assembly, context, next) => {
|
|
226
|
+
// Downstream errors propagate untouched; only this filter's own logic is guarded.
|
|
227
|
+
const assembled = await next()
|
|
228
|
+
try {
|
|
229
|
+
const status = promotion.status(context.agent)
|
|
230
|
+
if (status.promoted) {
|
|
231
|
+
// PROMOTED: unlock the complete assembled catalog in one step.
|
|
232
|
+
return assembled
|
|
233
|
+
}
|
|
234
|
+
// Controlled phase: the bootstrap pair; after a compaction, plus the
|
|
235
|
+
// compaction work set so mid-task work can continue.
|
|
236
|
+
const { boundary } = status
|
|
237
|
+
const keep = new Set(bootstrapTools)
|
|
238
|
+
if (boundary >= 0) for (const toolName of compactionTools) keep.add(toolName)
|
|
239
|
+
return keepTools(assembled, keep, true)
|
|
240
|
+
} catch (error) {
|
|
241
|
+
// A filter bug must never brick a session: degrade to the full catalog.
|
|
242
|
+
warnOnce(`${name}: bootstrap filter failed, exposing the full catalog: ${String((error && error.message) || error)}`)
|
|
243
|
+
return assembled
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
// Optionally cap the first model request's output budget while bootstrapping.
|
|
248
|
+
// Unset (`bootstrapMaxTokens` omitted) means the adapter default flows — the
|
|
249
|
+
// Minimal tool schema anchors at 256000 without a cap (issue #11).
|
|
250
|
+
if (bootstrapMaxTokens !== undefined) {
|
|
251
|
+
// Same registration discipline as the pre-step strip below: `prepend`
|
|
252
|
+
// keeps this listener the OUTERMOST transform of the agent/request
|
|
253
|
+
// waterfall for the same registration-order reasons (loader row
|
|
254
|
+
// application is concurrent; row order alone does not decide listener
|
|
255
|
+
// order — see issue #6 and upstream PR #13), so a later listener can
|
|
256
|
+
// never override the first-round budget after we set it.
|
|
257
|
+
ctx.on('agent/request', async (payload, next) => {
|
|
258
|
+
const resolved = await next()
|
|
259
|
+
const agent = payload.agent
|
|
260
|
+
if (promotion.status(agent).promoted) {
|
|
261
|
+
// The next request's seed proposal carries the previous header's
|
|
262
|
+
// maxTokens forward, so the injected cap must be stripped explicitly —
|
|
263
|
+
// otherwise it would persist for the whole session.
|
|
264
|
+
if (resolved.maxTokens === bootstrapMaxTokens) {
|
|
265
|
+
const { maxTokens: _bootstrap, ...rest } = resolved
|
|
266
|
+
return rest
|
|
267
|
+
}
|
|
268
|
+
return resolved
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
...resolved,
|
|
272
|
+
maxTokens: bootstrapMaxTokens,
|
|
273
|
+
}
|
|
274
|
+
}, { prepend: true })
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Strip first-step injected reminders (skill catalog, AGENTS.md) during
|
|
278
|
+
// bootstrap. Because this listener is the first registered (see the inject
|
|
279
|
+
// note, the row order in agent.cordis.yml, and `prepend` below), the strip
|
|
280
|
+
// is the final waterfall transform and actually removes what later
|
|
281
|
+
// listeners inject.
|
|
282
|
+
ctx.on('agent/pre-step', async ({ agent }, next) => {
|
|
283
|
+
// Downstream errors propagate untouched; only this filter's own logic is guarded.
|
|
284
|
+
const decision = await next()
|
|
285
|
+
if (decision.kind === 'reject') return decision
|
|
286
|
+
try {
|
|
287
|
+
if (promotion.status(agent).promoted || suppressedSources.size === 0) return decision
|
|
288
|
+
if (!Array.isArray(decision.messages)) return decision
|
|
289
|
+
const kept = decision.messages.filter((message) => {
|
|
290
|
+
const kind = message?.source?.kind
|
|
291
|
+
return typeof kind !== 'string' || !suppressedSources.has(kind)
|
|
292
|
+
})
|
|
293
|
+
return kept.length === decision.messages.length ? decision : { ...decision, messages: kept }
|
|
294
|
+
} catch (error) {
|
|
295
|
+
// A filter bug must never eat context: degrade to keeping every message.
|
|
296
|
+
warnOnce(`${name}: pre-step context filter failed, keeping injected context: ${String((error && error.message) || error)}`)
|
|
297
|
+
return decision
|
|
298
|
+
}
|
|
299
|
+
}, { prepend: true })
|
|
300
|
+
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { SessionRecord } from '../sessionHistory.js';
|
|
3
|
-
/** Interaction mode of the `/resume` picker (issue #112). */
|
|
4
|
-
export type ResumePickerMode = 'list' | 'confirm-delete' | 'rename';
|
|
5
|
-
/**
|
|
6
|
-
* `/resume` session picker in the CC ModelPicker style: a Pane with the
|
|
7
|
-
* recent sessions as Select rows (title + time description, ✓ on the
|
|
8
|
-
* current session), plus the hint line. Only WINDOW rows render; the window
|
|
9
|
-
* follows the focused row, with `↑ N more` / `↓ N more` markers at the
|
|
10
|
-
* edges.
|
|
11
|
-
*
|
|
12
|
-
* Beyond plain selection (pi-tui style session management, issue #112):
|
|
13
|
-
* - `confirm-delete` replaces the hints with a delete confirmation for the
|
|
14
|
-
* focused session (Enter deletes, Esc backs out);
|
|
15
|
-
* - `rename` shows an inline SearchBox editing the focused session's title
|
|
16
|
-
* (Enter saves, Esc discards).
|
|
17
|
-
* Keyboard handling lives in the caller (Chat).
|
|
18
|
-
*/
|
|
19
|
-
export declare function ResumePicker({ sessions, focusIndex, currentSessionId, mode, renameText, }: {
|
|
20
|
-
sessions: readonly SessionRecord[];
|
|
21
|
-
focusIndex: number;
|
|
22
|
-
currentSessionId: string;
|
|
23
|
-
mode: ResumePickerMode;
|
|
24
|
-
renameText: string;
|
|
25
|
-
}): React.ReactNode;
|
|
26
|
-
//# sourceMappingURL=ResumePicker.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ResumePicker.d.ts","sourceRoot":"","sources":["../../../src/components/ResumePicker.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAuBzD,6DAA6D;AAC7D,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,gBAAgB,GAAG,QAAQ,CAAA;AAEnE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,UAAU,EACV,gBAAgB,EAChB,IAAI,EACJ,UAAU,GACX,EAAE;IACD,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAA;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,CAAA;IACxB,IAAI,EAAE,gBAAgB,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;CACnB,GAAG,KAAK,CAAC,SAAS,CAsElB"}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from '../ui.js';
|
|
3
|
-
import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js';
|
|
4
|
-
import { t, getLang } from '../i18n.js';
|
|
5
|
-
import { Pane } from './design-system/Pane.js';
|
|
6
|
-
import { ListItem } from './design-system/ListItem.js';
|
|
7
|
-
import { HintLine } from './design-system/HintLine.js';
|
|
8
|
-
import { SearchBox } from './SearchBox.js';
|
|
9
|
-
import { modLabel } from '../utils/modifiers.js';
|
|
10
|
-
/** Compact timestamp like `Jan 2, 03:04` (en) / `1月2日 03:04` (zh). */
|
|
11
|
-
function formatTimestamp(ms) {
|
|
12
|
-
const date = new Date(ms);
|
|
13
|
-
return date.toLocaleString(getLang() === 'zh' ? 'zh-CN' : 'en-US', {
|
|
14
|
-
month: 'short',
|
|
15
|
-
day: 'numeric',
|
|
16
|
-
hour: '2-digit',
|
|
17
|
-
minute: '2-digit',
|
|
18
|
-
hour12: false,
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
/** Visible list window: the picker scrolls instead of dumping every session
|
|
22
|
-
* (long histories would fill the whole screen otherwise). */
|
|
23
|
-
const WINDOW = 8;
|
|
24
|
-
/**
|
|
25
|
-
* `/resume` session picker in the CC ModelPicker style: a Pane with the
|
|
26
|
-
* recent sessions as Select rows (title + time description, ✓ on the
|
|
27
|
-
* current session), plus the hint line. Only WINDOW rows render; the window
|
|
28
|
-
* follows the focused row, with `↑ N more` / `↓ N more` markers at the
|
|
29
|
-
* edges.
|
|
30
|
-
*
|
|
31
|
-
* Beyond plain selection (pi-tui style session management, issue #112):
|
|
32
|
-
* - `confirm-delete` replaces the hints with a delete confirmation for the
|
|
33
|
-
* focused session (Enter deletes, Esc backs out);
|
|
34
|
-
* - `rename` shows an inline SearchBox editing the focused session's title
|
|
35
|
-
* (Enter saves, Esc discards).
|
|
36
|
-
* Keyboard handling lives in the caller (Chat).
|
|
37
|
-
*/
|
|
38
|
-
export function ResumePicker({ sessions, focusIndex, currentSessionId, mode, renameText, }) {
|
|
39
|
-
const isTerminalFocused = useTerminalFocus();
|
|
40
|
-
const start = Math.max(0, Math.min(focusIndex - Math.floor(WINDOW / 2), sessions.length - WINDOW));
|
|
41
|
-
const visible = sessions.slice(start, start + WINDOW);
|
|
42
|
-
const above = start;
|
|
43
|
-
const below = Math.max(0, sessions.length - (start + WINDOW));
|
|
44
|
-
const focused = sessions[focusIndex];
|
|
45
|
-
return (_jsxs(Pane, { color: "permission", children: [_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "remember", bold: true, children: t('resume-title') }) }), above > 0 && (_jsx(Text, { dimColor: true, italic: true, children: t('resume-more-above', { n: above }) })), visible.map(session => (_jsx(ListItem, { isFocused: session.id === sessions[focusIndex]?.id, isSelected: session.id === currentSessionId, description: formatTimestamp(session.updatedAt), children: session.title || session.id }, session.id))), below > 0 && (_jsx(Text, { dimColor: true, italic: true, children: t('resume-more-below', { n: below }) })), mode === 'confirm-delete' && focused !== undefined && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "error", children: t('resume-delete-confirm', { name: focused.title || focused.id }) }) })), mode === 'rename' && (_jsx(Box, { marginTop: 1, children: _jsx(SearchBox, { query: renameText, isFocused: true, isTerminalFocused: isTerminalFocused, placeholder: t('resume-rename-placeholder'), prefix: "\u270E", borderless: true }) }))] }), _jsx(Text, { dimColor: true, italic: true, children: _jsx(HintLine, { text: mode === 'list'
|
|
46
|
-
? t('resume-hint-list', { mod: modLabel })
|
|
47
|
-
: mode === 'confirm-delete'
|
|
48
|
-
? t('resume-hint-delete')
|
|
49
|
-
: t('resume-hint-rename') }) })] }));
|
|
50
|
-
}
|