@shuind/dsh-codex-harness 0.1.20 → 0.1.21
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 +3 -2
- package/lib/client.js +87 -5
- package/lib/index.js +21 -8
- package/lib/types/client/index.d.ts +3 -1
- package/lib/types/client/index.js +88 -15
- package/lib/types/index.js +2 -2
- package/lib/types/patch.d.ts +1 -1
- package/lib/types/patch.js +27 -4
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
## 功能
|
|
6
6
|
|
|
7
7
|
- **精简 Codex coding-agent 提示词**:使用精简版系统提示词;身份、模型和工作目录由 `dsh-persona` 提供,避免重复的 Harness 身份说明,同时保留必要的工具契约和编码工作流。
|
|
8
|
-
- **Codex 工具**:`exec_command`、`write_stdin`、`apply_patch`、`update_plan
|
|
8
|
+
- **Codex 工具**:`exec_command`、`write_stdin`、`apply_patch`、`update_plan`;`apply_patch` 匹配失败时会显示文件、hunk、行号、可见空白和附近文件内容,便于修正上下文。
|
|
9
9
|
- **GPT 能力补全**:为 GPT 系列模型补充图片输入和思考强度选项;不会覆盖用户已有的显式配置。
|
|
10
10
|
- **Fast**:仅在 Codex preset 的模型选择菜单中显示,开启后向 Responses 请求发送 `service_tier: "priority"`。
|
|
11
|
+
- **旧版 Web 兼容**:在未提供新版模型设置/上下文设置 slot 的 DSH Web 中,Fast 和上下文容量会自动回退到旧版 composer slot;client 注入不依赖纯类型 slot 包。
|
|
11
12
|
- **远程搜索与压缩**:OpenAI Responses 请求默认优先使用 hosted `web_search` 和 `/responses/compact`;失败时回退到 DSH 的本地实现。
|
|
12
13
|
- **上下文容量**:在 Web 中设置 `1K`–`1000K` tokens,设置值作用于下一次请求。
|
|
13
14
|
- **活动状态**:Codex 等待模型响应或进行上下文压缩时,在输入框上方显示对应状态和耗时,让长时间 Deep Diving 不再像卡住。
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
## 安装
|
|
16
17
|
|
|
17
18
|
```sh
|
|
18
|
-
dsh plugin --profile web add @shuind/dsh-codex-harness@0.1.
|
|
19
|
+
dsh plugin --profile web add @shuind/dsh-codex-harness@0.1.21
|
|
19
20
|
```
|
|
20
21
|
|
|
21
22
|
重启 Web,创建新会话,在模式菜单中选择 **Codex 模式**。
|
package/lib/client.js
CHANGED
|
@@ -28,6 +28,11 @@ window.__ModuleLoader__.load({
|
|
|
28
28
|
/** Browser controls for the Codex request settings mounted by the Host plugin. */
|
|
29
29
|
const NS = "codex";
|
|
30
30
|
const SETTINGS_NAMESPACE = "codex";
|
|
31
|
+
const MODEL_SETTINGS_SLOT = "conversation.input.model.settings";
|
|
32
|
+
const CONTEXT_SETTINGS_SLOT = "conversation.input.context.settings";
|
|
33
|
+
const FAST_FALLBACK_SLOT = "conversation.input.right";
|
|
34
|
+
const CONTEXT_FALLBACK_SLOT = "conversation.input.dock";
|
|
35
|
+
const DEFAULT_CONTEXT_WINDOW = 262144;
|
|
31
36
|
const en = {
|
|
32
37
|
fast: "Fast mode",
|
|
33
38
|
fastOn: "Fast mode on (priority tier)",
|
|
@@ -95,6 +100,50 @@ window.__ModuleLoader__.load({
|
|
|
95
100
|
})]
|
|
96
101
|
});
|
|
97
102
|
}
|
|
103
|
+
/** The legacy composer seat is a compact inline row, not a menu panel. */
|
|
104
|
+
function FastModeFallback(props) {
|
|
105
|
+
const { sessionId, useSessions, useSettings, setSetting, t } = props;
|
|
106
|
+
const agentPreset = useSessions((state) => state.byId[sessionId]?.agentPreset);
|
|
107
|
+
const snapshot = useSettings((state) => state);
|
|
108
|
+
const fast = snapshot.value?.fast ?? false;
|
|
109
|
+
if (agentPreset !== "codex") return null;
|
|
110
|
+
return (0, react_jsx_runtime.jsxs)("button", {
|
|
111
|
+
type: "button",
|
|
112
|
+
role: "switch",
|
|
113
|
+
disabled: snapshot.writable === false,
|
|
114
|
+
"aria-checked": fast,
|
|
115
|
+
"aria-label": fast ? t("fastOn") : t("fastOff"),
|
|
116
|
+
title: fast ? t("fastOn") : t("fastOff"),
|
|
117
|
+
onClick: () => {
|
|
118
|
+
setSetting("fast", !fast);
|
|
119
|
+
},
|
|
120
|
+
style: {
|
|
121
|
+
boxSizing: "border-box",
|
|
122
|
+
display: "inline-flex",
|
|
123
|
+
alignItems: "center",
|
|
124
|
+
gap: 5,
|
|
125
|
+
height: 28,
|
|
126
|
+
border: "1px solid var(--dsw-alias-border-primary)",
|
|
127
|
+
borderRadius: 7,
|
|
128
|
+
padding: "0 8px",
|
|
129
|
+
color: fast ? "var(--dsw-static-blue-500)" : "var(--dsw-alias-label-secondary)",
|
|
130
|
+
background: fast ? "var(--dsw-alias-interactive-bg-selected)" : "transparent",
|
|
131
|
+
cursor: snapshot.writable === false ? "default" : "pointer",
|
|
132
|
+
font: "inherit",
|
|
133
|
+
fontSize: 12,
|
|
134
|
+
lineHeight: "20px",
|
|
135
|
+
fontWeight: fast ? 600 : 400,
|
|
136
|
+
whiteSpace: "nowrap"
|
|
137
|
+
},
|
|
138
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: t("fast") }), (0, react_jsx_runtime.jsx)("span", {
|
|
139
|
+
style: {
|
|
140
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
141
|
+
fontSize: 11
|
|
142
|
+
},
|
|
143
|
+
children: fast ? t("fastStateOn") : t("fastStateOff")
|
|
144
|
+
})]
|
|
145
|
+
});
|
|
146
|
+
}
|
|
98
147
|
function ContextSizeControl({ contextWindow, useSettings, setSetting, unsetSetting, t }) {
|
|
99
148
|
const snapshot = useSettings((state) => state);
|
|
100
149
|
const configured = snapshot.value?.contextWindow;
|
|
@@ -211,6 +260,16 @@ window.__ModuleLoader__.load({
|
|
|
211
260
|
]
|
|
212
261
|
});
|
|
213
262
|
}
|
|
263
|
+
function contextWindowFromProjection(useProjection) {
|
|
264
|
+
const pressure = useProjection("contextPressure");
|
|
265
|
+
return pressure?.contextWindow !== void 0 && pressure.contextWindow > 0 ? pressure.contextWindow : DEFAULT_CONTEXT_WINDOW;
|
|
266
|
+
}
|
|
267
|
+
function LegacyContextSizeControl({ useProjection, ...props }) {
|
|
268
|
+
return (0, react_jsx_runtime.jsx)(ContextSizeControl, {
|
|
269
|
+
...props,
|
|
270
|
+
contextWindow: contextWindowFromProjection(useProjection)
|
|
271
|
+
});
|
|
272
|
+
}
|
|
214
273
|
function elapsedSeconds(startedAt, now) {
|
|
215
274
|
return Math.max(0, Math.floor((now - startedAt) / 1e3));
|
|
216
275
|
}
|
|
@@ -275,7 +334,15 @@ window.__ModuleLoader__.load({
|
|
|
275
334
|
"locale",
|
|
276
335
|
"settingsScope"
|
|
277
336
|
];
|
|
278
|
-
/**
|
|
337
|
+
/** Select a preferred slot when the host declares it, otherwise use a stable legacy composer seat. */
|
|
338
|
+
function chooseDeclaredSlot(preferred, fallback, isDeclared) {
|
|
339
|
+
return isDeclared(preferred) ? preferred : fallback;
|
|
340
|
+
}
|
|
341
|
+
function slotIsDeclared(ctx, slot) {
|
|
342
|
+
const registry = ctx.slots;
|
|
343
|
+
return typeof registry.spec === "function" && registry.spec.call(ctx.slots, slot) !== void 0;
|
|
344
|
+
}
|
|
345
|
+
/** Mount Codex controls in the newest named seats, with legacy composer fallbacks. */
|
|
279
346
|
function apply(ctx) {
|
|
280
347
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
281
348
|
en,
|
|
@@ -287,20 +354,34 @@ window.__ModuleLoader__.load({
|
|
|
287
354
|
setSetting: (field, value) => settings.set(field, value),
|
|
288
355
|
unsetSetting: (field) => settings.unset(field)
|
|
289
356
|
});
|
|
290
|
-
ctx.slots.inject(
|
|
291
|
-
name:
|
|
357
|
+
if (chooseDeclaredSlot(MODEL_SETTINGS_SLOT, FAST_FALLBACK_SLOT, (slot) => slotIsDeclared(ctx, slot)) === MODEL_SETTINGS_SLOT) ctx.slots.inject(MODEL_SETTINGS_SLOT, () => ctx.slots.register({
|
|
358
|
+
name: MODEL_SETTINGS_SLOT,
|
|
292
359
|
id: "codex-fast",
|
|
293
360
|
order: 0,
|
|
294
361
|
locale: NS,
|
|
295
362
|
inject: injected
|
|
296
363
|
}, FastModeButton));
|
|
297
|
-
ctx.slots.inject(
|
|
298
|
-
name:
|
|
364
|
+
else ctx.slots.inject(FAST_FALLBACK_SLOT, () => ctx.slots.register({
|
|
365
|
+
name: FAST_FALLBACK_SLOT,
|
|
366
|
+
id: "codex-fast-fallback",
|
|
367
|
+
order: 0,
|
|
368
|
+
locale: NS,
|
|
369
|
+
inject: injected
|
|
370
|
+
}, FastModeFallback));
|
|
371
|
+
if (chooseDeclaredSlot(CONTEXT_SETTINGS_SLOT, CONTEXT_FALLBACK_SLOT, (slot) => slotIsDeclared(ctx, slot)) === CONTEXT_SETTINGS_SLOT) ctx.slots.inject(CONTEXT_SETTINGS_SLOT, () => ctx.slots.register({
|
|
372
|
+
name: CONTEXT_SETTINGS_SLOT,
|
|
299
373
|
id: "codex-context-size",
|
|
300
374
|
order: 0,
|
|
301
375
|
locale: NS,
|
|
302
376
|
inject: injected
|
|
303
377
|
}, ContextSizeControl));
|
|
378
|
+
else ctx.slots.inject(CONTEXT_FALLBACK_SLOT, () => ctx.slots.register({
|
|
379
|
+
name: CONTEXT_FALLBACK_SLOT,
|
|
380
|
+
id: "codex-context-size-fallback",
|
|
381
|
+
order: 0,
|
|
382
|
+
locale: NS,
|
|
383
|
+
inject: injected
|
|
384
|
+
}, LegacyContextSizeControl));
|
|
304
385
|
ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register({
|
|
305
386
|
name: "conversation.composer.dock",
|
|
306
387
|
id: "codex-activity",
|
|
@@ -315,6 +396,7 @@ window.__ModuleLoader__.load({
|
|
|
315
396
|
//#endregion
|
|
316
397
|
exports.ActivityLine = ActivityLine;
|
|
317
398
|
exports.apply = apply;
|
|
399
|
+
exports.chooseDeclaredSlot = chooseDeclaredSlot;
|
|
318
400
|
exports.default = client_default;
|
|
319
401
|
exports.inject = inject;
|
|
320
402
|
return module.exports;
|
package/lib/index.js
CHANGED
|
@@ -177,17 +177,30 @@ function findSequence(lines, expected, from, endOfFile) {
|
|
|
177
177
|
}
|
|
178
178
|
return -1;
|
|
179
179
|
}
|
|
180
|
+
function displayPatchLine(line) {
|
|
181
|
+
if (line.length === 0) return "<empty>";
|
|
182
|
+
return line.replaceAll(" ", "[space]").replaceAll(" ", "[tab]");
|
|
183
|
+
}
|
|
184
|
+
function formatPatchExcerpt(lines, start, count) {
|
|
185
|
+
if (lines.length === 0) return " <empty file>";
|
|
186
|
+
const first = Math.max(0, Math.min(start, lines.length - 1));
|
|
187
|
+
return lines.slice(first, first + count).map((line, offset) => ` ${first + offset + 1}: ${displayPatchLine(line)}`).join("\n");
|
|
188
|
+
}
|
|
189
|
+
function mismatchMessage(label, hunkIndex, expected, actual, from, endOfFile) {
|
|
190
|
+
const expectedPreview = expected.slice(0, 12).map((line, index) => ` ${index + 1}: ${displayPatchLine(line)}`).join("\n");
|
|
191
|
+
const expectedSuffix = expected.length > 12 ? "\n ..." : "";
|
|
192
|
+
const excerptStart = endOfFile ? Math.max(0, actual.length - 6) : Math.max(0, from - 2);
|
|
193
|
+
const markerHint = expected.some((wanted) => wanted.length > 0 && actual.includes(`-${wanted}`)) ? "\nHint: if a source line starts with a patch marker, repeat that marker after the operation marker; for example, `-- item` deletes the source line `- item`." : "";
|
|
194
|
+
return `could not find expected lines in ${label} hunk ${hunkIndex + 1} (search starts at line ${from + 1})\nExpected:\n${expectedPreview}${expectedSuffix}\nFile excerpt:\n${formatPatchExcerpt(actual, excerptStart, 10)}${markerHint}`;
|
|
195
|
+
}
|
|
180
196
|
/** Apply parsed update hunks and return LF-normalized text. */
|
|
181
|
-
function applyPatchHunks(original, hunks) {
|
|
197
|
+
function applyPatchHunks(original, hunks, label = "file") {
|
|
182
198
|
const value = splitText(original.replaceAll("\r\n", "\n"));
|
|
183
199
|
let cursor = 0;
|
|
184
|
-
for (const hunk of hunks) {
|
|
200
|
+
for (const [hunkIndex, hunk] of hunks.entries()) {
|
|
185
201
|
const expected = hunk.lines.filter((line) => line.kind !== "add").map((line) => line.text);
|
|
186
202
|
const start = findSequence(value.lines, expected, cursor, hunk.endOfFile);
|
|
187
|
-
if (start < 0)
|
|
188
|
-
const detail = expected.join("\n");
|
|
189
|
-
invalid(`could not find expected lines${detail.length === 0 ? "" : `:\n${detail}`}`);
|
|
190
|
-
}
|
|
203
|
+
if (start < 0) invalid(mismatchMessage(label, hunkIndex, expected, value.lines, cursor, hunk.endOfFile));
|
|
191
204
|
const replacement = hunk.lines.filter((line) => line.kind !== "delete").map((line) => line.text);
|
|
192
205
|
value.lines.splice(start, expected.length, ...replacement);
|
|
193
206
|
cursor = start + replacement.length;
|
|
@@ -1006,7 +1019,7 @@ function buildCodexSystemPrompt(config = {}) {
|
|
|
1006
1019
|
}
|
|
1007
1020
|
const EXEC_COMMAND_DESCRIPTION = "Runs a command in a PTY, returning output or a session ID for ongoing interaction.";
|
|
1008
1021
|
const WRITE_STDIN_DESCRIPTION = "Writes characters to an existing unified exec session and returns recent output.";
|
|
1009
|
-
const APPLY_PATCH_DESCRIPTION = "Edits files using
|
|
1022
|
+
const APPLY_PATCH_DESCRIPTION = "Edits files using Codex patch syntax with Begin/End Patch markers and file update directives. In hunk lines, the first character is the operation marker; repeat a source-leading marker when the source line itself starts with one.";
|
|
1010
1023
|
const UPDATE_PLAN_DESCRIPTION = "Updates the task plan.\nProvide an optional explanation and a list of plan items, each with a step and status.\nAt most one step can be in_progress at a time.";
|
|
1011
1024
|
const PLAN_STATUSES = [
|
|
1012
1025
|
"pending",
|
|
@@ -1064,7 +1077,7 @@ async function applyOnePatch(ctx, file, exec, policy) {
|
|
|
1064
1077
|
}
|
|
1065
1078
|
const sourceInfo = await observedTarget(ctx, target, exec);
|
|
1066
1079
|
const original = await ctx.fs.readText(target, exec.signal);
|
|
1067
|
-
const updated = file.kind === "delete" ? void 0 : applyPatchHunks(original, file.hunks);
|
|
1080
|
+
const updated = file.kind === "delete" ? void 0 : applyPatchHunks(original, file.hunks, file.path);
|
|
1068
1081
|
if (file.kind === "delete") {
|
|
1069
1082
|
await deletePatchedFile(ctx, target, sourceInfo.version, exec, policy);
|
|
1070
1083
|
return {
|
|
@@ -35,7 +35,9 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
35
35
|
type ActivityProps = PropsRuntime<'conversation.composer.dock'> & PropsLocale<'codex'>;
|
|
36
36
|
export declare function ActivityLine({ sessionId, useSession, useSessions, useProjection, t }: ActivityProps): import("react").JSX.Element | null;
|
|
37
37
|
export declare const inject: string[];
|
|
38
|
-
/**
|
|
38
|
+
/** Select a preferred slot when the host declares it, otherwise use a stable legacy composer seat. */
|
|
39
|
+
export declare function chooseDeclaredSlot(preferred: string, fallback: string, isDeclared: (slot: string) => boolean): string;
|
|
40
|
+
/** Mount Codex controls in the newest named seats, with legacy composer fallbacks. */
|
|
39
41
|
export declare function apply(ctx: Context): void;
|
|
40
42
|
declare const _default: {
|
|
41
43
|
inject: string[];
|
|
@@ -5,6 +5,11 @@ import { CODEX_CONTEXT_MAX, CODEX_CONTEXT_UNIT, CODEX_PRESET_ID } from "../conte
|
|
|
5
5
|
import { awaitingModelStartedAt } from "./activity.js";
|
|
6
6
|
const NS = 'codex';
|
|
7
7
|
const SETTINGS_NAMESPACE = 'codex';
|
|
8
|
+
const MODEL_SETTINGS_SLOT = 'conversation.input.model.settings';
|
|
9
|
+
const CONTEXT_SETTINGS_SLOT = 'conversation.input.context.settings';
|
|
10
|
+
const FAST_FALLBACK_SLOT = 'conversation.input.right';
|
|
11
|
+
const CONTEXT_FALLBACK_SLOT = 'conversation.input.dock';
|
|
12
|
+
const DEFAULT_CONTEXT_WINDOW = 262_144;
|
|
8
13
|
const en = {
|
|
9
14
|
fast: 'Fast mode',
|
|
10
15
|
fastOn: 'Fast mode on (priority tier)',
|
|
@@ -55,6 +60,33 @@ function FastModeButton({ sessionId, useSessions, useSettings, setSetting, t })
|
|
|
55
60
|
textAlign: 'left',
|
|
56
61
|
}, children: [_jsx("span", { children: t('fast') }), _jsx("span", { style: { color: 'var(--dsw-alias-label-tertiary)', fontSize: 12 }, children: fast ? t('fastStateOn') : t('fastStateOff') })] }));
|
|
57
62
|
}
|
|
63
|
+
/** The legacy composer seat is a compact inline row, not a menu panel. */
|
|
64
|
+
function FastModeFallback(props) {
|
|
65
|
+
const { sessionId, useSessions, useSettings, setSetting, t } = props;
|
|
66
|
+
const agentPreset = useSessions(state => state.byId[sessionId]?.agentPreset);
|
|
67
|
+
const snapshot = useSettings(state => state);
|
|
68
|
+
const fast = snapshot.value?.fast ?? false;
|
|
69
|
+
if (agentPreset !== CODEX_PRESET_ID)
|
|
70
|
+
return null;
|
|
71
|
+
return (_jsxs("button", { type: "button", role: "switch", disabled: snapshot.writable === false, "aria-checked": fast, "aria-label": fast ? t('fastOn') : t('fastOff'), title: fast ? t('fastOn') : t('fastOff'), onClick: () => { void setSetting('fast', !fast); }, style: {
|
|
72
|
+
boxSizing: 'border-box',
|
|
73
|
+
display: 'inline-flex',
|
|
74
|
+
alignItems: 'center',
|
|
75
|
+
gap: 5,
|
|
76
|
+
height: 28,
|
|
77
|
+
border: '1px solid var(--dsw-alias-border-primary)',
|
|
78
|
+
borderRadius: 7,
|
|
79
|
+
padding: '0 8px',
|
|
80
|
+
color: fast ? 'var(--dsw-static-blue-500)' : 'var(--dsw-alias-label-secondary)',
|
|
81
|
+
background: fast ? 'var(--dsw-alias-interactive-bg-selected)' : 'transparent',
|
|
82
|
+
cursor: snapshot.writable === false ? 'default' : 'pointer',
|
|
83
|
+
font: 'inherit',
|
|
84
|
+
fontSize: 12,
|
|
85
|
+
lineHeight: '20px',
|
|
86
|
+
fontWeight: fast ? 600 : 400,
|
|
87
|
+
whiteSpace: 'nowrap',
|
|
88
|
+
}, children: [_jsx("span", { children: t('fast') }), _jsx("span", { style: { color: 'var(--dsw-alias-label-tertiary)', fontSize: 11 }, children: fast ? t('fastStateOn') : t('fastStateOff') })] }));
|
|
89
|
+
}
|
|
58
90
|
function ContextSizeControl({ contextWindow, useSettings, setSetting, unsetSetting, t }) {
|
|
59
91
|
const snapshot = useSettings(state => state);
|
|
60
92
|
const configured = snapshot.value?.contextWindow;
|
|
@@ -90,6 +122,15 @@ function ContextSizeControl({ contextWindow, useSettings, setSetting, unsetSetti
|
|
|
90
122
|
fontSize: 11,
|
|
91
123
|
}, children: t('contextRestore') }))] }));
|
|
92
124
|
}
|
|
125
|
+
function contextWindowFromProjection(useProjection) {
|
|
126
|
+
const pressure = useProjection('contextPressure');
|
|
127
|
+
return pressure?.contextWindow !== undefined && pressure.contextWindow > 0
|
|
128
|
+
? pressure.contextWindow
|
|
129
|
+
: DEFAULT_CONTEXT_WINDOW;
|
|
130
|
+
}
|
|
131
|
+
function LegacyContextSizeControl({ useProjection, ...props }) {
|
|
132
|
+
return _jsx(ContextSizeControl, { ...props, contextWindow: contextWindowFromProjection(useProjection) });
|
|
133
|
+
}
|
|
93
134
|
function elapsedSeconds(startedAt, now) {
|
|
94
135
|
return Math.max(0, Math.floor((now - startedAt) / 1_000));
|
|
95
136
|
}
|
|
@@ -138,7 +179,15 @@ export function ActivityLine({ sessionId, useSession, useSessions, useProjection
|
|
|
138
179
|
} }), _jsx("span", { children: label }), _jsxs("span", { style: { color: 'var(--dsw-alias-label-tertiary)' }, children: ["\u00B7 ", elapsedSeconds(activity.startedAt, now), "s"] })] }));
|
|
139
180
|
}
|
|
140
181
|
export const inject = ['slots', 'locale', 'settingsScope'];
|
|
141
|
-
/**
|
|
182
|
+
/** Select a preferred slot when the host declares it, otherwise use a stable legacy composer seat. */
|
|
183
|
+
export function chooseDeclaredSlot(preferred, fallback, isDeclared) {
|
|
184
|
+
return isDeclared(preferred) ? preferred : fallback;
|
|
185
|
+
}
|
|
186
|
+
function slotIsDeclared(ctx, slot) {
|
|
187
|
+
const registry = ctx.slots;
|
|
188
|
+
return typeof registry.spec === 'function' && registry.spec.call(ctx.slots, slot) !== undefined;
|
|
189
|
+
}
|
|
190
|
+
/** Mount Codex controls in the newest named seats, with legacy composer fallbacks. */
|
|
142
191
|
export function apply(ctx) {
|
|
143
192
|
ctx.effect(() => ctx.locale.register(NS, { en, zh }), 'codex client: dictionaries');
|
|
144
193
|
const settings = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE });
|
|
@@ -147,20 +196,44 @@ export function apply(ctx) {
|
|
|
147
196
|
setSetting: (field, value) => settings.set(field, value),
|
|
148
197
|
unsetSetting: (field) => settings.unset(field),
|
|
149
198
|
});
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
199
|
+
const fastSlot = chooseDeclaredSlot(MODEL_SETTINGS_SLOT, FAST_FALLBACK_SLOT, slot => slotIsDeclared(ctx, slot));
|
|
200
|
+
if (fastSlot === MODEL_SETTINGS_SLOT) {
|
|
201
|
+
ctx.slots.inject(MODEL_SETTINGS_SLOT, () => ctx.slots.register({
|
|
202
|
+
name: MODEL_SETTINGS_SLOT,
|
|
203
|
+
id: 'codex-fast',
|
|
204
|
+
order: 0,
|
|
205
|
+
locale: NS,
|
|
206
|
+
inject: injected,
|
|
207
|
+
}, FastModeButton));
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
ctx.slots.inject(FAST_FALLBACK_SLOT, () => ctx.slots.register({
|
|
211
|
+
name: FAST_FALLBACK_SLOT,
|
|
212
|
+
id: 'codex-fast-fallback',
|
|
213
|
+
order: 0,
|
|
214
|
+
locale: NS,
|
|
215
|
+
inject: injected,
|
|
216
|
+
}, FastModeFallback));
|
|
217
|
+
}
|
|
218
|
+
const contextSlot = chooseDeclaredSlot(CONTEXT_SETTINGS_SLOT, CONTEXT_FALLBACK_SLOT, slot => slotIsDeclared(ctx, slot));
|
|
219
|
+
if (contextSlot === CONTEXT_SETTINGS_SLOT) {
|
|
220
|
+
ctx.slots.inject(CONTEXT_SETTINGS_SLOT, () => ctx.slots.register({
|
|
221
|
+
name: CONTEXT_SETTINGS_SLOT,
|
|
222
|
+
id: 'codex-context-size',
|
|
223
|
+
order: 0,
|
|
224
|
+
locale: NS,
|
|
225
|
+
inject: injected,
|
|
226
|
+
}, ContextSizeControl));
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
ctx.slots.inject(CONTEXT_FALLBACK_SLOT, () => ctx.slots.register({
|
|
230
|
+
name: CONTEXT_FALLBACK_SLOT,
|
|
231
|
+
id: 'codex-context-size-fallback',
|
|
232
|
+
order: 0,
|
|
233
|
+
locale: NS,
|
|
234
|
+
inject: injected,
|
|
235
|
+
}, LegacyContextSizeControl));
|
|
236
|
+
}
|
|
164
237
|
ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
|
|
165
238
|
name: 'conversation.composer.dock',
|
|
166
239
|
id: 'codex-activity',
|
package/lib/types/index.js
CHANGED
|
@@ -196,7 +196,7 @@ export function buildCodexSystemPrompt(config = {}) {
|
|
|
196
196
|
}
|
|
197
197
|
const EXEC_COMMAND_DESCRIPTION = 'Runs a command in a PTY, returning output or a session ID for ongoing interaction.';
|
|
198
198
|
const WRITE_STDIN_DESCRIPTION = 'Writes characters to an existing unified exec session and returns recent output.';
|
|
199
|
-
const APPLY_PATCH_DESCRIPTION = 'Edits files using
|
|
199
|
+
const APPLY_PATCH_DESCRIPTION = 'Edits files using Codex patch syntax with Begin/End Patch markers and file update directives. In hunk lines, the first character is the operation marker; repeat a source-leading marker when the source line itself starts with one.';
|
|
200
200
|
const UPDATE_PLAN_DESCRIPTION = 'Updates the task plan.\nProvide an optional explanation and a list of plan items, each with a step and status.\nAt most one step can be in_progress at a time.';
|
|
201
201
|
const PLAN_STATUSES = ['pending', 'in_progress', 'completed'];
|
|
202
202
|
function sessionCwd(exec) {
|
|
@@ -245,7 +245,7 @@ async function applyOnePatch(ctx, file, exec, policy) {
|
|
|
245
245
|
}
|
|
246
246
|
const sourceInfo = await observedTarget(ctx, target, exec);
|
|
247
247
|
const original = await ctx.fs.readText(target, exec.signal);
|
|
248
|
-
const updated = file.kind === 'delete' ? undefined : applyPatchHunks(original, file.hunks);
|
|
248
|
+
const updated = file.kind === 'delete' ? undefined : applyPatchHunks(original, file.hunks, file.path);
|
|
249
249
|
if (file.kind === 'delete') {
|
|
250
250
|
await deletePatchedFile(ctx, target, sourceInfo.version, exec, policy);
|
|
251
251
|
return { path: file.path, operation: 'deleted' };
|
package/lib/types/patch.d.ts
CHANGED
|
@@ -32,5 +32,5 @@ export type PatchFile = {
|
|
|
32
32
|
/** Parse one complete Codex patch after normalizing CRLF input to LF. */
|
|
33
33
|
export declare function parsePatch(input: string): PatchFile[];
|
|
34
34
|
/** Apply parsed update hunks and return LF-normalized text. */
|
|
35
|
-
export declare function applyPatchHunks(original: string, hunks: readonly PatchHunk[]): string;
|
|
35
|
+
export declare function applyPatchHunks(original: string, hunks: readonly PatchHunk[], label?: string): string;
|
|
36
36
|
//# sourceMappingURL=patch.d.ts.map
|
package/lib/types/patch.js
CHANGED
|
@@ -188,16 +188,39 @@ function findSequence(lines, expected, from, endOfFile) {
|
|
|
188
188
|
}
|
|
189
189
|
return -1;
|
|
190
190
|
}
|
|
191
|
+
function displayPatchLine(line) {
|
|
192
|
+
if (line.length === 0)
|
|
193
|
+
return '<empty>';
|
|
194
|
+
return line.replaceAll(' ', '[space]').replaceAll('\t', '[tab]');
|
|
195
|
+
}
|
|
196
|
+
function formatPatchExcerpt(lines, start, count) {
|
|
197
|
+
if (lines.length === 0)
|
|
198
|
+
return ' <empty file>';
|
|
199
|
+
const first = Math.max(0, Math.min(start, lines.length - 1));
|
|
200
|
+
return lines.slice(first, first + count)
|
|
201
|
+
.map((line, offset) => ` ${first + offset + 1}: ${displayPatchLine(line)}`)
|
|
202
|
+
.join('\n');
|
|
203
|
+
}
|
|
204
|
+
function mismatchMessage(label, hunkIndex, expected, actual, from, endOfFile) {
|
|
205
|
+
const expectedPreview = expected.slice(0, 12)
|
|
206
|
+
.map((line, index) => ` ${index + 1}: ${displayPatchLine(line)}`)
|
|
207
|
+
.join('\n');
|
|
208
|
+
const expectedSuffix = expected.length > 12 ? '\n ...' : '';
|
|
209
|
+
const excerptStart = endOfFile ? Math.max(0, actual.length - 6) : Math.max(0, from - 2);
|
|
210
|
+
const markerHint = expected.some(wanted => wanted.length > 0 && actual.includes(`-${wanted}`))
|
|
211
|
+
? '\nHint: if a source line starts with a patch marker, repeat that marker after the operation marker; for example, `-- item` deletes the source line `- item`.'
|
|
212
|
+
: '';
|
|
213
|
+
return `could not find expected lines in ${label} hunk ${hunkIndex + 1} (search starts at line ${from + 1})\nExpected:\n${expectedPreview}${expectedSuffix}\nFile excerpt:\n${formatPatchExcerpt(actual, excerptStart, 10)}${markerHint}`;
|
|
214
|
+
}
|
|
191
215
|
/** Apply parsed update hunks and return LF-normalized text. */
|
|
192
|
-
export function applyPatchHunks(original, hunks) {
|
|
216
|
+
export function applyPatchHunks(original, hunks, label = 'file') {
|
|
193
217
|
const value = splitText(original.replaceAll('\r\n', '\n'));
|
|
194
218
|
let cursor = 0;
|
|
195
|
-
for (const hunk of hunks) {
|
|
219
|
+
for (const [hunkIndex, hunk] of hunks.entries()) {
|
|
196
220
|
const expected = hunk.lines.filter(line => line.kind !== 'add').map(line => line.text);
|
|
197
221
|
const start = findSequence(value.lines, expected, cursor, hunk.endOfFile);
|
|
198
222
|
if (start < 0) {
|
|
199
|
-
|
|
200
|
-
invalid(`could not find expected lines${detail.length === 0 ? '' : `:\n${detail}`}`);
|
|
223
|
+
invalid(mismatchMessage(label, hunkIndex, expected, value.lines, cursor, hunk.endOfFile));
|
|
201
224
|
}
|
|
202
225
|
const replacement = hunk.lines.filter(line => line.kind !== 'delete').map(line => line.text);
|
|
203
226
|
value.lines.splice(start, expected.length, ...replacement);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shuind/dsh-codex-harness",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "A Codex harness with a streamlined system prompt for GPT models that do not fit DSH's native interface, while remaining compatible with the DSH plugin ecosystem.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -69,8 +69,7 @@
|
|
|
69
69
|
"@deepseek-ai/dsh-client-locale",
|
|
70
70
|
"@deepseek-ai/dsh-client-runtime",
|
|
71
71
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
72
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
73
|
-
"@deepseek-ai/dsh-client-ui-slots"
|
|
72
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
74
73
|
],
|
|
75
74
|
"platform": "web"
|
|
76
75
|
}
|