@khorsheed/dsh-local-agent-codex 0.2.0 → 0.2.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/CHANGELOG.md +13 -0
- package/README.en.md +1 -1
- package/README.i18n.yaml +2 -2
- package/README.md +1 -1
- package/lib/client.js +154 -69
- package/lib/index.js +113 -16
- package/lib/types/client/SettingsCard.js +11 -3
- package/lib/types/client/index.js +2 -2
- package/lib/types/codex-cli-provider.d.ts +42 -1
- package/lib/types/codex-cli-provider.js +69 -9
- package/lib/types/index.d.ts +5 -1
- package/lib/types/index.js +7 -4
- package/lib/types/live-driver.d.ts +2 -0
- package/lib/types/live-driver.js +24 -7
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
# 变更记录
|
|
2
|
+
## 0.2.2(2026-10-27)
|
|
3
|
+
|
|
4
|
+
- **成员按会话的沙箱模式运行**:会话设成只读或可写,codex 成员跟着用同样的权限。
|
|
5
|
+
- 成员的工具调用保留结构化参数和 MCP 结果;命令跑完一定带上输出和退出码。
|
|
6
|
+
|
|
7
|
+
## 0.2.1(2026-10-27)
|
|
8
|
+
|
|
9
|
+
- **修复几个模式套件一起装时设置存不上**:卡片上的开关和设置改存在本 harness 自己的目录里,不再写进配置文件;原来配好的值照常生效。
|
|
10
|
+
- **保存失败就说保存失败**:设置被拒时显示「保存失败」,不再假装「已保存」。
|
|
11
|
+
- **常驻模式(live)默认打开**:遇到问题可以在卡片上关掉,改回每次新起一个。
|
|
12
|
+
- **端点表单跟着登录、登出刷新**:登出后不再显示「API Key 已配置」,验证端点也不再报找不到凭据。
|
|
13
|
+
- **读不到登录状态时直说**:显示「读取登录状态失败」。
|
|
14
|
+
|
|
2
15
|
## 0.2.0(2026-10-26)
|
|
3
16
|
|
|
4
17
|
rc.3 之后到正式版的变化:
|
package/README.en.md
CHANGED
|
@@ -15,7 +15,7 @@ Some jobs you simply want Codex to do, and that used to mean running it in a ter
|
|
|
15
15
|
- **Keep talking, even after a restart**: have the agent follow up, or open the sub-session and message Codex yourself; it's the same Codex thread, and it picks up again after DSH restarts.
|
|
16
16
|
- **Paste a key and go**: enter the endpoint and API key on the settings card (an official platform key or a relay key), press "Save credential", then "Verify endpoint & list models" to check it connects.
|
|
17
17
|
- **Pick your models**: under "Available models", tick and drag to reorder; the top one is the default. You can also switch model and reasoning effort inside a sub-session.
|
|
18
|
-
- **Resident mode is faster**:
|
|
18
|
+
- **Resident mode is faster**: "Resident mode (live)" is on by default and the Codex process stays up, streaming output into the sub-session; stopping doesn't kill it, and after a crash it resumes the same thread.
|
|
19
19
|
|
|
20
20
|
## Install
|
|
21
21
|
|
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# last confirmed-consistent state. Both languages carry equal authority; after
|
|
3
3
|
# editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/local-agent-codex/README.en.md
|
|
5
|
-
packages/local-agent-codex/README.en.md:
|
|
6
|
-
packages/local-agent-codex/README.md:
|
|
5
|
+
packages/local-agent-codex/README.en.md: f41756008c330d0db9d309e8f2da59ee31bacd11
|
|
6
|
+
packages/local-agent-codex/README.md: fc175adc5795b795038a39b9031310e0bc40e0d6
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
- **接着聊,重启也续得上**:让 agent 追问,或者打开子会话直接发消息,继续的是同一个 Codex 线程;DSH 重启之后也接得上。
|
|
16
16
|
- **填好 key 就能用**:在设置卡片里填端点地址和 API Key(官方平台的 key 或中转的 key 都行),点「保存凭据」,再点「验证端点并获取模型」确认能连上。
|
|
17
17
|
- **自己挑模型**:在「可用模型」里勾选、拖动排序,排第一的就是默认模型;子会话里也能临时换模型和推理强度。
|
|
18
|
-
-
|
|
18
|
+
- **常驻模式更快**:「常驻模式(live)」默认开着,Codex 进程常驻,输出实时流进子会话;停下不杀进程,崩溃后自动续上原来的线程。
|
|
19
19
|
|
|
20
20
|
## 安装
|
|
21
21
|
|
package/lib/client.js
CHANGED
|
@@ -93,6 +93,83 @@ window.__ModuleLoader__.load({
|
|
|
93
93
|
});
|
|
94
94
|
return channel;
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Bind a settings card to the harness's own card-settings store (card-settings.ts
|
|
98
|
+
* on the host) layered over the row's host settings form: reads are the
|
|
99
|
+
* form's value with the stored overrides on top; writes go to the store only,
|
|
100
|
+
* never the profile patch — so they cannot be refused when several suites
|
|
101
|
+
* insert the same row. A host without the store keeps the plain row scope.
|
|
102
|
+
* @param ctx - the provider's client context.
|
|
103
|
+
* @param harness - the harness name (`dsh`, `kimi`, …).
|
|
104
|
+
* @param entryId - the plugin row id the base values come from.
|
|
105
|
+
* @returns the bound scope.
|
|
106
|
+
*/
|
|
107
|
+
function bindCardSettingsScope(ctx, harness, entryId) {
|
|
108
|
+
const row = bindSettingsScope(ctx, entryId);
|
|
109
|
+
const gateway = () => {
|
|
110
|
+
const remote = ctx.get("remote.localAgentGateway");
|
|
111
|
+
return typeof remote?.cardSettings === "function" && typeof remote.setCardSettings === "function" ? remote : void 0;
|
|
112
|
+
};
|
|
113
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
114
|
+
let stored;
|
|
115
|
+
let cached;
|
|
116
|
+
const emit = () => {
|
|
117
|
+
for (const listener of [...listeners]) listener();
|
|
118
|
+
};
|
|
119
|
+
const load = () => {
|
|
120
|
+
gateway()?.cardSettings(harness).then((result) => {
|
|
121
|
+
if (!result.ok || result.value === void 0) return;
|
|
122
|
+
stored = result.value;
|
|
123
|
+
emit();
|
|
124
|
+
}).catch(() => {});
|
|
125
|
+
};
|
|
126
|
+
const write = async (patch) => {
|
|
127
|
+
const remote = gateway();
|
|
128
|
+
if (remote === void 0) return false;
|
|
129
|
+
try {
|
|
130
|
+
const result = await remote.setCardSettings(harness, patch);
|
|
131
|
+
if (!result.ok || result.value === void 0) return false;
|
|
132
|
+
stored = result.value;
|
|
133
|
+
emit();
|
|
134
|
+
return true;
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
getSnapshot: () => {
|
|
141
|
+
const rowSnapshot = row.getSnapshot();
|
|
142
|
+
if (cached !== void 0 && cached.rowSnapshot === rowSnapshot && cached.stored === stored) return cached.snapshot;
|
|
143
|
+
const overrides = stored ?? {};
|
|
144
|
+
const snapshot = gateway() === void 0 ? rowSnapshot : {
|
|
145
|
+
...rowSnapshot,
|
|
146
|
+
value: rowSnapshot.value === void 0 && stored === void 0 ? void 0 : {
|
|
147
|
+
...rowSnapshot.value,
|
|
148
|
+
...overrides
|
|
149
|
+
},
|
|
150
|
+
writable: true,
|
|
151
|
+
status: rowSnapshot.status === "loading" && stored !== void 0 ? "ready" : rowSnapshot.status === "unavailable" && stored !== void 0 ? "ready" : rowSnapshot.status
|
|
152
|
+
};
|
|
153
|
+
cached = {
|
|
154
|
+
rowSnapshot,
|
|
155
|
+
stored,
|
|
156
|
+
snapshot
|
|
157
|
+
};
|
|
158
|
+
return snapshot;
|
|
159
|
+
},
|
|
160
|
+
subscribe: (listener) => {
|
|
161
|
+
if (listeners.size === 0) load();
|
|
162
|
+
listeners.add(listener);
|
|
163
|
+
const off = row.subscribe(listener);
|
|
164
|
+
return () => {
|
|
165
|
+
listeners.delete(listener);
|
|
166
|
+
off();
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
set: (field, value) => gateway() === void 0 ? row.set(field, value) : write({ [field]: value }),
|
|
170
|
+
unset: (field) => gateway() === void 0 ? row.unset(field) : write({ [field]: null })
|
|
171
|
+
};
|
|
172
|
+
}
|
|
96
173
|
//#endregion
|
|
97
174
|
//#region src/client/locales.ts
|
|
98
175
|
/** `local-agent-codex` namespace dictionaries. */
|
|
@@ -202,8 +279,13 @@ window.__ModuleLoader__.load({
|
|
|
202
279
|
* re-probes so a mid-session credential loss cannot leave a stale green dot.
|
|
203
280
|
* @module @khorsheed/dsh-local-agent/client — auth status bus
|
|
204
281
|
*/
|
|
205
|
-
const
|
|
206
|
-
const
|
|
282
|
+
const BUS_KEY = Symbol.for("dsh.local-agent.auth-status");
|
|
283
|
+
const bus = globalThis[BUS_KEY] ??= {
|
|
284
|
+
statuses: /* @__PURE__ */ new Map(),
|
|
285
|
+
listeners: /* @__PURE__ */ new Set()
|
|
286
|
+
};
|
|
287
|
+
const statuses = bus.statuses;
|
|
288
|
+
const listeners = bus.listeners;
|
|
207
289
|
/** Record one harness's latest probe result and notify subscribers. */
|
|
208
290
|
function publishAuthStatus(harnessId, status) {
|
|
209
291
|
if (statuses.get(harnessId) === status) return;
|
|
@@ -261,30 +343,30 @@ window.__ModuleLoader__.load({
|
|
|
261
343
|
document.head.appendChild(tag);
|
|
262
344
|
}
|
|
263
345
|
var ProviderAuthBlock_module_css_default = {
|
|
264
|
-
"
|
|
265
|
-
"
|
|
346
|
+
"dotOff": "On5p7W_dotOff",
|
|
347
|
+
"rows": "On5p7W_rows",
|
|
266
348
|
"rosterError": "On5p7W_rosterError",
|
|
267
349
|
"section": "On5p7W_section",
|
|
268
|
-
"
|
|
269
|
-
"
|
|
270
|
-
"rows": "On5p7W_rows",
|
|
350
|
+
"rowName": "On5p7W_rowName",
|
|
351
|
+
"status": "On5p7W_status",
|
|
271
352
|
"loginButton": "On5p7W_loginButton",
|
|
272
|
-
"rowCard": "On5p7W_rowCard",
|
|
273
|
-
"intro": "On5p7W_intro",
|
|
274
|
-
"rowTag": "On5p7W_rowTag",
|
|
275
353
|
"loginPrompt": "On5p7W_loginPrompt",
|
|
276
|
-
"
|
|
277
|
-
"
|
|
354
|
+
"promptText": "On5p7W_promptText",
|
|
355
|
+
"codeInput": "On5p7W_codeInput",
|
|
278
356
|
"rowHead": "On5p7W_rowHead",
|
|
357
|
+
"rowTag": "On5p7W_rowTag",
|
|
358
|
+
"openPage": "On5p7W_openPage",
|
|
359
|
+
"rowCard": "On5p7W_rowCard",
|
|
360
|
+
"logoutButton": "On5p7W_logoutButton",
|
|
279
361
|
"dotOn": "On5p7W_dotOn",
|
|
280
|
-
"
|
|
281
|
-
"
|
|
282
|
-
"codeInput": "On5p7W_codeInput",
|
|
362
|
+
"intro": "On5p7W_intro",
|
|
363
|
+
"rowIdentity": "On5p7W_rowIdentity",
|
|
283
364
|
"dotPending": "On5p7W_dotPending",
|
|
284
|
-
"rowPending": "On5p7W_rowPending",
|
|
285
365
|
"rowActions": "On5p7W_rowActions",
|
|
286
|
-
"
|
|
287
|
-
"
|
|
366
|
+
"title": "On5p7W_title",
|
|
367
|
+
"credentialDot": "On5p7W_credentialDot",
|
|
368
|
+
"rowPending": "On5p7W_rowPending",
|
|
369
|
+
"retryButton": "On5p7W_retryButton"
|
|
288
370
|
};
|
|
289
371
|
//#endregion
|
|
290
372
|
//#region ../local-agent/src/client/ProviderAuthBlock.tsx
|
|
@@ -437,8 +519,8 @@ window.__ModuleLoader__.load({
|
|
|
437
519
|
const statusKind = usePublishedAuthStatus(harness.id) ?? view.status;
|
|
438
520
|
const authenticated = statusKind === "authenticated";
|
|
439
521
|
const loginPending = pendingSince !== void 0;
|
|
440
|
-
const loginable = view.loginable ??
|
|
441
|
-
const logoutable = view.logoutable ??
|
|
522
|
+
const loginable = view.loginable ?? statusKind !== "unavailable";
|
|
523
|
+
const logoutable = view.logoutable ?? statusKind !== "unavailable";
|
|
442
524
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
443
525
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
444
526
|
className: ProviderAuthBlock_module_css_default.rowHead,
|
|
@@ -471,7 +553,7 @@ window.__ModuleLoader__.load({
|
|
|
471
553
|
statusKind === "checking" && t("loading"),
|
|
472
554
|
statusKind === "authenticated" && t("settings.authenticated"),
|
|
473
555
|
statusKind === "anonymous" && t("settings.notAuthenticated"),
|
|
474
|
-
statusKind === "unavailable" && t("
|
|
556
|
+
statusKind === "unavailable" && t("settings.statusFailed")
|
|
475
557
|
]
|
|
476
558
|
}),
|
|
477
559
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -564,8 +646,8 @@ window.__ModuleLoader__.load({
|
|
|
564
646
|
}
|
|
565
647
|
var AuthStatusDot_module_css_default = {
|
|
566
648
|
"credentialDot": "OFjNUG_credentialDot",
|
|
567
|
-
"dotPending": "OFjNUG_dotPending",
|
|
568
649
|
"dotOff": "OFjNUG_dotOff",
|
|
650
|
+
"dotPending": "OFjNUG_dotPending",
|
|
569
651
|
"dotOn": "OFjNUG_dotOn"
|
|
570
652
|
};
|
|
571
653
|
//#endregion
|
|
@@ -582,7 +664,7 @@ window.__ModuleLoader__.load({
|
|
|
582
664
|
}
|
|
583
665
|
//#endregion
|
|
584
666
|
//#region \0dsh-css:../local-agent/src/client/ModelListSection.module.css.mjs
|
|
585
|
-
const css$1 = "._4eeJ7W_block{flex-direction:column;gap:10px;display:flex}._4eeJ7W_blockTitle{color:var(--dsw-alias-label-primary);align-items:center;gap:6px;margin:0;font-size:13px;font-weight:500;line-height:1.4;display:flex}._4eeJ7W_info{appearance:none;color:var(--dsw-alias-label-tertiary);cursor:help;background:0 0;border:0;border-radius:50%;padding:0 2px;font-size:12px;line-height:1}._4eeJ7W_row{align-items:center;gap:12px;display:flex}._4eeJ7W_saved{color:var(--dsw-alias-state-success-primary);font-size:12px}._4eeJ7W_errorText{color:var(--dsw-alias-state-error-primary);font-size:12px}._4eeJ7W_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}._4eeJ7W_modelInput{min-width:0;font:inherit;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;flex:1;padding:5px 8px;font-size:13px;line-height:1.4}._4eeJ7W_modelSave{appearance:none;font:inherit;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:8px;flex:none;padding:5px 10px;font-size:13px;line-height:1.4}._4eeJ7W_modelList{flex-direction:column;gap:8px;display:flex}._4eeJ7W_modelRow{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);cursor:grab;border-radius:8px;align-items:center;gap:8px;padding:6px 8px;display:flex}._4eeJ7W_modelRowDragging{opacity:.6;cursor:grabbing}._4eeJ7W_modelDrag{color:var(--dsw-alias-label-tertiary);cursor:grab;user-select:none}._4eeJ7W_modelRowValue{font-size:13px;font-family:var(--dsw-alias-font-mono,monospace);color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}._4eeJ7W_modelRowLabel{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;max-width:40%;font-size:12px;overflow:hidden}._4eeJ7W_modelDefaultChip{color:var(--dsw-alias-success,#1a9e54);background:color-mix(in srgb, var(--dsw-alias-success,#1a9e54) 12%, transparent);border-radius:999px;padding:1px 8px;font-size:11px;line-height:1.4}._4eeJ7W_modelDelete{appearance:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:4px;padding:2px 4px;font-size:12px}._4eeJ7W_modelAdd{appearance:none;border:1px dashed var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:5px 12px;font-size:13px}._4eeJ7W_dialogBackdrop{z-index:100;background:#00000052;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._4eeJ7W_dialog{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;flex-direction:column;gap:10px;width:min(520px,100vw - 48px);max-height:min(560px,100vh - 96px);padding:16px;display:flex;box-shadow:0 12px 40px #00000059}._4eeJ7W_dialogHead{justify-content:space-between;align-items:center;display:flex}._4eeJ7W_dialogTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:14px;font-weight:600}._4eeJ7W_dialogClose{appearance:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:4px;padding:2px 4px;font-size:13px}._4eeJ7W_dialogHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px}._4eeJ7W_dialogToolbar{align-items:center;gap:8px;display:flex}._4eeJ7W_dialogToolbar ._4eeJ7W_modelInput{flex:1}._4eeJ7W_dialogSelectAll{appearance:none;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;padding:4px 6px;font-size:13px}._4eeJ7W_dialogList{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex-direction:column;flex:1;gap:2px;min-height:120px;max-height:320px;padding:6px;display:flex;overflow-y:auto}._4eeJ7W_dialogItem{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:8px;padding:6px 8px;font-size:13px;display:flex}._4eeJ7W_dialogItemAdded{opacity:.55;cursor:default}._4eeJ7W_dialogItemValue{font-family:var(--dsw-alias-font-mono,monospace)}._4eeJ7W_dialogItemLabel{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._4eeJ7W_dialogActions{justify-content:flex-end;gap:8px;display:flex}._4eeJ7W_dialogConfirm{appearance:none;color:var(--dsw-alias-
|
|
667
|
+
const css$1 = "._4eeJ7W_block{flex-direction:column;gap:10px;display:flex}._4eeJ7W_blockTitle{color:var(--dsw-alias-label-primary);align-items:center;gap:6px;margin:0;font-size:13px;font-weight:500;line-height:1.4;display:flex}._4eeJ7W_info{appearance:none;color:var(--dsw-alias-label-tertiary);cursor:help;background:0 0;border:0;border-radius:50%;padding:0 2px;font-size:12px;line-height:1}._4eeJ7W_row{align-items:center;gap:12px;display:flex}._4eeJ7W_saved{color:var(--dsw-alias-state-success-primary);font-size:12px}._4eeJ7W_errorText{color:var(--dsw-alias-state-error-primary);font-size:12px}._4eeJ7W_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}._4eeJ7W_modelInput{min-width:0;font:inherit;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;flex:1;padding:5px 8px;font-size:13px;line-height:1.4}._4eeJ7W_modelSave{appearance:none;font:inherit;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:8px;flex:none;padding:5px 10px;font-size:13px;line-height:1.4}._4eeJ7W_modelList{flex-direction:column;gap:8px;display:flex}._4eeJ7W_modelRow{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);cursor:grab;border-radius:8px;align-items:center;gap:8px;padding:6px 8px;display:flex}._4eeJ7W_modelRowDragging{opacity:.6;cursor:grabbing}._4eeJ7W_modelDrag{color:var(--dsw-alias-label-tertiary);cursor:grab;user-select:none}._4eeJ7W_modelRowValue{font-size:13px;font-family:var(--dsw-alias-font-mono,monospace);color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}._4eeJ7W_modelRowLabel{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;max-width:40%;font-size:12px;overflow:hidden}._4eeJ7W_modelDefaultChip{color:var(--dsw-alias-success,#1a9e54);background:color-mix(in srgb, var(--dsw-alias-success,#1a9e54) 12%, transparent);border-radius:999px;padding:1px 8px;font-size:11px;line-height:1.4}._4eeJ7W_modelDelete{appearance:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:4px;padding:2px 4px;font-size:12px}._4eeJ7W_modelAdd{appearance:none;border:1px dashed var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:5px 12px;font-size:13px}._4eeJ7W_dialogBackdrop{z-index:100;background:#00000052;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._4eeJ7W_dialog{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;flex-direction:column;gap:10px;width:min(520px,100vw - 48px);max-height:min(560px,100vh - 96px);padding:16px;display:flex;box-shadow:0 12px 40px #00000059}._4eeJ7W_dialogHead{justify-content:space-between;align-items:center;display:flex}._4eeJ7W_dialogTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:14px;font-weight:600}._4eeJ7W_dialogClose{appearance:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:4px;padding:2px 4px;font-size:13px}._4eeJ7W_dialogHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px}._4eeJ7W_dialogToolbar{align-items:center;gap:8px;display:flex}._4eeJ7W_dialogToolbar ._4eeJ7W_modelInput{flex:1}._4eeJ7W_dialogSelectAll{appearance:none;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;padding:4px 6px;font-size:13px}._4eeJ7W_dialogList{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex-direction:column;flex:1;gap:2px;min-height:120px;max-height:320px;padding:6px;display:flex;overflow-y:auto}._4eeJ7W_dialogItem{color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:6px;align-items:center;gap:8px;padding:6px 8px;font-size:13px;display:flex}._4eeJ7W_dialogItemAdded{opacity:.55;cursor:default}._4eeJ7W_dialogItemValue{font-family:var(--dsw-alias-font-mono,monospace)}._4eeJ7W_dialogItemLabel{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._4eeJ7W_dialogActions{justify-content:flex-end;gap:8px;display:flex}._4eeJ7W_dialogConfirm{appearance:none;color:var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-button-primary-fill,#1f2329);cursor:pointer;border:0;border-radius:8px;padding:6px 14px;font-size:13px}._4eeJ7W_dialogConfirm:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill,#1f2329))}._4eeJ7W_dialogConfirm:disabled{opacity:.4;cursor:default}";
|
|
586
668
|
const tagId$1 = "@khorsheed/dsh-local-agent-codex/ModelListSection.module.css";
|
|
587
669
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
588
670
|
const tag = document.createElement("style");
|
|
@@ -592,39 +674,39 @@ window.__ModuleLoader__.load({
|
|
|
592
674
|
document.head.appendChild(tag);
|
|
593
675
|
}
|
|
594
676
|
var ModelListSection_module_css_default = {
|
|
595
|
-
"
|
|
677
|
+
"modelRowValue": "_4eeJ7W_modelRowValue",
|
|
596
678
|
"blockTitle": "_4eeJ7W_blockTitle",
|
|
679
|
+
"dialogItemValue": "_4eeJ7W_dialogItemValue",
|
|
680
|
+
"dialogBackdrop": "_4eeJ7W_dialogBackdrop",
|
|
597
681
|
"block": "_4eeJ7W_block",
|
|
598
|
-
"modelRowDragging": "_4eeJ7W_modelRowDragging",
|
|
599
|
-
"dialogSelectAll": "_4eeJ7W_dialogSelectAll",
|
|
600
682
|
"dialogTitle": "_4eeJ7W_dialogTitle",
|
|
683
|
+
"row": "_4eeJ7W_row",
|
|
684
|
+
"modelInput": "_4eeJ7W_modelInput",
|
|
685
|
+
"modelRowDragging": "_4eeJ7W_modelRowDragging",
|
|
601
686
|
"dialog": "_4eeJ7W_dialog",
|
|
687
|
+
"dialogActions": "_4eeJ7W_dialogActions",
|
|
688
|
+
"modelAdd": "_4eeJ7W_modelAdd",
|
|
689
|
+
"modelDrag": "_4eeJ7W_modelDrag",
|
|
690
|
+
"dialogSelectAll": "_4eeJ7W_dialogSelectAll",
|
|
691
|
+
"dialogItemLabel": "_4eeJ7W_dialogItemLabel",
|
|
692
|
+
"dialogToolbar": "_4eeJ7W_dialogToolbar",
|
|
602
693
|
"dialogList": "_4eeJ7W_dialogList",
|
|
603
|
-
"row": "_4eeJ7W_row",
|
|
604
694
|
"modelSave": "_4eeJ7W_modelSave",
|
|
605
|
-
"errorText": "_4eeJ7W_errorText",
|
|
606
|
-
"info": "_4eeJ7W_info",
|
|
607
|
-
"modelDelete": "_4eeJ7W_modelDelete",
|
|
608
695
|
"dialogHead": "_4eeJ7W_dialogHead",
|
|
609
|
-
"
|
|
610
|
-
"modelDrag": "_4eeJ7W_modelDrag",
|
|
611
|
-
"dialogItemValue": "_4eeJ7W_dialogItemValue",
|
|
612
|
-
"dialogBackdrop": "_4eeJ7W_dialogBackdrop",
|
|
613
|
-
"dialogActions": "_4eeJ7W_dialogActions",
|
|
614
|
-
"dialogItemLabel": "_4eeJ7W_dialogItemLabel",
|
|
696
|
+
"dialogHint": "_4eeJ7W_dialogHint",
|
|
615
697
|
"modelList": "_4eeJ7W_modelList",
|
|
698
|
+
"dialogItem": "_4eeJ7W_dialogItem",
|
|
616
699
|
"modelDefaultChip": "_4eeJ7W_modelDefaultChip",
|
|
617
|
-
"
|
|
700
|
+
"errorText": "_4eeJ7W_errorText",
|
|
701
|
+
"saved": "_4eeJ7W_saved",
|
|
618
702
|
"hint": "_4eeJ7W_hint",
|
|
619
|
-
"
|
|
620
|
-
"dialogClose": "_4eeJ7W_dialogClose",
|
|
703
|
+
"dialogConfirm": "_4eeJ7W_dialogConfirm",
|
|
621
704
|
"modelRowLabel": "_4eeJ7W_modelRowLabel",
|
|
705
|
+
"info": "_4eeJ7W_info",
|
|
706
|
+
"modelDelete": "_4eeJ7W_modelDelete",
|
|
622
707
|
"dialogItemAdded": "_4eeJ7W_dialogItemAdded",
|
|
623
|
-
"
|
|
624
|
-
"
|
|
625
|
-
"saved": "_4eeJ7W_saved",
|
|
626
|
-
"dialogItem": "_4eeJ7W_dialogItem",
|
|
627
|
-
"dialogHint": "_4eeJ7W_dialogHint"
|
|
708
|
+
"modelRow": "_4eeJ7W_modelRow",
|
|
709
|
+
"dialogClose": "_4eeJ7W_dialogClose"
|
|
628
710
|
};
|
|
629
711
|
//#endregion
|
|
630
712
|
//#region ../local-agent/src/client/ModelListSection.tsx
|
|
@@ -996,33 +1078,33 @@ window.__ModuleLoader__.load({
|
|
|
996
1078
|
document.head.appendChild(tag);
|
|
997
1079
|
}
|
|
998
1080
|
var SettingsCard_module_css_default = {
|
|
999
|
-
"
|
|
1000
|
-
"knob": "oWITGW_knob",
|
|
1001
|
-
"hint": "oWITGW_hint",
|
|
1002
|
-
"modelInput": "oWITGW_modelInput",
|
|
1003
|
-
"card": "oWITGW_card",
|
|
1004
|
-
"cardOpen": "oWITGW_cardOpen",
|
|
1005
|
-
"name": "oWITGW_name",
|
|
1006
|
-
"modelSave": "oWITGW_modelSave",
|
|
1007
|
-
"chevronOpen": "oWITGW_chevronOpen",
|
|
1008
|
-
"saved": "oWITGW_saved",
|
|
1009
|
-
"switch": "oWITGW_switch",
|
|
1010
|
-
"description": "oWITGW_description",
|
|
1011
|
-
"errorText": "oWITGW_errorText",
|
|
1081
|
+
"configuredChip": "oWITGW_configuredChip",
|
|
1012
1082
|
"headText": "oWITGW_headText",
|
|
1013
|
-
"endpointFields": "oWITGW_endpointFields",
|
|
1014
|
-
"body": "oWITGW_body",
|
|
1015
|
-
"rowLabel": "oWITGW_rowLabel",
|
|
1016
|
-
"blockTitle": "oWITGW_blockTitle",
|
|
1017
|
-
"chevron": "oWITGW_chevron",
|
|
1018
1083
|
"info": "oWITGW_info",
|
|
1019
|
-
"
|
|
1084
|
+
"row": "oWITGW_row",
|
|
1085
|
+
"body": "oWITGW_body",
|
|
1086
|
+
"block": "oWITGW_block",
|
|
1020
1087
|
"nameRow": "oWITGW_nameRow",
|
|
1088
|
+
"chevron": "oWITGW_chevron",
|
|
1089
|
+
"chevronOpen": "oWITGW_chevronOpen",
|
|
1090
|
+
"cardOpen": "oWITGW_cardOpen",
|
|
1091
|
+
"blockTitle": "oWITGW_blockTitle",
|
|
1021
1092
|
"endpointField": "oWITGW_endpointField",
|
|
1093
|
+
"header": "oWITGW_header",
|
|
1094
|
+
"description": "oWITGW_description",
|
|
1022
1095
|
"endpointLabel": "oWITGW_endpointLabel",
|
|
1023
|
-
"
|
|
1096
|
+
"rowLabel": "oWITGW_rowLabel",
|
|
1024
1097
|
"switchOn": "oWITGW_switchOn",
|
|
1025
|
-
"
|
|
1098
|
+
"knob": "oWITGW_knob",
|
|
1099
|
+
"endpointFields": "oWITGW_endpointFields",
|
|
1100
|
+
"name": "oWITGW_name",
|
|
1101
|
+
"saved": "oWITGW_saved",
|
|
1102
|
+
"switch": "oWITGW_switch",
|
|
1103
|
+
"errorText": "oWITGW_errorText",
|
|
1104
|
+
"card": "oWITGW_card",
|
|
1105
|
+
"hint": "oWITGW_hint",
|
|
1106
|
+
"modelInput": "oWITGW_modelInput",
|
|
1107
|
+
"modelSave": "oWITGW_modelSave"
|
|
1026
1108
|
};
|
|
1027
1109
|
//#endregion
|
|
1028
1110
|
//#region src/client/SettingsCard.tsx
|
|
@@ -1053,6 +1135,7 @@ window.__ModuleLoader__.load({
|
|
|
1053
1135
|
const endpointUnavailable = endpointAuth === void 0 || endpointState === "unavailable";
|
|
1054
1136
|
const endpointLoading = !endpointUnavailable && endpointState === null;
|
|
1055
1137
|
const apiKeyConfigured = endpointState !== null && endpointState !== "unavailable" && endpointState.apiKeySet;
|
|
1138
|
+
const authPublished = usePublishedAuthStatus("codex");
|
|
1056
1139
|
(0, react.useEffect)(() => {
|
|
1057
1140
|
if (!active || endpointAuth === void 0) return;
|
|
1058
1141
|
let stale = false;
|
|
@@ -1067,7 +1150,8 @@ window.__ModuleLoader__.load({
|
|
|
1067
1150
|
}, [
|
|
1068
1151
|
active,
|
|
1069
1152
|
endpointAuth,
|
|
1070
|
-
endpointSaved
|
|
1153
|
+
endpointSaved,
|
|
1154
|
+
authPublished
|
|
1071
1155
|
]);
|
|
1072
1156
|
const saveEndpoint = () => {
|
|
1073
1157
|
if (endpointAuth === void 0) return;
|
|
@@ -1092,8 +1176,9 @@ window.__ModuleLoader__.load({
|
|
|
1092
1176
|
const write = (field, value) => {
|
|
1093
1177
|
setSaved(false);
|
|
1094
1178
|
setError(false);
|
|
1095
|
-
scope.set(field, value).then(() => {
|
|
1096
|
-
|
|
1179
|
+
scope.set(field, value).then((accepted) => {
|
|
1180
|
+
if (accepted === false) setError(true);
|
|
1181
|
+
else setSaved(true);
|
|
1097
1182
|
}, () => {
|
|
1098
1183
|
setError(true);
|
|
1099
1184
|
});
|
|
@@ -1463,7 +1548,7 @@ window.__ModuleLoader__.load({
|
|
|
1463
1548
|
en
|
|
1464
1549
|
}), "local-agent-codex: dictionaries");
|
|
1465
1550
|
const t = ctx.locale.bind(NS);
|
|
1466
|
-
const scope =
|
|
1551
|
+
const scope = bindCardSettingsScope(ctx, "codex", "local-agent-codex");
|
|
1467
1552
|
const authT = ctx.locale.bind("local-agent");
|
|
1468
1553
|
const gateway = () => ctx.get("remote.localAgentGateway");
|
|
1469
1554
|
const cardFace = () => ({
|
package/lib/index.js
CHANGED
|
@@ -795,14 +795,15 @@ function codexAppServerItemToLine(item) {
|
|
|
795
795
|
} : void 0;
|
|
796
796
|
case "commandExecution": {
|
|
797
797
|
const command = typeof item["command"] === "string" ? item.command : void 0;
|
|
798
|
-
const
|
|
799
|
-
if (command === void 0 &&
|
|
798
|
+
const outcome = commandOutcome(item["aggregatedOutput"], item["exitCode"], item["status"]);
|
|
799
|
+
if (command === void 0 && outcome.result === void 0) return void 0;
|
|
800
800
|
return {
|
|
801
801
|
kind: "tool",
|
|
802
802
|
id: itemId ?? `codex-live-${randomUUID()}`,
|
|
803
803
|
name: "Bash",
|
|
804
804
|
...command === void 0 ? {} : { args: command },
|
|
805
|
-
...
|
|
805
|
+
...outcome.result === void 0 ? {} : { result: outcome.result },
|
|
806
|
+
...outcome.failed ? { failed: true } : {}
|
|
806
807
|
};
|
|
807
808
|
}
|
|
808
809
|
case "webSearch": return {
|
|
@@ -846,10 +847,20 @@ function codexAppServerItemToLine(item) {
|
|
|
846
847
|
case "mcpToolCall": {
|
|
847
848
|
const server = typeof item["server"] === "string" ? item.server : void 0;
|
|
848
849
|
const tool = typeof item["tool"] === "string" ? item.tool : void 0;
|
|
850
|
+
const args = item["arguments"] === void 0 || item["arguments"] === null ? void 0 : typeof item["arguments"] === "string" ? item["arguments"] : JSON.stringify(item["arguments"]);
|
|
851
|
+
const outcome = item["result"];
|
|
852
|
+
const text = (Array.isArray(outcome?.content) ? outcome.content : []).filter((block) => typeof block === "object" && block !== null).map((block) => typeof block["text"] === "string" ? block["text"] : void 0).filter((entry) => entry !== void 0 && entry !== "").join("\n");
|
|
853
|
+
const error = item["error"];
|
|
854
|
+
const failure = typeof error?.message === "string" && error.message !== "" ? error.message : void 0;
|
|
849
855
|
return {
|
|
850
856
|
kind: "tool",
|
|
851
857
|
id: itemId ?? `codex-live-${randomUUID()}`,
|
|
852
|
-
name: server !== void 0 && tool !== void 0 ? `${server}/${tool}` : tool ?? "mcp"
|
|
858
|
+
name: server !== void 0 && tool !== void 0 ? `${server}/${tool}` : tool ?? "mcp",
|
|
859
|
+
...args === void 0 || args === "" || args === "{}" ? {} : { args },
|
|
860
|
+
...failure !== void 0 ? {
|
|
861
|
+
result: failure,
|
|
862
|
+
failed: true
|
|
863
|
+
} : text === "" ? {} : { result: text }
|
|
853
864
|
};
|
|
854
865
|
}
|
|
855
866
|
case "plan": return typeof item["text"] === "string" && item["text"].trim() !== "" ? {
|
|
@@ -1376,7 +1387,7 @@ var CodexLiveDriver = class {
|
|
|
1376
1387
|
if (rt.threadId === void 0) {
|
|
1377
1388
|
const permissionParams = {
|
|
1378
1389
|
approvalPolicy: "never",
|
|
1379
|
-
sandbox: this.config.sandbox ?? "workspace-write"
|
|
1390
|
+
sandbox: this.config.sandbox ?? spec.sandbox ?? "workspace-write"
|
|
1380
1391
|
};
|
|
1381
1392
|
if (spec.resume === void 0) {
|
|
1382
1393
|
const response = await rt.peer.request("thread/start", {
|
|
@@ -2121,6 +2132,58 @@ async function listCodexConfigModels(homeDir) {
|
|
|
2121
2132
|
}
|
|
2122
2133
|
return found;
|
|
2123
2134
|
}
|
|
2135
|
+
//#endregion
|
|
2136
|
+
//#region lib/types/codex-cli-provider.js
|
|
2137
|
+
/**
|
|
2138
|
+
* One-shot Codex CLI subagent lifecycle: spawn `codex exec "<task>"` through
|
|
2139
|
+
* the subprocess seam under the harness's scoped home, capture its printed
|
|
2140
|
+
* response as the run output, and dispose to whole-tree quiescence. Mirrors
|
|
2141
|
+
* the kimi-cli provider: every accepted run is a fresh process and a fresh
|
|
2142
|
+
* codex session; there is no continuation across runs.
|
|
2143
|
+
*
|
|
2144
|
+
* The provider name `codex-local` avoids colliding with the official
|
|
2145
|
+
* `subagent-codex` package's provider name `codex` — a composition that
|
|
2146
|
+
* mounts both would fail loud with DUPLICATE_PROVIDER. The tool row in this
|
|
2147
|
+
* bundle's patch takes the official model-facing name `subagent_codex`
|
|
2148
|
+
* instead: the official preset row ships disabled, and the patch disables
|
|
2149
|
+
* it too (a deliberate user re-enable conflicts loud, by design).
|
|
2150
|
+
* @module @khorsheed/dsh-local-agent-codex/codex-cli-provider
|
|
2151
|
+
*/
|
|
2152
|
+
/**
|
|
2153
|
+
* The synthesized result for a call whose result the stream never recorded
|
|
2154
|
+
* (a killed run, or a result-less item type like web_search_call). The
|
|
2155
|
+
* released-log contract resolves every advertised call before its step
|
|
2156
|
+
* closes, so an interrupted call folds with this error result. Same text as
|
|
2157
|
+
* the kimi provider's placeholder, for family consistency.
|
|
2158
|
+
*/
|
|
2159
|
+
/**
|
|
2160
|
+
* A finished command's result text and failure. Codex reports a command that
|
|
2161
|
+
* printed nothing with an empty aggregated output — a crash (exit 139) or a
|
|
2162
|
+
* silent failure read as "no result" and the mirror wrote the interrupted
|
|
2163
|
+
* placeholder. A finished command (an exit code, or a completed/failed status)
|
|
2164
|
+
* always carries a result: its output, or "(无输出)", with a non-zero exit
|
|
2165
|
+
* code appended and the result marked failed. A command still running yields
|
|
2166
|
+
* none, so the fold keeps waiting for it.
|
|
2167
|
+
* @param output - the item's aggregated output.
|
|
2168
|
+
* @param exitCode - the item's exit code, when finished.
|
|
2169
|
+
* @param status - the item's status.
|
|
2170
|
+
* @returns the result text (undefined while running) and whether it failed.
|
|
2171
|
+
*/
|
|
2172
|
+
function commandOutcome(output, exitCode, status) {
|
|
2173
|
+
const text = typeof output === "string" && output.trim() !== "" ? output : void 0;
|
|
2174
|
+
const code = typeof exitCode === "number" && Number.isFinite(exitCode) ? exitCode : void 0;
|
|
2175
|
+
const finished = code !== void 0 || status === "completed" || status === "failed";
|
|
2176
|
+
const failed = code !== void 0 && code !== 0 || status === "failed";
|
|
2177
|
+
if (!finished) return text === void 0 ? { failed: false } : {
|
|
2178
|
+
result: text,
|
|
2179
|
+
failed: false
|
|
2180
|
+
};
|
|
2181
|
+
const exit = code !== void 0 && code !== 0 ? `(退出码 ${code})` : status === "failed" ? "(执行失败)" : "";
|
|
2182
|
+
return {
|
|
2183
|
+
result: text === void 0 ? exit === "" ? "(无输出)" : `${exit.slice(0, -1)},无输出)` : exit === "" ? text : `${text}\n${exit}`,
|
|
2184
|
+
failed
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2124
2187
|
/** Quote one TOML basic string for the `-c` config override. */
|
|
2125
2188
|
function tomlString(value) {
|
|
2126
2189
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
@@ -2133,6 +2196,24 @@ const DEFAULT_DISPOSE_GRACE_MS = 3e3;
|
|
|
2133
2196
|
* provider-controlled error output only.
|
|
2134
2197
|
*/
|
|
2135
2198
|
const CODEX_AUTH_FAILURE = /401 unauthorized|unauthorized|authentication failed|not logged in|access token/i;
|
|
2199
|
+
/**
|
|
2200
|
+
* A session's standing sandbox mode — the host's `sandbox/mode` vocabulary is
|
|
2201
|
+
* codex's own (`read-only` | `workspace-write` | `danger-full-access`). Reads
|
|
2202
|
+
* the delegating session first (the mode the user sees and changes), then
|
|
2203
|
+
* the child's inherited record.
|
|
2204
|
+
* @param ctx - context carrying the live session store.
|
|
2205
|
+
* @param childSession - the member's child session, when one exists.
|
|
2206
|
+
* @param parentSessionId - the delegating session.
|
|
2207
|
+
* @returns the mode, or undefined when neither session records one.
|
|
2208
|
+
*/
|
|
2209
|
+
function sessionSandboxMode(ctx, childSession, parentSessionId) {
|
|
2210
|
+
const sessions = ctx.get("sessions");
|
|
2211
|
+
const parent = parentSessionId === void 0 ? void 0 : sessions?.get(SessionId(parentSessionId));
|
|
2212
|
+
for (const session of [parent, childSession]) {
|
|
2213
|
+
const mode = session?.snapshotEvents().filter((event) => event.type === "sandbox/mode").at(-1)?.data;
|
|
2214
|
+
if (mode?.mode === "read-only" || mode?.mode === "workspace-write" || mode?.mode === "danger-full-access") return mode.mode;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2136
2217
|
/** Register one member run with the channel; see {@link CodexMemberRunHandle}. */
|
|
2137
2218
|
function registerCodexMemberRun(ctx, childSessionId, parentSessionId) {
|
|
2138
2219
|
const registry = ctx.localAgent;
|
|
@@ -2229,7 +2310,7 @@ var CodexCliProvider = class {
|
|
|
2229
2310
|
* relay (a custom `model_providers` entry on the argv, the key in the
|
|
2230
2311
|
* env); with no key the spawn carries no credential at all.
|
|
2231
2312
|
*/
|
|
2232
|
-
constructor(ctx, sandbox =
|
|
2313
|
+
constructor(ctx, sandbox = void 0, live, model, overrides, endpoint) {
|
|
2233
2314
|
this.ctx = ctx;
|
|
2234
2315
|
this.sandbox = sandbox;
|
|
2235
2316
|
this.live = live;
|
|
@@ -2237,6 +2318,18 @@ var CodexCliProvider = class {
|
|
|
2237
2318
|
this.overrides = overrides;
|
|
2238
2319
|
this.endpoint = endpoint;
|
|
2239
2320
|
}
|
|
2321
|
+
/**
|
|
2322
|
+
* The sandbox this round runs under: the plugin config's explicit policy
|
|
2323
|
+
* when set, else the member's session's own standing mode — the delegating
|
|
2324
|
+
* session's latest `sandbox/mode`, then the child's inherited one — so a
|
|
2325
|
+
* member runs with exactly the access its session shows (a full-access
|
|
2326
|
+
* session's Codex member was confined to workspace-write and Blender
|
|
2327
|
+
* crashed under the seatbelt). `workspace-write` when neither says.
|
|
2328
|
+
*/
|
|
2329
|
+
sandboxFor(childSession, parentSessionId) {
|
|
2330
|
+
if (this.sandbox !== void 0) return this.sandbox;
|
|
2331
|
+
return sessionSandboxMode(this.ctx, childSession, parentSessionId) ?? "workspace-write";
|
|
2332
|
+
}
|
|
2240
2333
|
/** The scope's endpoint credential for this round; absent resolver = never. */
|
|
2241
2334
|
resolveEndpoint(homeDir) {
|
|
2242
2335
|
return this.endpoint === void 0 ? Promise.resolve(void 0) : this.endpoint(homeDir).catch(() => void 0);
|
|
@@ -2327,6 +2420,7 @@ var CodexCliProvider = class {
|
|
|
2327
2420
|
homeDir,
|
|
2328
2421
|
childSession,
|
|
2329
2422
|
parentSessionId: request.parent.session.id,
|
|
2423
|
+
sandbox: this.sandboxFor(childSession, request.parent.session.id),
|
|
2330
2424
|
...startModel === void 0 ? {} : { startModel },
|
|
2331
2425
|
...configuration === void 0 ? {} : { configuration: configuration.resolved },
|
|
2332
2426
|
onThreadId: (threadId) => {
|
|
@@ -2357,7 +2451,7 @@ var CodexCliProvider = class {
|
|
|
2357
2451
|
...exec === void 0 ? {} : { exec },
|
|
2358
2452
|
endpointLabel: baseUrl,
|
|
2359
2453
|
endpointArgv: endpointConfigOverrides(endpoint),
|
|
2360
|
-
sandbox: this.
|
|
2454
|
+
sandbox: this.sandboxFor(childSession, request.parent.session.id),
|
|
2361
2455
|
...configuration?.resolved ?? resolveRoundModel(this.overrides?.(runId) ?? requestedModel, () => this.model?.(scope)),
|
|
2362
2456
|
disposeGraceMs: DEFAULT_DISPOSE_GRACE_MS,
|
|
2363
2457
|
spawn: (spec) => this.ctx.subprocess.spawn(spec),
|
|
@@ -2416,6 +2510,7 @@ var CodexCliProvider = class {
|
|
|
2416
2510
|
homeDir,
|
|
2417
2511
|
childSession,
|
|
2418
2512
|
parentSessionId: request.parent.session.id,
|
|
2513
|
+
sandbox: this.sandboxFor(childSession, request.parent.session.id),
|
|
2419
2514
|
resume: {
|
|
2420
2515
|
cliSessionId: intent.cliSessionId,
|
|
2421
2516
|
turn: nextTurn
|
|
@@ -2446,7 +2541,7 @@ var CodexCliProvider = class {
|
|
|
2446
2541
|
...exec === void 0 ? {} : { exec },
|
|
2447
2542
|
endpointLabel: baseUrl,
|
|
2448
2543
|
endpointArgv: endpointConfigOverrides(endpoint),
|
|
2449
|
-
sandbox: this.
|
|
2544
|
+
sandbox: this.sandboxFor(childSession, request.parent.session.id),
|
|
2450
2545
|
...configuration?.resolved ?? resolveRoundModel(this.overrides?.(intent.childSessionId) ?? requestedModel, () => this.model?.(scope)),
|
|
2451
2546
|
disposeGraceMs: DEFAULT_DISPOSE_GRACE_MS,
|
|
2452
2547
|
spawn: (spec) => this.ctx.subprocess.spawn(spec),
|
|
@@ -2574,13 +2669,14 @@ function foldCodexStreamLine(state, raw) {
|
|
|
2574
2669
|
} else if (item.type === "command_execution") {
|
|
2575
2670
|
countToolCall(state, "command_execution");
|
|
2576
2671
|
const command = typeof item.command === "string" ? item.command : void 0;
|
|
2577
|
-
const
|
|
2578
|
-
if (command !== void 0 ||
|
|
2672
|
+
const outcome = commandOutcome(item.aggregated_output, item.exit_code, item.status);
|
|
2673
|
+
if (command !== void 0 || outcome.result !== void 0) state.lines.push({
|
|
2579
2674
|
kind: "tool",
|
|
2580
2675
|
id: typeof item.id === "string" ? item.id : `codex-tool-${state.lines.length}`,
|
|
2581
2676
|
name: "Bash",
|
|
2582
2677
|
...command === void 0 ? {} : { args: command },
|
|
2583
|
-
...
|
|
2678
|
+
...outcome.result === void 0 ? {} : { result: outcome.result },
|
|
2679
|
+
...outcome.failed ? { failed: true } : {}
|
|
2584
2680
|
});
|
|
2585
2681
|
} else if (item.type === "web_search_call") {
|
|
2586
2682
|
countToolCall(state, "web_search_call");
|
|
@@ -2976,7 +3072,7 @@ function appendCodexTranscriptLine(childSession, turn, step, line, usage) {
|
|
|
2976
3072
|
type: "text",
|
|
2977
3073
|
text: line.result ?? "(工具调用被中断,未能记录返回结果)"
|
|
2978
3074
|
}],
|
|
2979
|
-
isError: line.result === void 0
|
|
3075
|
+
isError: line.result === void 0 || line.failed === true
|
|
2980
3076
|
})
|
|
2981
3077
|
}, {
|
|
2982
3078
|
surfaceOp: "append",
|
|
@@ -3902,7 +3998,7 @@ const inject = [
|
|
|
3902
3998
|
* behavior byte-identical.
|
|
3903
3999
|
*/
|
|
3904
4000
|
const SETTINGS_FIELDS = {
|
|
3905
|
-
live: z.boolean().default(
|
|
4001
|
+
live: z.boolean().default(true),
|
|
3906
4002
|
liveMirrorGranularity: z.union([z.const("event"), z.const("token")]).default("token"),
|
|
3907
4003
|
model: z.string(),
|
|
3908
4004
|
recentModels: z.array(z.string()).default([])
|
|
@@ -3954,7 +4050,7 @@ function apply(ctx, config) {
|
|
|
3954
4050
|
const model = vol(config.model);
|
|
3955
4051
|
const recentModels = vol(config.recentModels);
|
|
3956
4052
|
return {
|
|
3957
|
-
live: vol(config.live) ??
|
|
4053
|
+
live: vol(config.live) ?? true,
|
|
3958
4054
|
liveMirrorGranularity: vol(config.liveMirrorGranularity) ?? "token",
|
|
3959
4055
|
...model === void 0 ? {} : { model },
|
|
3960
4056
|
...recentModels === void 0 ? {} : { recentModels }
|
|
@@ -3971,6 +4067,7 @@ function apply(ctx, config) {
|
|
|
3971
4067
|
}
|
|
3972
4068
|
const face = settingsFace(ctx, {
|
|
3973
4069
|
namespace: CODEX_SETTINGS_NAMESPACE,
|
|
4070
|
+
harness: "codex",
|
|
3974
4071
|
legacySchema: CODEX_SETTINGS_SCHEMA,
|
|
3975
4072
|
legacyBase,
|
|
3976
4073
|
read: readSettings
|
|
@@ -3997,7 +4094,7 @@ function apply(ctx, config) {
|
|
|
3997
4094
|
}, CATALOG_WARMUP_DELAY_MS);
|
|
3998
4095
|
warmup.unref();
|
|
3999
4096
|
const liveSwitch = new LiveDriverSwitch(ctx, face, {
|
|
4000
|
-
sandbox,
|
|
4097
|
+
sandbox: config.sandbox,
|
|
4001
4098
|
model: (childSessionId) => memberModelOverrides.get(childSessionId) ?? resolveModel((ctx.localAgent.memberBinding?.(childSessionId) ?? ctx.localAgent.getDelegation(childSessionId))?.scope),
|
|
4002
4099
|
endpoint: resolveEndpoint,
|
|
4003
4100
|
...config.liveIdleMs === void 0 ? {} : { liveIdleMs: config.liveIdleMs }
|
|
@@ -4019,7 +4116,7 @@ function apply(ctx, config) {
|
|
|
4019
4116
|
catalog: (scopedHome, cwd) => modelCatalog.read(scopedHome, cwd),
|
|
4020
4117
|
catalogDefault: (scopedHome, cwd) => modelCatalog.readDefault(scopedHome, cwd)
|
|
4021
4118
|
});
|
|
4022
|
-
const disposeProvider = ctx.subagents.registerProvider(new CodexCliProvider(ctx, sandbox, liveSwitch.resolve, resolveModel, (childSessionId) => memberModelOverrides.get(childSessionId), resolveEndpoint));
|
|
4119
|
+
const disposeProvider = ctx.subagents.registerProvider(new CodexCliProvider(ctx, config.sandbox, liveSwitch.resolve, resolveModel, (childSessionId) => memberModelOverrides.get(childSessionId), resolveEndpoint));
|
|
4023
4120
|
const disposeHarness = ctx.localAgent.register({
|
|
4024
4121
|
prepareMember: async ({ binding, childSession, configuration, signal }) => {
|
|
4025
4122
|
const driver = liveSwitch.resolve(binding.childSessionId);
|
|
@@ -7,7 +7,7 @@ import { IconChevronDownOutlineMedium } from './icons.js';
|
|
|
7
7
|
// the block carries no runtime identity to share.
|
|
8
8
|
import { ProviderAuthBlock, } from '@khorsheed/dsh-local-agent/src/client/ProviderAuthBlock.tsx';
|
|
9
9
|
import { AuthStatusDot } from '@khorsheed/dsh-local-agent/src/client/AuthStatusDot.tsx';
|
|
10
|
-
import { publishAuthStatus, useHarnessAuthStatus } from '@khorsheed/dsh-local-agent/src/client/auth-status.ts';
|
|
10
|
+
import { publishAuthStatus, useHarnessAuthStatus, usePublishedAuthStatus } from '@khorsheed/dsh-local-agent/src/client/auth-status.ts';
|
|
11
11
|
import { ModelListSection } from '@khorsheed/dsh-local-agent/src/client/ModelListSection.tsx';
|
|
12
12
|
import css from './SettingsCard.module.css';
|
|
13
13
|
/**
|
|
@@ -43,6 +43,10 @@ function useCardState(active, useSettings, scope, endpointAuth) {
|
|
|
43
43
|
const endpointUnavailable = endpointAuth === undefined || endpointState === 'unavailable';
|
|
44
44
|
const endpointLoading = !endpointUnavailable && endpointState === null;
|
|
45
45
|
const apiKeyConfigured = endpointState !== null && endpointState !== 'unavailable' && endpointState.apiKeySet;
|
|
46
|
+
// A login or logout elsewhere in the card (the auth block) changes what
|
|
47
|
+
// the store holds — logout deletes the saved key: re-read, or the form
|
|
48
|
+
// keeps saying 已配置 for a key that is gone.
|
|
49
|
+
const authPublished = usePublishedAuthStatus('codex');
|
|
46
50
|
useEffect(() => {
|
|
47
51
|
if (!active || endpointAuth === undefined)
|
|
48
52
|
return;
|
|
@@ -51,7 +55,7 @@ function useCardState(active, useSettings, scope, endpointAuth) {
|
|
|
51
55
|
setEndpointState(state ?? 'unavailable'); }, () => { if (!stale)
|
|
52
56
|
setEndpointState('unavailable'); });
|
|
53
57
|
return () => { stale = true; };
|
|
54
|
-
}, [active, endpointAuth, endpointSaved]);
|
|
58
|
+
}, [active, endpointAuth, endpointSaved, authPublished]);
|
|
55
59
|
// The credential save: one `configureEndpoint` call carrying only the
|
|
56
60
|
// dirty fields (a blank draft clears its field); the answer is the
|
|
57
61
|
// post-write resolved state, so the chips and the auth probe flip without
|
|
@@ -82,7 +86,11 @@ function useCardState(active, useSettings, scope, endpointAuth) {
|
|
|
82
86
|
const write = (field, value) => {
|
|
83
87
|
setSaved(false);
|
|
84
88
|
setError(false);
|
|
85
|
-
|
|
89
|
+
// The host answers a refused write with `false` (ConfigForm), not a rejection.
|
|
90
|
+
void scope.set(field, value).then((accepted) => { if (accepted === false)
|
|
91
|
+
setError(true);
|
|
92
|
+
else
|
|
93
|
+
setSaved(true); }, () => { setError(true); });
|
|
86
94
|
};
|
|
87
95
|
return {
|
|
88
96
|
snapshot, saved, error, ready, live, write,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { bindCardSettingsScope } from '@khorsheed/dsh-local-agent/src/client/settings-scope.ts';
|
|
2
2
|
/**
|
|
3
3
|
* Local-agent-codex plugin, browser half: the codex settings card — a section
|
|
4
4
|
* of the family core's「本机 Agent 委派」family card on the Plugins page
|
|
@@ -46,7 +46,7 @@ export const inject = ['slots', 'remote', 'remote.commands', 'locale'];
|
|
|
46
46
|
export function apply(ctx) {
|
|
47
47
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'local-agent-codex: dictionaries');
|
|
48
48
|
const t = ctx.locale.bind(NS);
|
|
49
|
-
const scope =
|
|
49
|
+
const scope = bindCardSettingsScope(ctx, 'codex', 'local-agent-codex');
|
|
50
50
|
// The auth block's copy lives in the family core's dictionary; binding is
|
|
51
51
|
// stable per namespace and late dictionary registration still resolves.
|
|
52
52
|
const authT = ctx.locale.bind('local-agent');
|
|
@@ -28,6 +28,23 @@ import { type CodexEndpointAuth } from './endpoint-auth.ts';
|
|
|
28
28
|
* closes, so an interrupted call folds with this error result. Same text as
|
|
29
29
|
* the kimi provider's placeholder, for family consistency.
|
|
30
30
|
*/
|
|
31
|
+
/**
|
|
32
|
+
* A finished command's result text and failure. Codex reports a command that
|
|
33
|
+
* printed nothing with an empty aggregated output — a crash (exit 139) or a
|
|
34
|
+
* silent failure read as "no result" and the mirror wrote the interrupted
|
|
35
|
+
* placeholder. A finished command (an exit code, or a completed/failed status)
|
|
36
|
+
* always carries a result: its output, or "(无输出)", with a non-zero exit
|
|
37
|
+
* code appended and the result marked failed. A command still running yields
|
|
38
|
+
* none, so the fold keeps waiting for it.
|
|
39
|
+
* @param output - the item's aggregated output.
|
|
40
|
+
* @param exitCode - the item's exit code, when finished.
|
|
41
|
+
* @param status - the item's status.
|
|
42
|
+
* @returns the result text (undefined while running) and whether it failed.
|
|
43
|
+
*/
|
|
44
|
+
export declare function commandOutcome(output: unknown, exitCode: unknown, status: unknown): {
|
|
45
|
+
result?: string;
|
|
46
|
+
failed: boolean;
|
|
47
|
+
};
|
|
31
48
|
export declare const CODEX_INTERRUPTED_TOOL_RESULT = "\uFF08\u5DE5\u5177\u8C03\u7528\u88AB\u4E2D\u65AD\uFF0C\u672A\u80FD\u8BB0\u5F55\u8FD4\u56DE\u7ED3\u679C\uFF09";
|
|
32
49
|
/** Default POSIX grace between subprocess termination tiers. */
|
|
33
50
|
export declare const DEFAULT_DISPOSE_GRACE_MS = 3000;
|
|
@@ -37,6 +54,17 @@ export declare const DEFAULT_DISPOSE_GRACE_MS = 3000;
|
|
|
37
54
|
* provider-controlled error output only.
|
|
38
55
|
*/
|
|
39
56
|
export declare const CODEX_AUTH_FAILURE: RegExp;
|
|
57
|
+
/**
|
|
58
|
+
* A session's standing sandbox mode — the host's `sandbox/mode` vocabulary is
|
|
59
|
+
* codex's own (`read-only` | `workspace-write` | `danger-full-access`). Reads
|
|
60
|
+
* the delegating session first (the mode the user sees and changes), then
|
|
61
|
+
* the child's inherited record.
|
|
62
|
+
* @param ctx - context carrying the live session store.
|
|
63
|
+
* @param childSession - the member's child session, when one exists.
|
|
64
|
+
* @param parentSessionId - the delegating session.
|
|
65
|
+
* @returns the mode, or undefined when neither session records one.
|
|
66
|
+
*/
|
|
67
|
+
export declare function sessionSandboxMode(ctx: Context, childSession: Session | undefined, parentSessionId: string | undefined): CodexSandbox | undefined;
|
|
40
68
|
/** Codex sandbox policy values accepted by `codex exec --sandbox`. */
|
|
41
69
|
export type CodexSandbox = 'read-only' | 'workspace-write' | 'danger-full-access';
|
|
42
70
|
/**
|
|
@@ -76,6 +104,7 @@ export declare function registerCodexMemberRun(ctx: Context, childSessionId: str
|
|
|
76
104
|
export declare function codexCliVersion(ctx: Context, homeDir: string): Promise<string | undefined>;
|
|
77
105
|
export declare class CodexCliProvider implements SubagentProvider {
|
|
78
106
|
private readonly ctx;
|
|
107
|
+
/** An explicit policy from the plugin config; undefined follows the member's session. */
|
|
79
108
|
private readonly sandbox;
|
|
80
109
|
private readonly live?;
|
|
81
110
|
private readonly model?;
|
|
@@ -107,7 +136,18 @@ export declare class CodexCliProvider implements SubagentProvider {
|
|
|
107
136
|
* relay (a custom `model_providers` entry on the argv, the key in the
|
|
108
137
|
* env); with no key the spawn carries no credential at all.
|
|
109
138
|
*/
|
|
110
|
-
constructor(ctx: Context,
|
|
139
|
+
constructor(ctx: Context,
|
|
140
|
+
/** An explicit policy from the plugin config; undefined follows the member's session. */
|
|
141
|
+
sandbox?: CodexSandbox | undefined, live?: CodexLiveDriver | ((childSessionId: string) => CodexLiveDriver | undefined) | undefined, model?: ((scope?: string) => string | undefined) | undefined, overrides?: ((childSessionId: string) => string | undefined) | undefined, endpoint?: ((scopeHome: string) => Promise<CodexEndpointAuth>) | undefined);
|
|
142
|
+
/**
|
|
143
|
+
* The sandbox this round runs under: the plugin config's explicit policy
|
|
144
|
+
* when set, else the member's session's own standing mode — the delegating
|
|
145
|
+
* session's latest `sandbox/mode`, then the child's inherited one — so a
|
|
146
|
+
* member runs with exactly the access its session shows (a full-access
|
|
147
|
+
* session's Codex member was confined to workspace-write and Blender
|
|
148
|
+
* crashed under the seatbelt). `workspace-write` when neither says.
|
|
149
|
+
*/
|
|
150
|
+
sandboxFor(childSession: Session | undefined, parentSessionId: string | undefined): CodexSandbox;
|
|
111
151
|
/** The scope's endpoint credential for this round; absent resolver = never. */
|
|
112
152
|
private resolveEndpoint;
|
|
113
153
|
/** Resolve the live driver for one round's member, if live is on for it. */
|
|
@@ -252,6 +292,7 @@ export type CodexTranscriptLine =
|
|
|
252
292
|
name: string;
|
|
253
293
|
args?: string;
|
|
254
294
|
result?: string;
|
|
295
|
+
failed?: boolean;
|
|
255
296
|
};
|
|
256
297
|
/** Mutable fold state shared by the batch parse and the incremental parser. */
|
|
257
298
|
interface CodexStreamFoldState {
|
|
@@ -30,6 +30,30 @@ import { codexRolloutRoundFacts, usageFromCodex } from './records.js';
|
|
|
30
30
|
* closes, so an interrupted call folds with this error result. Same text as
|
|
31
31
|
* the kimi provider's placeholder, for family consistency.
|
|
32
32
|
*/
|
|
33
|
+
/**
|
|
34
|
+
* A finished command's result text and failure. Codex reports a command that
|
|
35
|
+
* printed nothing with an empty aggregated output — a crash (exit 139) or a
|
|
36
|
+
* silent failure read as "no result" and the mirror wrote the interrupted
|
|
37
|
+
* placeholder. A finished command (an exit code, or a completed/failed status)
|
|
38
|
+
* always carries a result: its output, or "(无输出)", with a non-zero exit
|
|
39
|
+
* code appended and the result marked failed. A command still running yields
|
|
40
|
+
* none, so the fold keeps waiting for it.
|
|
41
|
+
* @param output - the item's aggregated output.
|
|
42
|
+
* @param exitCode - the item's exit code, when finished.
|
|
43
|
+
* @param status - the item's status.
|
|
44
|
+
* @returns the result text (undefined while running) and whether it failed.
|
|
45
|
+
*/
|
|
46
|
+
export function commandOutcome(output, exitCode, status) {
|
|
47
|
+
const text = typeof output === 'string' && output.trim() !== '' ? output : undefined;
|
|
48
|
+
const code = typeof exitCode === 'number' && Number.isFinite(exitCode) ? exitCode : undefined;
|
|
49
|
+
const finished = code !== undefined || status === 'completed' || status === 'failed';
|
|
50
|
+
const failed = (code !== undefined && code !== 0) || status === 'failed';
|
|
51
|
+
if (!finished)
|
|
52
|
+
return text === undefined ? { failed: false } : { result: text, failed: false };
|
|
53
|
+
const exit = code !== undefined && code !== 0 ? `(退出码 ${code})` : status === 'failed' ? '(执行失败)' : '';
|
|
54
|
+
const result = text === undefined ? (exit === '' ? '(无输出)' : `${exit.slice(0, -1)},无输出)`) : exit === '' ? text : `${text}\n${exit}`;
|
|
55
|
+
return { result, failed };
|
|
56
|
+
}
|
|
33
57
|
export const CODEX_INTERRUPTED_TOOL_RESULT = '(工具调用被中断,未能记录返回结果)';
|
|
34
58
|
/** Quote one TOML basic string for the `-c` config override. */
|
|
35
59
|
function tomlString(value) {
|
|
@@ -43,6 +67,26 @@ export const DEFAULT_DISPOSE_GRACE_MS = 3_000;
|
|
|
43
67
|
* provider-controlled error output only.
|
|
44
68
|
*/
|
|
45
69
|
export const CODEX_AUTH_FAILURE = /401 unauthorized|unauthorized|authentication failed|not logged in|access token/i;
|
|
70
|
+
/**
|
|
71
|
+
* A session's standing sandbox mode — the host's `sandbox/mode` vocabulary is
|
|
72
|
+
* codex's own (`read-only` | `workspace-write` | `danger-full-access`). Reads
|
|
73
|
+
* the delegating session first (the mode the user sees and changes), then
|
|
74
|
+
* the child's inherited record.
|
|
75
|
+
* @param ctx - context carrying the live session store.
|
|
76
|
+
* @param childSession - the member's child session, when one exists.
|
|
77
|
+
* @param parentSessionId - the delegating session.
|
|
78
|
+
* @returns the mode, or undefined when neither session records one.
|
|
79
|
+
*/
|
|
80
|
+
export function sessionSandboxMode(ctx, childSession, parentSessionId) {
|
|
81
|
+
const sessions = ctx.get('sessions');
|
|
82
|
+
const parent = parentSessionId === undefined ? undefined : sessions?.get(SessionId(parentSessionId));
|
|
83
|
+
for (const session of [parent, childSession]) {
|
|
84
|
+
const mode = session?.snapshotEvents().filter(event => event.type === 'sandbox/mode').at(-1)?.data;
|
|
85
|
+
if (mode?.mode === 'read-only' || mode?.mode === 'workspace-write' || mode?.mode === 'danger-full-access')
|
|
86
|
+
return mode.mode;
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
46
90
|
/** Register one member run with the channel; see {@link CodexMemberRunHandle}. */
|
|
47
91
|
export function registerCodexMemberRun(ctx, childSessionId, parentSessionId) {
|
|
48
92
|
const registry = ctx.localAgent;
|
|
@@ -155,7 +199,9 @@ export class CodexCliProvider {
|
|
|
155
199
|
* relay (a custom `model_providers` entry on the argv, the key in the
|
|
156
200
|
* env); with no key the spawn carries no credential at all.
|
|
157
201
|
*/
|
|
158
|
-
constructor(ctx,
|
|
202
|
+
constructor(ctx,
|
|
203
|
+
/** An explicit policy from the plugin config; undefined follows the member's session. */
|
|
204
|
+
sandbox = undefined, live, model, overrides, endpoint) {
|
|
159
205
|
this.ctx = ctx;
|
|
160
206
|
this.sandbox = sandbox;
|
|
161
207
|
this.live = live;
|
|
@@ -163,6 +209,19 @@ export class CodexCliProvider {
|
|
|
163
209
|
this.overrides = overrides;
|
|
164
210
|
this.endpoint = endpoint;
|
|
165
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* The sandbox this round runs under: the plugin config's explicit policy
|
|
214
|
+
* when set, else the member's session's own standing mode — the delegating
|
|
215
|
+
* session's latest `sandbox/mode`, then the child's inherited one — so a
|
|
216
|
+
* member runs with exactly the access its session shows (a full-access
|
|
217
|
+
* session's Codex member was confined to workspace-write and Blender
|
|
218
|
+
* crashed under the seatbelt). `workspace-write` when neither says.
|
|
219
|
+
*/
|
|
220
|
+
sandboxFor(childSession, parentSessionId) {
|
|
221
|
+
if (this.sandbox !== undefined)
|
|
222
|
+
return this.sandbox;
|
|
223
|
+
return sessionSandboxMode(this.ctx, childSession, parentSessionId) ?? 'workspace-write';
|
|
224
|
+
}
|
|
166
225
|
/** The scope's endpoint credential for this round; absent resolver = never. */
|
|
167
226
|
resolveEndpoint(homeDir) {
|
|
168
227
|
return this.endpoint === undefined ? Promise.resolve(undefined) : this.endpoint(homeDir).catch(() => undefined);
|
|
@@ -311,6 +370,7 @@ export class CodexCliProvider {
|
|
|
311
370
|
homeDir,
|
|
312
371
|
childSession,
|
|
313
372
|
parentSessionId: request.parent.session.id,
|
|
373
|
+
sandbox: this.sandboxFor(childSession, request.parent.session.id),
|
|
314
374
|
...startModel === undefined ? {} : { startModel },
|
|
315
375
|
...configuration === undefined ? {} : { configuration: configuration.resolved },
|
|
316
376
|
// The thread id arrives with thread/start (server-assigned), far
|
|
@@ -352,7 +412,7 @@ export class CodexCliProvider {
|
|
|
352
412
|
...exec === undefined ? {} : { exec },
|
|
353
413
|
endpointLabel: baseUrl,
|
|
354
414
|
endpointArgv: endpointConfigOverrides(endpoint),
|
|
355
|
-
sandbox: this.
|
|
415
|
+
sandbox: this.sandboxFor(childSession, request.parent.session.id),
|
|
356
416
|
...(configuration?.resolved ?? resolveRoundModel(this.overrides?.(runId) ?? requestedModel, () => this.model?.(scope))),
|
|
357
417
|
disposeGraceMs: DEFAULT_DISPOSE_GRACE_MS,
|
|
358
418
|
spawn: spec => this.ctx.subprocess.spawn(spec),
|
|
@@ -441,6 +501,7 @@ export class CodexCliProvider {
|
|
|
441
501
|
homeDir,
|
|
442
502
|
childSession,
|
|
443
503
|
parentSessionId: request.parent.session.id,
|
|
504
|
+
sandbox: this.sandboxFor(childSession, request.parent.session.id),
|
|
444
505
|
resume: { cliSessionId: intent.cliSessionId, turn: nextTurn },
|
|
445
506
|
...startModel === undefined ? {} : { startModel },
|
|
446
507
|
...configuration === undefined ? {} : { configuration: configuration.resolved },
|
|
@@ -466,7 +527,7 @@ export class CodexCliProvider {
|
|
|
466
527
|
...exec === undefined ? {} : { exec },
|
|
467
528
|
endpointLabel: baseUrl,
|
|
468
529
|
endpointArgv: endpointConfigOverrides(endpoint),
|
|
469
|
-
sandbox: this.
|
|
530
|
+
sandbox: this.sandboxFor(childSession, request.parent.session.id),
|
|
470
531
|
...(configuration?.resolved ?? resolveRoundModel(this.overrides?.(intent.childSessionId) ?? requestedModel, () => this.model?.(scope))),
|
|
471
532
|
disposeGraceMs: DEFAULT_DISPOSE_GRACE_MS,
|
|
472
533
|
spawn: spec => this.ctx.subprocess.spawn(spec),
|
|
@@ -600,16 +661,15 @@ function foldCodexStreamLine(state, raw) {
|
|
|
600
661
|
// command with neither text nor output is still a call the CLI made.
|
|
601
662
|
countToolCall(state, 'command_execution');
|
|
602
663
|
const command = typeof item.command === 'string' ? item.command : undefined;
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
: undefined;
|
|
606
|
-
if (command !== undefined || output !== undefined) {
|
|
664
|
+
const outcome = commandOutcome(item.aggregated_output, item.exit_code, item.status);
|
|
665
|
+
if (command !== undefined || outcome.result !== undefined) {
|
|
607
666
|
state.lines.push({
|
|
608
667
|
kind: 'tool',
|
|
609
668
|
id: typeof item.id === 'string' ? item.id : `codex-tool-${state.lines.length}`,
|
|
610
669
|
name: 'Bash',
|
|
611
670
|
...command === undefined ? {} : { args: command },
|
|
612
|
-
...
|
|
671
|
+
...outcome.result === undefined ? {} : { result: outcome.result },
|
|
672
|
+
...outcome.failed ? { failed: true } : {},
|
|
613
673
|
});
|
|
614
674
|
}
|
|
615
675
|
}
|
|
@@ -1095,7 +1155,7 @@ export function appendCodexTranscriptLine(childSession, turn, step, line, usage)
|
|
|
1095
1155
|
message: createToolResultMessage({
|
|
1096
1156
|
callId: line.id,
|
|
1097
1157
|
content: [{ type: 'text', text: line.result ?? CODEX_INTERRUPTED_TOOL_RESULT }],
|
|
1098
|
-
isError: line.result === undefined,
|
|
1158
|
+
isError: line.result === undefined || line.failed === true,
|
|
1099
1159
|
}),
|
|
1100
1160
|
}, { surfaceOp: 'append', sourceEventSeqs: [call.seq] });
|
|
1101
1161
|
childSession.append('step/end', { turn, step });
|
package/lib/types/index.d.ts
CHANGED
|
@@ -28,7 +28,11 @@ export declare const CATALOG_WARMUP_DELAY_MS = 1500;
|
|
|
28
28
|
export declare const inject: string[];
|
|
29
29
|
/** Plugin config: the sandbox mode fresh delegations default to, plus the live driver. */
|
|
30
30
|
export interface Config {
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* An explicit Codex sandbox policy for `codex exec` / app-server threads.
|
|
33
|
+
* Unset (the default), each member runs under its own session's sandbox
|
|
34
|
+
* mode — the access the session shows.
|
|
35
|
+
*/
|
|
32
36
|
sandbox?: 'read-only' | 'workspace-write' | 'danger-full-access';
|
|
33
37
|
/**
|
|
34
38
|
* Deployment-level endpoint credential: the relay's base URL (an
|
package/lib/types/index.js
CHANGED
|
@@ -49,7 +49,7 @@ export const inject = ['localAgent', 'subagents', 'subprocess', 'settings'];
|
|
|
49
49
|
* behavior byte-identical.
|
|
50
50
|
*/
|
|
51
51
|
const SETTINGS_FIELDS = {
|
|
52
|
-
live: z.boolean().default(
|
|
52
|
+
live: z.boolean().default(true),
|
|
53
53
|
liveMirrorGranularity: z.union([z.const('event'), z.const('token')]).default('token'),
|
|
54
54
|
model: z.string(),
|
|
55
55
|
recentModels: z.array(z.string()).default([]),
|
|
@@ -117,7 +117,7 @@ export function apply(ctx, config) {
|
|
|
117
117
|
const model = vol(config.model);
|
|
118
118
|
const recentModels = vol(config.recentModels);
|
|
119
119
|
return {
|
|
120
|
-
live: vol(config.live) ??
|
|
120
|
+
live: vol(config.live) ?? true,
|
|
121
121
|
liveMirrorGranularity: vol(config.liveMirrorGranularity) ?? 'token',
|
|
122
122
|
...model === undefined ? {} : { model },
|
|
123
123
|
...recentModels === undefined ? {} : { recentModels },
|
|
@@ -131,6 +131,8 @@ export function apply(ctx, config) {
|
|
|
131
131
|
}
|
|
132
132
|
const face = settingsFace(ctx, {
|
|
133
133
|
namespace: CODEX_SETTINGS_NAMESPACE,
|
|
134
|
+
// Card writes live in the harness's own store (card-settings.ts), never the profile patch.
|
|
135
|
+
harness: 'codex',
|
|
134
136
|
legacySchema: CODEX_SETTINGS_SCHEMA,
|
|
135
137
|
legacyBase,
|
|
136
138
|
read: readSettings,
|
|
@@ -186,7 +188,8 @@ export function apply(ctx, config) {
|
|
|
186
188
|
}, CATALOG_WARMUP_DELAY_MS);
|
|
187
189
|
warmup.unref();
|
|
188
190
|
const liveSwitch = new LiveDriverSwitch(ctx, face, {
|
|
189
|
-
|
|
191
|
+
// Only an explicit policy overrides the member's session mode.
|
|
192
|
+
sandbox: config.sandbox,
|
|
190
193
|
// The member's scope decides whose list answers: a named scope's
|
|
191
194
|
// rounds read that scope's models.json, exactly like its endpoint
|
|
192
195
|
// credential.
|
|
@@ -218,7 +221,7 @@ export function apply(ctx, config) {
|
|
|
218
221
|
// cache.
|
|
219
222
|
catalogDefault: (scopedHome, cwd) => modelCatalog.readDefault(scopedHome, cwd),
|
|
220
223
|
});
|
|
221
|
-
const disposeProvider = ctx.subagents.registerProvider(new CodexCliProvider(ctx, sandbox, liveSwitch.resolve, resolveModel, childSessionId => memberModelOverrides.get(childSessionId), resolveEndpoint));
|
|
224
|
+
const disposeProvider = ctx.subagents.registerProvider(new CodexCliProvider(ctx, config.sandbox, liveSwitch.resolve, resolveModel, childSessionId => memberModelOverrides.get(childSessionId), resolveEndpoint));
|
|
222
225
|
const disposeHarness = ctx.localAgent.register({
|
|
223
226
|
prepareMember: async ({ binding, childSession, configuration, signal }) => {
|
|
224
227
|
const driver = liveSwitch.resolve(binding.childSessionId);
|
|
@@ -64,6 +64,8 @@ export interface CodexLiveRoundSpec {
|
|
|
64
64
|
readonly childSession: Session;
|
|
65
65
|
/** The delegating session (member-channel registration). */
|
|
66
66
|
readonly parentSessionId: string;
|
|
67
|
+
/** The sandbox the member's thread starts under (its session's mode; see CodexCliProvider.sandboxFor). */
|
|
68
|
+
readonly sandbox?: 'read-only' | 'workspace-write' | 'danger-full-access';
|
|
67
69
|
/**
|
|
68
70
|
* Resume round: continue the thread recorded as `cliSessionId`, appending
|
|
69
71
|
* under this turn number. Also the reattach path after a runtime crash.
|
package/lib/types/live-driver.js
CHANGED
|
@@ -27,7 +27,7 @@ import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
|
27
27
|
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
28
28
|
import { settleRunResult, subprocessRunHandle, } from '@deepseek-ai/dsh-subagent';
|
|
29
29
|
import { delegationEnv, persistChildSession, LiveStreamPublisher, LiveFlush, LIVE_FLUSH_INTERVAL_MS } from '@khorsheed/dsh-local-agent';
|
|
30
|
-
import { appendCodexRequestHeader, appendCodexTranscriptLine, codexAssistantEvent, codexLineText, DEFAULT_DISPOSE_GRACE_MS, registerCodexMemberRun, textTask, } from './codex-cli-provider.js';
|
|
30
|
+
import { appendCodexRequestHeader, appendCodexTranscriptLine, codexAssistantEvent, codexLineText, commandOutcome, DEFAULT_DISPOSE_GRACE_MS, registerCodexMemberRun, textTask, } from './codex-cli-provider.js';
|
|
31
31
|
import { codexRolloutRoundFacts } from './records.js';
|
|
32
32
|
import { endpointAuthEnv, endpointConfigOverrides } from './endpoint-auth.js';
|
|
33
33
|
/** Default idle lifetime of an unused resident runtime before reclaim. */
|
|
@@ -99,17 +99,16 @@ export function codexAppServerItemToLine(item) {
|
|
|
99
99
|
: undefined;
|
|
100
100
|
case 'commandExecution': {
|
|
101
101
|
const command = typeof item['command'] === 'string' ? item.command : undefined;
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
: undefined;
|
|
105
|
-
if (command === undefined && output === undefined)
|
|
102
|
+
const outcome = commandOutcome(item['aggregatedOutput'], item['exitCode'], item['status']);
|
|
103
|
+
if (command === undefined && outcome.result === undefined)
|
|
106
104
|
return undefined;
|
|
107
105
|
return {
|
|
108
106
|
kind: 'tool',
|
|
109
107
|
id: itemId ?? `codex-live-${randomUUID()}`,
|
|
110
108
|
name: 'Bash',
|
|
111
109
|
...command === undefined ? {} : { args: command },
|
|
112
|
-
...
|
|
110
|
+
...outcome.result === undefined ? {} : { result: outcome.result },
|
|
111
|
+
...outcome.failed ? { failed: true } : {},
|
|
113
112
|
};
|
|
114
113
|
}
|
|
115
114
|
case 'webSearch':
|
|
@@ -162,12 +161,30 @@ export function codexAppServerItemToLine(item) {
|
|
|
162
161
|
return { kind: 'tool', id: itemId ?? `codex-live-${randomUUID()}`, name: `collab/${tool}` };
|
|
163
162
|
}
|
|
164
163
|
case 'mcpToolCall': {
|
|
164
|
+
// An MCP call (the member bridge's room tools): {server, tool,
|
|
165
|
+
// arguments, status, result?: {content}, error?: {message}} — the
|
|
166
|
+
// arguments and the outcome ride the completed item; without them a
|
|
167
|
+
// member's ask_user_question read as a bare name.
|
|
165
168
|
const server = typeof item['server'] === 'string' ? item.server : undefined;
|
|
166
169
|
const tool = typeof item['tool'] === 'string' ? item.tool : undefined;
|
|
170
|
+
const args = item['arguments'] === undefined || item['arguments'] === null
|
|
171
|
+
? undefined
|
|
172
|
+
: typeof item['arguments'] === 'string' ? item['arguments'] : JSON.stringify(item['arguments']);
|
|
173
|
+
const outcome = item['result'];
|
|
174
|
+
const content = Array.isArray(outcome?.content) ? outcome.content : [];
|
|
175
|
+
const text = content
|
|
176
|
+
.filter((block) => typeof block === 'object' && block !== null)
|
|
177
|
+
.map(block => typeof block['text'] === 'string' ? block['text'] : undefined)
|
|
178
|
+
.filter((entry) => entry !== undefined && entry !== '')
|
|
179
|
+
.join('\n');
|
|
180
|
+
const error = item['error'];
|
|
181
|
+
const failure = typeof error?.message === 'string' && error.message !== '' ? error.message : undefined;
|
|
167
182
|
return {
|
|
168
183
|
kind: 'tool',
|
|
169
184
|
id: itemId ?? `codex-live-${randomUUID()}`,
|
|
170
185
|
name: server !== undefined && tool !== undefined ? `${server}/${tool}` : tool ?? 'mcp',
|
|
186
|
+
...args === undefined || args === '' || args === '{}' ? {} : { args },
|
|
187
|
+
...failure !== undefined ? { result: failure, failed: true } : text === '' ? {} : { result: text },
|
|
171
188
|
};
|
|
172
189
|
}
|
|
173
190
|
case 'plan':
|
|
@@ -719,7 +736,7 @@ export class CodexLiveDriver {
|
|
|
719
736
|
if (rt.threadId === undefined) {
|
|
720
737
|
const permissionParams = {
|
|
721
738
|
approvalPolicy: 'never',
|
|
722
|
-
sandbox: this.config.sandbox ?? 'workspace-write',
|
|
739
|
+
sandbox: this.config.sandbox ?? spec.sandbox ?? 'workspace-write',
|
|
723
740
|
};
|
|
724
741
|
if (spec.resume === undefined) {
|
|
725
742
|
const response = await rt.peer.request('thread/start', {
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"local-agent",
|
|
9
9
|
"codex"
|
|
10
10
|
],
|
|
11
|
-
"version": "0.2.
|
|
11
|
+
"version": "0.2.2",
|
|
12
12
|
"type": "module",
|
|
13
13
|
"main": "lib/index.js",
|
|
14
14
|
"types": "lib/types/index.d.ts",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"dsh": ">=0.1.5-rc.1 <0.3.0-0"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@khorsheed/dsh-local-agent": "^0.2.
|
|
72
|
+
"@khorsheed/dsh-local-agent": "^0.2.2"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
75
|
"@deepseek-ai/cordis": "^4.0.1",
|