@shuind/dsh-codex-harness 0.1.19 → 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 +5 -4
- package/lib/client.js +87 -5
- package/lib/index.js +38 -20
- package/lib/types/client/index.d.ts +3 -1
- package/lib/types/client/index.js +88 -15
- package/lib/types/index.d.ts +4 -1
- package/lib/types/index.js +17 -13
- package/lib/types/patch.d.ts +1 -1
- package/lib/types/patch.js +27 -4
- package/package.json +3 -4
- package/presets/codex/agent.cordis.yml +1 -0
- package/presets/codex/preset.yml +3 -3
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# @shuind/dsh-codex-harness
|
|
2
2
|
|
|
3
|
-
在 DSH 中提供 Codex preset
|
|
3
|
+
在 DSH 中提供 Codex preset:使用精简版 Codex 系统提示词与工具契约,支持图片输入、可选思考强度、Fast 优先级请求、OpenAI Responses 网页搜索与远程压缩,以及在 Web 设置上下文容量;旨在给来源于 Codex 的 token 提供适配 harness,并兼容 DSH 生态。
|
|
4
4
|
|
|
5
5
|
## 功能
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- **Codex 工具**:`exec_command`、`write_stdin`、`apply_patch`、`update_plan
|
|
7
|
+
- **精简 Codex coding-agent 提示词**:使用精简版系统提示词;身份、模型和工作目录由 `dsh-persona` 提供,避免重复的 Harness 身份说明,同时保留必要的工具契约和编码工作流。
|
|
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;
|
|
@@ -949,9 +962,7 @@ function applyCodexRequestSettings(request, settings) {
|
|
|
949
962
|
...settings.fast ? { serviceTier: "priority" } : {}
|
|
950
963
|
};
|
|
951
964
|
}
|
|
952
|
-
const CODEX_BASE_PROMPT = String.raw
|
|
953
|
-
|
|
954
|
-
## General
|
|
965
|
+
const CODEX_BASE_PROMPT = String.raw`## General
|
|
955
966
|
|
|
956
967
|
- When searching for text or files, prefer using rg or rg --files respectively because rg is much faster than alternatives like grep. If rg is not available, use the next best alternative.
|
|
957
968
|
|
|
@@ -959,7 +970,6 @@ const CODEX_BASE_PROMPT = String.raw`You are Codex, based on {{model}}. You are
|
|
|
959
970
|
|
|
960
971
|
- Default to ASCII when editing or creating files. Only introduce non-ASCII or other Unicode characters when there is a clear justification and the file already uses them.
|
|
961
972
|
- Add succinct code comments that explain non-obvious code. Do not add comments that merely narrate assignments or control flow.
|
|
962
|
-
- Use apply_patch for single-file edits when practical. The apply_patch tool accepts its freeform patch language; do not wrap that patch in JSON.
|
|
963
973
|
- You may be in a dirty git worktree. Never revert existing changes you did not make unless the user explicitly requests it. If unrelated files are changed, leave them alone.
|
|
964
974
|
|
|
965
975
|
## Planning
|
|
@@ -970,16 +980,10 @@ const CODEX_BASE_PROMPT = String.raw`You are Codex, based on {{model}}. You are
|
|
|
970
980
|
## dsh session
|
|
971
981
|
|
|
972
982
|
- The user and you share one workspace. Inspect the repository and every applicable AGENTS.md before editing.
|
|
973
|
-
- This session's preset was selected when the session was created and stays fixed for its lifetime. Do not attempt to switch the preset or replace its tool catalog while the session is running.
|
|
974
|
-
- dsh provides the execution, filesystem, session, policy, and Skills capabilities behind these tools. Use those extension points as supplied; do not invent a second harness or bypass the filesystem service for file edits.
|
|
975
|
-
- The core Codex tool names, arguments, and result formats are fixed: use exec_command for terminal work, write_stdin for an existing interactive command, apply_patch for file changes, and update_plan for multi-step tasks.
|
|
976
983
|
|
|
977
984
|
## Task execution
|
|
978
985
|
|
|
979
986
|
- Keep the user informed with concise progress updates and lead with the result.
|
|
980
|
-
- Prefer existing functions and extension points over new machinery.
|
|
981
|
-
- Do not claim that a command, edit, or test succeeded unless it actually succeeded.
|
|
982
|
-
- Use the exact tool names and argument formats supplied by this session; do not invent replacement editing tools.
|
|
983
987
|
|
|
984
988
|
## Presenting your work
|
|
985
989
|
|
|
@@ -996,13 +1000,26 @@ const CODEX_COLLABORATION_PROMPT = String.raw`## Collaboration
|
|
|
996
1000
|
- Solve problems by thinking from first principles and at a higher level.
|
|
997
1001
|
- Make things as effortless as possible for the user.
|
|
998
1002
|
`;
|
|
999
|
-
/**
|
|
1003
|
+
/** Put the agent persona first and omit the generic Harness identity opener. */
|
|
1004
|
+
function normalizeCodexPromptAssembly(assembly) {
|
|
1005
|
+
const sections = assembly.sections.filter((section) => section.name !== "harness:identity");
|
|
1006
|
+
const persona = sections.find((section) => section.name === "deployment:persona");
|
|
1007
|
+
if (persona === void 0) return {
|
|
1008
|
+
...assembly,
|
|
1009
|
+
sections
|
|
1010
|
+
};
|
|
1011
|
+
return {
|
|
1012
|
+
...assembly,
|
|
1013
|
+
sections: [persona, ...sections.filter((section) => section.name !== "deployment:persona")]
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
/** Build the Codex operating prompt with optional, user-enabled collaboration guidance. */
|
|
1000
1017
|
function buildCodexSystemPrompt(config = {}) {
|
|
1001
1018
|
return config.collaborationPrompt === true ? `${CODEX_BASE_PROMPT}\n\n${CODEX_COLLABORATION_PROMPT}` : CODEX_BASE_PROMPT;
|
|
1002
1019
|
}
|
|
1003
1020
|
const EXEC_COMMAND_DESCRIPTION = "Runs a command in a PTY, returning output or a session ID for ongoing interaction.";
|
|
1004
1021
|
const WRITE_STDIN_DESCRIPTION = "Writes characters to an existing unified exec session and returns recent output.";
|
|
1005
|
-
const APPLY_PATCH_DESCRIPTION = "
|
|
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.";
|
|
1006
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.";
|
|
1007
1024
|
const PLAN_STATUSES = [
|
|
1008
1025
|
"pending",
|
|
@@ -1060,7 +1077,7 @@ async function applyOnePatch(ctx, file, exec, policy) {
|
|
|
1060
1077
|
}
|
|
1061
1078
|
const sourceInfo = await observedTarget(ctx, target, exec);
|
|
1062
1079
|
const original = await ctx.fs.readText(target, exec.signal);
|
|
1063
|
-
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);
|
|
1064
1081
|
if (file.kind === "delete") {
|
|
1065
1082
|
await deletePatchedFile(ctx, target, sourceInfo.version, exec, policy);
|
|
1066
1083
|
return {
|
|
@@ -1388,6 +1405,7 @@ function apply(ctx, config = {}) {
|
|
|
1388
1405
|
if ((resolved.hostedWebSearch || resolved.remoteCompact) && options.purpose === void 0 && isGptModel(options.model)) return hostedWebSearchStream(next);
|
|
1389
1406
|
return next();
|
|
1390
1407
|
}));
|
|
1408
|
+
ctx.on("system-prompt/assemble", async (_assembly, _context, next) => normalizeCodexPromptAssembly(await next()));
|
|
1391
1409
|
ctx.systemPrompt.section({
|
|
1392
1410
|
name: "codex:base",
|
|
1393
1411
|
order: 10,
|
|
@@ -1404,4 +1422,4 @@ var types_default = {
|
|
|
1404
1422
|
apply
|
|
1405
1423
|
};
|
|
1406
1424
|
//#endregion
|
|
1407
|
-
export { CODEX_SETTINGS_NAMESPACE, CODEX_SETTINGS_SCHEMA, Config, apply, applyCodexRequestSettings, buildCodexSystemPrompt, types_default as default, enrichCodexModel, inject, name };
|
|
1425
|
+
export { CODEX_SETTINGS_NAMESPACE, CODEX_SETTINGS_SCHEMA, Config, apply, applyCodexRequestSettings, buildCodexSystemPrompt, types_default as default, enrichCodexModel, inject, name, normalizeCodexPromptAssembly };
|
|
@@ -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.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Context } from '@deepseek-ai/cordis';
|
|
3
3
|
import z from '@deepseek-ai/schemastery';
|
|
4
4
|
import type { LlmCallConfig } from '@deepseek-ai/dsh-llm';
|
|
5
|
+
import type { PromptAssembly } from '@deepseek-ai/dsh-system-prompt';
|
|
5
6
|
declare module '@deepseek-ai/dsh-llm' {
|
|
6
7
|
interface LlmCallConfig {
|
|
7
8
|
/** Codex request context capacity override, in tokens. */
|
|
@@ -50,7 +51,9 @@ export interface CodexModelProfile {
|
|
|
50
51
|
export declare function enrichCodexModel(model: CodexModelProfile): CodexModelProfile;
|
|
51
52
|
/** Apply the live Codex controls to one agent request without leaking them to other routes. */
|
|
52
53
|
export declare function applyCodexRequestSettings(request: LlmCallConfig, settings: CodexSettings): LlmCallConfig;
|
|
53
|
-
/**
|
|
54
|
+
/** Put the agent persona first and omit the generic Harness identity opener. */
|
|
55
|
+
export declare function normalizeCodexPromptAssembly(assembly: PromptAssembly): PromptAssembly;
|
|
56
|
+
/** Build the Codex operating prompt with optional, user-enabled collaboration guidance. */
|
|
54
57
|
export declare function buildCodexSystemPrompt(config?: Pick<Config, 'collaborationPrompt'>): string;
|
|
55
58
|
/** Mount the Codex prompt/tool layer inside one fixed agent preset. */
|
|
56
59
|
export declare function apply(ctx: Context, config?: Config): void;
|
package/lib/types/index.js
CHANGED
|
@@ -138,9 +138,8 @@ export function applyCodexRequestSettings(request, settings) {
|
|
|
138
138
|
...settings.fast ? { serviceTier: 'priority' } : {},
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
## General
|
|
141
|
+
// The shipped persona owns the single Codex identity line, model, and working directory.
|
|
142
|
+
const CODEX_BASE_PROMPT = String.raw `## General
|
|
144
143
|
|
|
145
144
|
- When searching for text or files, prefer using rg or rg --files respectively because rg is much faster than alternatives like grep. If rg is not available, use the next best alternative.
|
|
146
145
|
|
|
@@ -148,7 +147,6 @@ const CODEX_BASE_PROMPT = String.raw `You are Codex, based on {{model}}. You are
|
|
|
148
147
|
|
|
149
148
|
- Default to ASCII when editing or creating files. Only introduce non-ASCII or other Unicode characters when there is a clear justification and the file already uses them.
|
|
150
149
|
- Add succinct code comments that explain non-obvious code. Do not add comments that merely narrate assignments or control flow.
|
|
151
|
-
- Use apply_patch for single-file edits when practical. The apply_patch tool accepts its freeform patch language; do not wrap that patch in JSON.
|
|
152
150
|
- You may be in a dirty git worktree. Never revert existing changes you did not make unless the user explicitly requests it. If unrelated files are changed, leave them alone.
|
|
153
151
|
|
|
154
152
|
## Planning
|
|
@@ -159,16 +157,10 @@ const CODEX_BASE_PROMPT = String.raw `You are Codex, based on {{model}}. You are
|
|
|
159
157
|
## dsh session
|
|
160
158
|
|
|
161
159
|
- The user and you share one workspace. Inspect the repository and every applicable AGENTS.md before editing.
|
|
162
|
-
- This session's preset was selected when the session was created and stays fixed for its lifetime. Do not attempt to switch the preset or replace its tool catalog while the session is running.
|
|
163
|
-
- dsh provides the execution, filesystem, session, policy, and Skills capabilities behind these tools. Use those extension points as supplied; do not invent a second harness or bypass the filesystem service for file edits.
|
|
164
|
-
- The core Codex tool names, arguments, and result formats are fixed: use exec_command for terminal work, write_stdin for an existing interactive command, apply_patch for file changes, and update_plan for multi-step tasks.
|
|
165
160
|
|
|
166
161
|
## Task execution
|
|
167
162
|
|
|
168
163
|
- Keep the user informed with concise progress updates and lead with the result.
|
|
169
|
-
- Prefer existing functions and extension points over new machinery.
|
|
170
|
-
- Do not claim that a command, edit, or test succeeded unless it actually succeeded.
|
|
171
|
-
- Use the exact tool names and argument formats supplied by this session; do not invent replacement editing tools.
|
|
172
164
|
|
|
173
165
|
## Presenting your work
|
|
174
166
|
|
|
@@ -185,7 +177,18 @@ const CODEX_COLLABORATION_PROMPT = String.raw `## Collaboration
|
|
|
185
177
|
- Solve problems by thinking from first principles and at a higher level.
|
|
186
178
|
- Make things as effortless as possible for the user.
|
|
187
179
|
`;
|
|
188
|
-
/**
|
|
180
|
+
/** Put the agent persona first and omit the generic Harness identity opener. */
|
|
181
|
+
export function normalizeCodexPromptAssembly(assembly) {
|
|
182
|
+
const sections = assembly.sections.filter(section => section.name !== 'harness:identity');
|
|
183
|
+
const persona = sections.find(section => section.name === 'deployment:persona');
|
|
184
|
+
if (persona === undefined)
|
|
185
|
+
return { ...assembly, sections };
|
|
186
|
+
return {
|
|
187
|
+
...assembly,
|
|
188
|
+
sections: [persona, ...sections.filter(section => section.name !== 'deployment:persona')],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/** Build the Codex operating prompt with optional, user-enabled collaboration guidance. */
|
|
189
192
|
export function buildCodexSystemPrompt(config = {}) {
|
|
190
193
|
return config.collaborationPrompt === true
|
|
191
194
|
? `${CODEX_BASE_PROMPT}\n\n${CODEX_COLLABORATION_PROMPT}`
|
|
@@ -193,7 +196,7 @@ export function buildCodexSystemPrompt(config = {}) {
|
|
|
193
196
|
}
|
|
194
197
|
const EXEC_COMMAND_DESCRIPTION = 'Runs a command in a PTY, returning output or a session ID for ongoing interaction.';
|
|
195
198
|
const WRITE_STDIN_DESCRIPTION = 'Writes characters to an existing unified exec session and returns recent output.';
|
|
196
|
-
const APPLY_PATCH_DESCRIPTION = '
|
|
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.';
|
|
197
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.';
|
|
198
201
|
const PLAN_STATUSES = ['pending', 'in_progress', 'completed'];
|
|
199
202
|
function sessionCwd(exec) {
|
|
@@ -242,7 +245,7 @@ async function applyOnePatch(ctx, file, exec, policy) {
|
|
|
242
245
|
}
|
|
243
246
|
const sourceInfo = await observedTarget(ctx, target, exec);
|
|
244
247
|
const original = await ctx.fs.readText(target, exec.signal);
|
|
245
|
-
const updated = file.kind === 'delete' ? undefined : applyPatchHunks(original, file.hunks);
|
|
248
|
+
const updated = file.kind === 'delete' ? undefined : applyPatchHunks(original, file.hunks, file.path);
|
|
246
249
|
if (file.kind === 'delete') {
|
|
247
250
|
await deletePatchedFile(ctx, target, sourceInfo.version, exec, policy);
|
|
248
251
|
return { path: file.path, operation: 'deleted' };
|
|
@@ -485,6 +488,7 @@ export function apply(ctx, config = {}) {
|
|
|
485
488
|
return next();
|
|
486
489
|
}));
|
|
487
490
|
}
|
|
491
|
+
ctx.on('system-prompt/assemble', async (_assembly, _context, next) => normalizeCodexPromptAssembly(await next()));
|
|
488
492
|
ctx.systemPrompt.section({ name: 'codex:base', order: 10, text: buildCodexSystemPrompt(resolved) });
|
|
489
493
|
registerExecTools(ctx, resolved);
|
|
490
494
|
registerPatchTool(ctx);
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shuind/dsh-codex-harness",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "0.1.21",
|
|
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",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -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
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
- id: persona
|
|
5
5
|
name: '@deepseek-ai/dsh-persona'
|
|
6
6
|
config:
|
|
7
|
+
# Keep identity, model, cwd, and runtime context in one persona section.
|
|
7
8
|
text: >-
|
|
8
9
|
You are Codex, a coding agent based on the {{model}} model. Your working directory is {{cwd}}.
|
|
9
10
|
includeRuntimeContext: true
|
package/presets/codex/preset.yml
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
name: Codex 模式
|
|
2
|
-
description:
|
|
3
|
-
order: 5
|
|
1
|
+
name: Codex 模式
|
|
2
|
+
description: 使用精简版 Codex 提示词、exec_command、write_stdin、apply_patch 和 update_plan,并保留 dsh 的 Skills 与可插拔运行时。
|
|
3
|
+
order: 5
|