dsh-acp-enhanced 0.5.0 → 0.5.2
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/lib/index.js +53 -14
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -446,8 +446,12 @@ export function apply(ctx, config) {
|
|
|
446
446
|
/** Whether one live session has produced anything yet. A preset swap is only
|
|
447
447
|
* legal while it is blank (dsh-agent-presets product rule): swapping tools
|
|
448
448
|
* mid-conversation would strand logged tool calls the new composition cannot
|
|
449
|
-
* make.
|
|
450
|
-
|
|
449
|
+
* make. Checks both the live turn marker and persisted user messages, so the
|
|
450
|
+
* same rule holds for resumed sessions whose on-disk logs may not carry
|
|
451
|
+
* `turn/start`. */
|
|
452
|
+
const isBlankSession = (session) => !session.events.some((event) => (
|
|
453
|
+
event.type === 'turn/start' || event.type === 'user/message'
|
|
454
|
+
))
|
|
451
455
|
|
|
452
456
|
/**
|
|
453
457
|
* Resolve the preset a new/resumed session will run under, and the setup hook
|
|
@@ -476,13 +480,21 @@ export function apply(ctx, config) {
|
|
|
476
480
|
}
|
|
477
481
|
}
|
|
478
482
|
|
|
479
|
-
/**
|
|
480
|
-
|
|
483
|
+
/** Persisted facts for one stored session: the preset it runs under and
|
|
484
|
+
* whether its log shows any real content. Both read from the on-disk log
|
|
485
|
+
* because a just-resumed session's in-memory event log fills asynchronously
|
|
486
|
+
* — the loadSession response would otherwise judge blank-ness before the
|
|
487
|
+
* events arrive. Returns undefined when the artifact is missing/corrupt
|
|
488
|
+
* (resume itself reports the failure; this lookup must not mask it). */
|
|
489
|
+
async function persistedFactsOf(sessionId) {
|
|
481
490
|
const persistence = ctx.get('sessionPersistence')
|
|
482
491
|
if (persistence === undefined) return undefined
|
|
483
492
|
try {
|
|
484
493
|
const { meta, events } = await persistence.load(sessionId)
|
|
485
|
-
return
|
|
494
|
+
return {
|
|
495
|
+
preset: resolveSessionPreset({ header: meta, events }),
|
|
496
|
+
blank: isBlankSession({ header: meta, events }),
|
|
497
|
+
}
|
|
486
498
|
} catch {
|
|
487
499
|
// A missing/corrupt artifact leaves resume itself to report the failure;
|
|
488
500
|
// the preset lookup must not mask it with a second, misleading error.
|
|
@@ -710,6 +722,16 @@ export function apply(ctx, config) {
|
|
|
710
722
|
case 'turn/start': {
|
|
711
723
|
record.turnCount += 1
|
|
712
724
|
record.turnStartedAt = Date.now()
|
|
725
|
+
// The preset-selection window closes the moment the session produces
|
|
726
|
+
// its first turn: re-advertise the config so the editor's
|
|
727
|
+
// agent_preset dropdown collapses to the running preset (ACP has no
|
|
728
|
+
// disabled state; without this the client keeps showing the full
|
|
729
|
+
// session/new list and offers switches the server must reject).
|
|
730
|
+
if (record.turnCount === 1) {
|
|
731
|
+
broadcastConfig(record).catch((error) => {
|
|
732
|
+
logger.warn(`acp-enhanced: config rebroadcast after first turn failed: ${String(error)}`)
|
|
733
|
+
})
|
|
734
|
+
}
|
|
713
735
|
break
|
|
714
736
|
}
|
|
715
737
|
case 'turn/end': {
|
|
@@ -1090,18 +1112,30 @@ export function apply(ctx, config) {
|
|
|
1090
1112
|
// Broken presets must not be offered: mounting one always fails.
|
|
1091
1113
|
const mountable = (await presets.list()).filter((preset) => preset.broken === undefined)
|
|
1092
1114
|
if (mountable.length > 0) {
|
|
1115
|
+
const current = runningPresetOf(record.agent.session) ?? ''
|
|
1116
|
+
// A just-resumed session's in-memory events fill asynchronously; trust
|
|
1117
|
+
// the on-disk log snapshot taken at load time when one exists.
|
|
1118
|
+
const blank = record.blankFromLog ?? isBlankSession(record.agent.session)
|
|
1093
1119
|
options.push({
|
|
1094
1120
|
id: 'agent_preset',
|
|
1095
1121
|
type: 'select',
|
|
1096
1122
|
name: 'Agent preset',
|
|
1097
|
-
description:
|
|
1123
|
+
description: blank
|
|
1124
|
+
? 'Model-facing tool/prompt composition for this session (switch only while blank).'
|
|
1125
|
+
: 'Model-facing tool/prompt composition for this session (locked: switching requires a blank session).',
|
|
1098
1126
|
category: 'model_config',
|
|
1099
|
-
currentValue:
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1127
|
+
currentValue: current,
|
|
1128
|
+
// ACP has no per-option disabled state, so a non-blank session
|
|
1129
|
+
// advertises only the running preset — the editor shows the current
|
|
1130
|
+
// mode without offering a switch the server would reject (the
|
|
1131
|
+
// setSessionConfigOption guard stays as the authoritative check).
|
|
1132
|
+
options: blank
|
|
1133
|
+
? mountable.map((preset) => ({
|
|
1134
|
+
value: preset.id,
|
|
1135
|
+
name: preset.name ?? preset.id,
|
|
1136
|
+
...preset.description === undefined ? {} : { description: preset.description },
|
|
1137
|
+
}))
|
|
1138
|
+
: [{ value: current, name: mountable.find((preset) => preset.id === current)?.name ?? (current || 'host') }],
|
|
1105
1139
|
})
|
|
1106
1140
|
}
|
|
1107
1141
|
}
|
|
@@ -1761,6 +1795,10 @@ export function apply(ctx, config) {
|
|
|
1761
1795
|
buffer: {},
|
|
1762
1796
|
thoughtBuffer: {},
|
|
1763
1797
|
contextWindow: undefined,
|
|
1798
|
+
/** Blank-ness captured from the on-disk log at load time (resumed
|
|
1799
|
+
* sessions' in-memory events fill asynchronously). Undefined for
|
|
1800
|
+
* fresh/live sessions, which judge blank-ness live. */
|
|
1801
|
+
blankFromLog: undefined,
|
|
1764
1802
|
lastUsage: undefined,
|
|
1765
1803
|
selection,
|
|
1766
1804
|
}
|
|
@@ -2021,10 +2059,10 @@ export function apply(ctx, config) {
|
|
|
2021
2059
|
configOptions: await buildConfigOptions(live),
|
|
2022
2060
|
}
|
|
2023
2061
|
}
|
|
2024
|
-
const runningPreset = await
|
|
2062
|
+
const runningPreset = await persistedFactsOf(sessionId)
|
|
2025
2063
|
let composition
|
|
2026
2064
|
try {
|
|
2027
|
-
composition = await composePreset(runningPreset)
|
|
2065
|
+
composition = await composePreset(runningPreset?.preset)
|
|
2028
2066
|
} catch (error) {
|
|
2029
2067
|
// Same mapping as session/new: a roster that cannot supply the
|
|
2030
2068
|
// session's logged preset is reported as a client mistake.
|
|
@@ -2048,6 +2086,7 @@ export function apply(ctx, config) {
|
|
|
2048
2086
|
}
|
|
2049
2087
|
const record = makeRecord(handle)
|
|
2050
2088
|
record.additionalDirectories = additionalDirectories
|
|
2089
|
+
record.blankFromLog = runningPreset?.blank
|
|
2051
2090
|
sessions.set(sessionId, record)
|
|
2052
2091
|
// Zed inserts the thread before the load RPC completes; replay the
|
|
2053
2092
|
// conversation history as notifications so the thread renders.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-acp-enhanced",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Enhanced ACP server for DeepSeek Harness: block-level streaming, usage/stat telemetry (cache hit rate, token speed, input/output tokens, context length, turns, tool timing), model & reasoning-effort switching, and permission-preset control over the ACP wire (Zed-friendly)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|