@bitkyc08/opencodex 2.69.0 → 2.70.0
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/gui/dist/assets/App-_MrbTrBe.js +51 -0
- package/gui/dist/assets/{Tray-uqKRqfvw.js → Tray-5PuUalET.js} +1 -1
- package/gui/dist/assets/index-DKrO2eF3.js +86 -0
- package/gui/dist/assets/quota-summary-BCLivc0Q.js +1 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic-model-contract.ts +119 -0
- package/src/adapters/anthropic.ts +14 -83
- package/src/adapters/base.ts +2 -0
- package/src/adapters/command-code-project-context.ts +542 -0
- package/src/adapters/command-code-tool-text.ts +99 -67
- package/src/adapters/command-code.ts +5 -1
- package/src/adapters/cursor/catalog.ts +11 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/devin/cloud-direct/catalog.ts +91 -0
- package/src/adapters/devin/cloud-direct/chat.ts +78 -48
- package/src/adapters/devin/cloud-direct/tool-schema.ts +121 -0
- package/src/adapters/devin/context-overflow.ts +75 -0
- package/src/adapters/devin/live-models.ts +174 -13
- package/src/adapters/devin/reasoning-signature.ts +100 -0
- package/src/adapters/devin.ts +310 -117
- package/src/adapters/kiro/payload.ts +10 -10
- package/src/adapters/kiro-images.ts +58 -7
- package/src/adapters/openai-chat/passthrough.ts +3 -1
- package/src/adapters/openai-chat.ts +2 -1
- package/src/adapters/openai-responses/passthrough.ts +2 -1
- package/src/bridge/response-json.ts +15 -1
- package/src/bridge/sse.ts +3 -1
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/cli/account-extended.ts +31 -1
- package/src/cli/account.ts +3 -0
- package/src/cli/capabilities.ts +13 -0
- package/src/cli/client-start-fence.ts +36 -0
- package/src/cli/dispatch.ts +3 -2
- package/src/cli/index.ts +98 -117
- package/src/cli/provider-runtime.ts +23 -2
- package/src/cli/provider.ts +3 -0
- package/src/cli/resolve.ts +18 -0
- package/src/cli/start-owner-fence.ts +30 -0
- package/src/cli/stop-approval.ts +13 -1
- package/src/cli/stop-restore.ts +73 -0
- package/src/cli/tray-proxy.ts +221 -18
- package/src/cli/version-skew.ts +25 -23
- package/src/client/runtime.ts +8 -4
- package/src/codex/auth-api/account-list.ts +11 -5
- package/src/codex/auth-api/login-flow.ts +5 -3
- package/src/codex/auth-api/main-account-probe.ts +186 -110
- package/src/codex/auth-api/pool-mode-gate.ts +61 -7
- package/src/codex/auth-api/pool-quota-probe.ts +142 -90
- package/src/codex/auth-api/reset-credit-service.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +1 -0
- package/src/codex/catalog/model-hints.ts +3 -3
- package/src/codex/catalog/provider-models.ts +2 -0
- package/src/codex/low-quota-events.ts +28 -0
- package/src/codex/low-quota-observer.ts +23 -0
- package/src/codex/low-quota-protection.ts +272 -0
- package/src/codex/quota-query-backoff.ts +148 -0
- package/src/codex/quota.ts +7 -2
- package/src/config/diagnostics.ts +10 -1
- package/src/config/load-degrade.ts +17 -0
- package/src/config/mutation-lock.ts +3 -4
- package/src/config/process-state.ts +1 -1
- package/src/config/provider-validation.ts +22 -0
- package/src/config/schema/blocked-model-redirects.ts +17 -0
- package/src/config/schema/config-schema.ts +4 -1
- package/src/config/schema/leaf-validators.ts +33 -2
- package/src/config/serving-runtimes.ts +412 -0
- package/src/generated/compatibility-version.json +211 -115
- package/src/generated/model-metadata.ts +3 -3
- package/src/images/loop.ts +3 -0
- package/src/lib/errors.ts +2 -1
- package/src/lib/handled-signal-exit.ts +23 -0
- package/src/lib/request-failure-model.ts +1 -0
- package/src/lib/self-launch-argv.ts +9 -0
- package/src/lib/shadow-call.ts +5 -2
- package/src/lib/strict-semver.ts +26 -0
- package/src/lib/transient-service-path.ts +16 -0
- package/src/lib/windows-service-wrappers.ts +10 -6
- package/src/lib/winsw.ts +3 -1
- package/src/oauth/anthropic-model-routes.ts +105 -0
- package/src/oauth/anthropic-routing.ts +85 -48
- package/src/oauth/devin.ts +135 -5
- package/src/oauth/index.ts +20 -3
- package/src/oauth/kiro-terminal-failover.ts +23 -9
- package/src/oauth/pool-settings-capability.ts +10 -2
- package/src/oauth/store.ts +1 -1
- package/src/protocols/encoders/adapter-events.ts +3 -1
- package/src/providers/github-copilot-context.ts +27 -0
- package/src/providers/kiro-models.ts +4 -0
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/quota/account-cache.ts +8 -3
- package/src/providers/quota/devin.ts +286 -0
- package/src/providers/quota/vendor-probes-oauth.ts +6 -2
- package/src/providers/quota.ts +1 -1
- package/src/providers/registry/entries-core.ts +1 -1
- package/src/providers/registry/model-seeds.ts +5 -2
- package/src/responses/compaction-images.ts +27 -0
- package/src/router.ts +104 -16
- package/src/routing/analytics.ts +1 -0
- package/src/server/auth-cors.ts +9 -1
- package/src/server/background-lifecycle.ts +21 -10
- package/src/server/chat-completions.ts +2 -1
- package/src/server/chat-native.ts +1 -0
- package/src/server/index.ts +4 -8
- package/src/server/inference/client-encoder-delivery.ts +3 -0
- package/src/server/management/context.ts +3 -0
- package/src/server/management/low-quota-routes.ts +14 -0
- package/src/server/management/oauth-account-routes.ts +21 -1
- package/src/server/management/provider-overwrite-carry.ts +79 -6
- package/src/server/management/provider-routes.ts +41 -28
- package/src/server/management/route-registry.ts +1 -0
- package/src/server/management-api.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +3 -0
- package/src/server/responses/adapter-delivery.ts +3 -0
- package/src/server/responses/adapter-dispatch.ts +20 -4
- package/src/server/responses/antigravity-validation-refusal.ts +20 -0
- package/src/server/responses/core-auth.ts +1 -0
- package/src/server/responses/core-codex-account.ts +1 -0
- package/src/server/responses/core-normalize.ts +4 -0
- package/src/server/responses/passthrough-dispatch.ts +8 -1
- package/src/server/responses/request-sidecar-auth.ts +2 -0
- package/src/server/responses/request-transport.ts +27 -10
- package/src/server/responses/response-effects.ts +4 -1
- package/src/server/responses/run-turn-execution.ts +126 -42
- package/src/server/responses/sidecar-execution.ts +4 -1
- package/src/service/diagnostics.ts +3 -1
- package/src/service/guarded-manager-target.ts +39 -12
- package/src/service/launchd.ts +14 -6
- package/src/service/repair.ts +6 -1
- package/src/service/service-child-ownership.ts +168 -0
- package/src/service/state.ts +22 -4
- package/src/service/systemd.ts +2 -2
- package/src/service/windows-guarded-manager.ts +530 -0
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service.ts +2 -0
- package/src/types/config.ts +26 -0
- package/src/types/provider.ts +12 -0
- package/src/types/request.ts +2 -0
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +13 -0
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/vision/anthropic-describe.ts +2 -1
- package/src/web-search/anthropic-executor.ts +2 -1
- package/src/web-search/loop.ts +1 -0
- package/gui/dist/assets/App-DDbgOTh6.js +0 -51
- package/gui/dist/assets/index-CuaBJkG4.js +0 -86
- package/gui/dist/assets/quota-summary-COppQSVq.js +0 -1
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows manager-to-proxy binding for consent-bound desktop stops.
|
|
3
|
+
*
|
|
4
|
+
* guarded-manager-target probes launchd and systemd registrations and proves the
|
|
5
|
+
* running manager owns the approved PID through its parent chain. Windows had no
|
|
6
|
+
* such proof: any registered Task Scheduler task or WinSW service made the whole
|
|
7
|
+
* platform "unknown", so a consent-bound stop always answered approval-changed
|
|
8
|
+
* while resolve still reported takeover supported — a promise the stop path could
|
|
9
|
+
* not keep. This module performs the Windows half of the same contract: prove the
|
|
10
|
+
* manager, or say exactly why it cannot be proven.
|
|
11
|
+
*
|
|
12
|
+
* The evidence mirrors what a careful operator checks by hand:
|
|
13
|
+
*
|
|
14
|
+
* - Task Scheduler: the task must be present, its registered definition must be a
|
|
15
|
+
* recognized OpenCodex layout (current healthy or the exact legacy shape — never
|
|
16
|
+
* a hand-edited or foreign task under the fixed name), it must be RUNNING, and
|
|
17
|
+
* the approved PID's ancestor chain must contain a wrapper process whose command
|
|
18
|
+
* line carries this home's canonical launcher/script path as a complete token.
|
|
19
|
+
* - WinSW: the service must be started, its registered binary path must be this
|
|
20
|
+
* installation's WinSW executable, and the SCM-reported service PID must be an
|
|
21
|
+
* ancestor of the approved PID.
|
|
22
|
+
* - Anything unreadable stays unknown: a manager that cannot be proven stays
|
|
23
|
+
* untouchable, exactly like a launchd job whose state cannot be read.
|
|
24
|
+
*/
|
|
25
|
+
import { execFileSync } from "node:child_process";
|
|
26
|
+
|
|
27
|
+
import { resolveTrustedWindowsPowerShellExe } from "../lib/windows-elevation";
|
|
28
|
+
import { cachedCurrentWindowsIdentity, resolveCurrentWindowsPrincipal, WINDOWS_PRINCIPAL_LOOKUP_TIMEOUT_MS } from "../lib/windows-user-principal";
|
|
29
|
+
import { statusWinswRaw, winswExePath, WINSW_SERVICE_ID, type WinswStatus } from "../lib/winsw";
|
|
30
|
+
import type { GuardedManagerStopped, GuardedManagerTarget } from "./guarded-manager-target";
|
|
31
|
+
import { TASK, windowsLauncherVbsPath, windowsServiceScriptPath } from "./state";
|
|
32
|
+
import { probeWindowsSchedulerTask, querySchtasks, windowsWscript, type WindowsSchedulerTaskProbe } from "./windows-scheduler";
|
|
33
|
+
import { windowsTaskRegistrationHealthy, windowsTaskRegistrationRefreshableLegacy } from "./windows-taskxml";
|
|
34
|
+
|
|
35
|
+
/** Runtime table of one Win32 process; a null parent means the chain is unreadable. */
|
|
36
|
+
export interface WindowsProcessEntry {
|
|
37
|
+
pid: number;
|
|
38
|
+
parentPid: number | null;
|
|
39
|
+
name: string | null;
|
|
40
|
+
commandLine: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Get-ScheduledTask State, collapsed to what the stop decision needs. */
|
|
44
|
+
export type WindowsTaskState = "running" | "not-running" | "unknown";
|
|
45
|
+
|
|
46
|
+
/** SCM view of the WinSW service registration and its live process. */
|
|
47
|
+
export interface WindowsWinswServiceInfo {
|
|
48
|
+
state: string;
|
|
49
|
+
pid: number | null;
|
|
50
|
+
pathName: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Injectable seams for the Windows probes. Tests pass hermetic substitutes;
|
|
55
|
+
* production callers leave everything unset so the live queries below run. Every
|
|
56
|
+
* default implementation exits early off win32, mirroring probeWindowsSchedulerTask.
|
|
57
|
+
*/
|
|
58
|
+
export interface WindowsGuardedManagerDeps {
|
|
59
|
+
winProcs?: () => WindowsProcessEntry[] | null;
|
|
60
|
+
winTaskXml?: () => string;
|
|
61
|
+
winTaskState?: () => WindowsTaskState;
|
|
62
|
+
winService?: () => WindowsWinswServiceInfo | null;
|
|
63
|
+
winRegistrationOurs?: (xml: string, expectedUserIds: readonly string[] | null) => boolean;
|
|
64
|
+
winTaskUserIds?: () => readonly string[] | null;
|
|
65
|
+
winScriptPath?: () => string;
|
|
66
|
+
winLauncherPath?: () => string;
|
|
67
|
+
winWinswExePath?: () => string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type ResolvedWindowsDeps = Required<WindowsGuardedManagerDeps>;
|
|
71
|
+
|
|
72
|
+
const POWERSHELL_TIMEOUT_MS = 5_000;
|
|
73
|
+
const PROCESS_SNAPSHOT_BUFFER = 16 * 1024 * 1024;
|
|
74
|
+
const ANCESTOR_DEPTH_LIMIT = 32;
|
|
75
|
+
|
|
76
|
+
function runPowerShell(command: string): string {
|
|
77
|
+
return execFileSync(resolveTrustedWindowsPowerShellExe(), [
|
|
78
|
+
"-NoProfile", "-NoLogo", "-NonInteractive", "-Command", command,
|
|
79
|
+
], {
|
|
80
|
+
encoding: "utf8",
|
|
81
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
82
|
+
timeout: POWERSHELL_TIMEOUT_MS,
|
|
83
|
+
windowsHide: true,
|
|
84
|
+
maxBuffer: PROCESS_SNAPSHOT_BUFFER,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* One Win32_Process snapshot covering every identity question in this module:
|
|
90
|
+
* parent chains for the bound proof, and names and command lines for the wrapper scan.
|
|
91
|
+
* A null return means the enumeration itself could not run — callers fail closed,
|
|
92
|
+
* because an unreadable process table is not evidence that no wrapper survives.
|
|
93
|
+
*/
|
|
94
|
+
export function windowsProcessList(): WindowsProcessEntry[] | null {
|
|
95
|
+
if (process.platform !== "win32") return null;
|
|
96
|
+
try {
|
|
97
|
+
const output = runPowerShell(
|
|
98
|
+
"Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,CommandLine | ConvertTo-Json -Compress",
|
|
99
|
+
);
|
|
100
|
+
const trimmed = output.trim();
|
|
101
|
+
if (!trimmed) return [];
|
|
102
|
+
const parsed: unknown = JSON.parse(trimmed);
|
|
103
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
104
|
+
const entries: WindowsProcessEntry[] = [];
|
|
105
|
+
for (const row of rows) {
|
|
106
|
+
if (typeof row !== "object" || row === null) continue;
|
|
107
|
+
const record = row as { ProcessId?: unknown; ParentProcessId?: unknown; Name?: unknown; CommandLine?: unknown };
|
|
108
|
+
const pid = Number(record.ProcessId);
|
|
109
|
+
const parent = Number(record.ParentProcessId);
|
|
110
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) continue;
|
|
111
|
+
entries.push({
|
|
112
|
+
pid,
|
|
113
|
+
parentPid: Number.isSafeInteger(parent) && parent > 0 ? parent : null,
|
|
114
|
+
name: typeof record.Name === "string" ? record.Name : null,
|
|
115
|
+
commandLine: typeof record.CommandLine === "string" ? record.CommandLine : null,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return entries;
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The registered task's RUNNING state, read through Get-ScheduledTask whose State
|
|
126
|
+
* values are enum names and therefore locale-independent — parsing schtasks table
|
|
127
|
+
* output was rejected earlier because its values are localized.
|
|
128
|
+
*/
|
|
129
|
+
export function windowsScheduledTaskState(taskName = TASK): WindowsTaskState {
|
|
130
|
+
if (process.platform !== "win32") return "unknown";
|
|
131
|
+
try {
|
|
132
|
+
const output = runPowerShell(
|
|
133
|
+
"(Get-ScheduledTask -TaskName '" + taskName.replace(/'/g, "''") + "' -ErrorAction Stop).State",
|
|
134
|
+
).trim();
|
|
135
|
+
if (/^running$/i.test(output)) return "running";
|
|
136
|
+
if (/^(ready|disabled)$/i.test(output)) return "not-running";
|
|
137
|
+
return "unknown";
|
|
138
|
+
} catch {
|
|
139
|
+
return "unknown";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The registered task XML, decoded exactly like every other scheduler reader. */
|
|
144
|
+
export function windowsScheduledTaskXml(taskName = TASK): string {
|
|
145
|
+
return querySchtasks(["/query", "/tn", taskName, "/xml"]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The WinSW service's SCM row. state "absent" means the query ran and the service
|
|
150
|
+
* is genuinely not installed; a null return means the query itself failed — the
|
|
151
|
+
* difference between "no manager" and "unproven manager".
|
|
152
|
+
*/
|
|
153
|
+
export function windowsWinswServiceInfo(serviceName = WINSW_SERVICE_ID): WindowsWinswServiceInfo | null {
|
|
154
|
+
if (process.platform !== "win32") return null;
|
|
155
|
+
try {
|
|
156
|
+
const output = runPowerShell(
|
|
157
|
+
"Get-CimInstance Win32_Service | Where-Object { $_.Name -eq '" + serviceName.replace(/'/g, "''") + "' } "
|
|
158
|
+
+ "| Select-Object ProcessId,State,PathName | ConvertTo-Json -Compress",
|
|
159
|
+
).trim();
|
|
160
|
+
if (!output) return { state: "absent", pid: null, pathName: null };
|
|
161
|
+
const parsed: unknown = JSON.parse(output);
|
|
162
|
+
const record = (Array.isArray(parsed) ? parsed[0] : parsed) as {
|
|
163
|
+
ProcessId?: unknown; State?: unknown; PathName?: unknown;
|
|
164
|
+
} | null | undefined;
|
|
165
|
+
if (typeof record !== "object" || record === null) return null;
|
|
166
|
+
const pid = Number(record.ProcessId);
|
|
167
|
+
return {
|
|
168
|
+
state: typeof record.State === "string" && record.State.trim() ? record.State.trim() : "unknown",
|
|
169
|
+
pid: Number.isSafeInteger(pid) && pid > 0 ? pid : null,
|
|
170
|
+
pathName: typeof record.PathName === "string" && record.PathName.trim() ? record.PathName.trim() : null,
|
|
171
|
+
};
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** The two OpenCodex task shapes this stop may legitimately end. */
|
|
178
|
+
export function windowsTaskRegistrationIsOurs(
|
|
179
|
+
xml: string,
|
|
180
|
+
expectedUserIds: readonly string[] | null,
|
|
181
|
+
launcher = windowsLauncherVbsPath(),
|
|
182
|
+
): boolean {
|
|
183
|
+
if (expectedUserIds === null) return false;
|
|
184
|
+
return windowsTaskRegistrationHealthy(xml, windowsWscript(), launcher, expectedUserIds)
|
|
185
|
+
|| windowsTaskRegistrationRefreshableLegacy(xml, windowsWscript(), launcher);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function currentWindowsTaskUserIds(): readonly string[] | null {
|
|
189
|
+
try {
|
|
190
|
+
let identity = cachedCurrentWindowsIdentity();
|
|
191
|
+
if (!identity) {
|
|
192
|
+
resolveCurrentWindowsPrincipal(WINDOWS_PRINCIPAL_LOOKUP_TIMEOUT_MS);
|
|
193
|
+
identity = cachedCurrentWindowsIdentity();
|
|
194
|
+
}
|
|
195
|
+
return identity ? [identity.sid, identity.name] : null;
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* True when path appears in commandLine as a COMPLETE token — the same boundary
|
|
203
|
+
* rule the windows-service-wrappers kill list uses, ported so the read-only proof
|
|
204
|
+
* cannot accept a substring hit. A quoted path inside an argument string counts;
|
|
205
|
+
* a suffix or prefix glued to it does not.
|
|
206
|
+
*/
|
|
207
|
+
export function commandLineHasPathToken(commandLine: string | null | undefined, path: string): boolean {
|
|
208
|
+
if (!commandLine || !path) return false;
|
|
209
|
+
const haystack = commandLine.toLowerCase();
|
|
210
|
+
const needle = path.toLowerCase();
|
|
211
|
+
const boundary = (ch: string | undefined): boolean => ch === undefined || /[\s"']/.test(ch);
|
|
212
|
+
let index = 0;
|
|
213
|
+
for (;;) {
|
|
214
|
+
index = haystack.indexOf(needle, index);
|
|
215
|
+
if (index < 0) return false;
|
|
216
|
+
const end = index + needle.length;
|
|
217
|
+
if (boundary(haystack[index - 1]) && boundary(haystack[end])) return true;
|
|
218
|
+
index = end;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function resolveWindowsDeps(deps: WindowsGuardedManagerDeps, live: boolean): ResolvedWindowsDeps {
|
|
223
|
+
// "live" is false whenever the caller simulated platform win32 (tests or a future
|
|
224
|
+
// cross-platform probe): every unset probe then returns its least-proving answer
|
|
225
|
+
// instead of silently querying the host the simulation did not mean to touch.
|
|
226
|
+
return {
|
|
227
|
+
winProcs: deps.winProcs ?? (live ? windowsProcessList : () => null),
|
|
228
|
+
winTaskXml: deps.winTaskXml ?? (live ? () => windowsScheduledTaskXml() : () => {
|
|
229
|
+
throw new Error("Windows task query is unavailable in this context");
|
|
230
|
+
}),
|
|
231
|
+
winTaskState: deps.winTaskState ?? (live ? () => windowsScheduledTaskState() : () => "unknown"),
|
|
232
|
+
winService: deps.winService ?? (live ? () => windowsWinswServiceInfo() : () => null),
|
|
233
|
+
winRegistrationOurs: deps.winRegistrationOurs ?? ((xml, expectedUserIds) =>
|
|
234
|
+
windowsTaskRegistrationIsOurs(xml, expectedUserIds, (deps.winLauncherPath ?? windowsLauncherVbsPath)())),
|
|
235
|
+
winTaskUserIds: deps.winTaskUserIds ?? (live ? currentWindowsTaskUserIds : () => null),
|
|
236
|
+
winScriptPath: deps.winScriptPath ?? windowsServiceScriptPath,
|
|
237
|
+
winLauncherPath: deps.winLauncherPath ?? windowsLauncherVbsPath,
|
|
238
|
+
winWinswExePath: deps.winWinswExePath ?? winswExePath,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function wrapperPaths(io: ResolvedWindowsDeps): string[] {
|
|
243
|
+
return [io.winLauncherPath(), io.winScriptPath()];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Ancestor chain via the snapshot table — same depth cap as the POSIX walk. */
|
|
247
|
+
export function isDescendantOf(
|
|
248
|
+
ancestorPid: number,
|
|
249
|
+
pid: number,
|
|
250
|
+
processes: readonly WindowsProcessEntry[],
|
|
251
|
+
): boolean {
|
|
252
|
+
const parents = new Map(processes.map(entry => [entry.pid, entry.parentPid]));
|
|
253
|
+
let current = pid;
|
|
254
|
+
const seen = new Set<number>();
|
|
255
|
+
for (let depth = 0; depth < ANCESTOR_DEPTH_LIMIT; depth += 1) {
|
|
256
|
+
if (current === ancestorPid) return true;
|
|
257
|
+
if (seen.has(current)) return false;
|
|
258
|
+
seen.add(current);
|
|
259
|
+
const parent = parents.get(current);
|
|
260
|
+
if (parent === undefined || parent === null || parent <= 0) return false;
|
|
261
|
+
current = parent;
|
|
262
|
+
}
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Every ancestor of pid whose own command line carries a canonical wrapper path,
|
|
268
|
+
* nearest first. A hit means the wscript/cmd supervision chain between the task
|
|
269
|
+
* action and the proxy is intact for THIS home — not just that some OpenCodex
|
|
270
|
+
* process exists somewhere on the box.
|
|
271
|
+
*/
|
|
272
|
+
export function ancestorWrapperPids(
|
|
273
|
+
pid: number,
|
|
274
|
+
processes: readonly WindowsProcessEntry[],
|
|
275
|
+
canonicalPaths: readonly string[],
|
|
276
|
+
): number[] {
|
|
277
|
+
const byPid = new Map(processes.map(entry => [entry.pid, entry]));
|
|
278
|
+
const found: number[] = [];
|
|
279
|
+
const seen = new Set<number>();
|
|
280
|
+
let current = pid;
|
|
281
|
+
for (let depth = 0; depth < ANCESTOR_DEPTH_LIMIT; depth += 1) {
|
|
282
|
+
const entry = byPid.get(current);
|
|
283
|
+
if (!entry || seen.has(current)) break;
|
|
284
|
+
seen.add(current);
|
|
285
|
+
const parent = entry.parentPid;
|
|
286
|
+
if (parent === null || parent <= 0) break;
|
|
287
|
+
const parentEntry = byPid.get(parent);
|
|
288
|
+
if (parentEntry?.commandLine
|
|
289
|
+
&& canonicalPaths.some(path => commandLineHasPathToken(parentEntry.commandLine, path))) {
|
|
290
|
+
found.push(parent);
|
|
291
|
+
}
|
|
292
|
+
current = parent;
|
|
293
|
+
}
|
|
294
|
+
return found;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Any live process whose command line carries a canonical wrapper path. A wrapper
|
|
299
|
+
* that survived schtasks /end keeps respawning the proxy, so the post-stop probe
|
|
300
|
+
* treats one as an ACTIVE manager even when the task itself reports not running.
|
|
301
|
+
* The enumerating probe cannot match itself — the patterns are compared in
|
|
302
|
+
* TypeScript, never embedded in the PowerShell text — but excluding the current
|
|
303
|
+
* process costs nothing if that ever changes.
|
|
304
|
+
*/
|
|
305
|
+
export function wrapperProcessesAlive(
|
|
306
|
+
processes: readonly WindowsProcessEntry[],
|
|
307
|
+
canonicalPaths: readonly string[],
|
|
308
|
+
): number[] {
|
|
309
|
+
return processes
|
|
310
|
+
.filter(entry => entry.pid !== process.pid && entry.commandLine
|
|
311
|
+
&& canonicalPaths.some(path => commandLineHasPathToken(entry.commandLine, path)))
|
|
312
|
+
.map(entry => entry.pid);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* A wscript/cscript/cmd process whose command line could not be read. It may be this home's
|
|
317
|
+
* wrapper, and nothing can prove otherwise. Only processes tied to the approved proxy by
|
|
318
|
+
* ancestry count: a non-admin CIM query returns no command line for other users' and elevated
|
|
319
|
+
* processes, so treating every unreadable cmd.exe on the machine as a wrapper would make the
|
|
320
|
+
* guarded stop unreachable on an ordinary desktop.
|
|
321
|
+
*/
|
|
322
|
+
function isUnreadableWrapperCandidate(entry: WindowsProcessEntry): boolean {
|
|
323
|
+
return !entry.commandLine?.trim() && /^(?:wscript|cscript|cmd)\.exe$/i.test(entry.name ?? "");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Unreadable wrapper candidates among pid's ancestors, nearest first. */
|
|
327
|
+
export function unreadableAncestorWrapperPids(pid: number, processes: readonly WindowsProcessEntry[]): number[] {
|
|
328
|
+
const byPid = new Map(processes.map(entry => [entry.pid, entry]));
|
|
329
|
+
const found: number[] = [];
|
|
330
|
+
const seen = new Set<number>();
|
|
331
|
+
let current = pid;
|
|
332
|
+
for (let depth = 0; depth < ANCESTOR_DEPTH_LIMIT; depth += 1) {
|
|
333
|
+
const entry = byPid.get(current);
|
|
334
|
+
if (!entry || seen.has(current)) break;
|
|
335
|
+
seen.add(current);
|
|
336
|
+
const parent = entry.parentPid;
|
|
337
|
+
if (parent === null || parent <= 0) break;
|
|
338
|
+
const parentEntry = byPid.get(parent);
|
|
339
|
+
if (parentEntry && isUnreadableWrapperCandidate(parentEntry)) found.push(parent);
|
|
340
|
+
current = parent;
|
|
341
|
+
}
|
|
342
|
+
return found;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Unreadable wrapper candidates that are the former manager process or descend from it. After
|
|
347
|
+
* the approved child exits, such a survivor can still respawn it, so post-stop it is unknown.
|
|
348
|
+
*/
|
|
349
|
+
export function unreadableWrappersOfManager(managerPid: number, processes: readonly WindowsProcessEntry[]): number[] {
|
|
350
|
+
const byPid = new Map(processes.map(entry => [entry.pid, entry]));
|
|
351
|
+
return processes.filter(entry => {
|
|
352
|
+
if (!isUnreadableWrapperCandidate(entry)) return false;
|
|
353
|
+
let current: number | null = entry.pid;
|
|
354
|
+
for (let depth = 0; current !== null && current > 0 && depth < ANCESTOR_DEPTH_LIMIT; depth += 1) {
|
|
355
|
+
if (current === managerPid) return true;
|
|
356
|
+
current = byPid.get(current)?.parentPid ?? null;
|
|
357
|
+
}
|
|
358
|
+
return false;
|
|
359
|
+
}).map(entry => entry.pid);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function unknown(reason: string): GuardedManagerTarget {
|
|
363
|
+
return { kind: "unknown", reason };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function inspectWindowsSchedulerManager(
|
|
367
|
+
approvedPid: number,
|
|
368
|
+
io: ResolvedWindowsDeps,
|
|
369
|
+
): GuardedManagerTarget {
|
|
370
|
+
let xml: string;
|
|
371
|
+
try {
|
|
372
|
+
xml = io.winTaskXml();
|
|
373
|
+
} catch {
|
|
374
|
+
return unknown("the Task Scheduler registration could not be read");
|
|
375
|
+
}
|
|
376
|
+
const processes = io.winProcs();
|
|
377
|
+
if (processes === null) return unknown("the Windows process list could not be read");
|
|
378
|
+
const paths = wrapperPaths(io);
|
|
379
|
+
const ancestors = ancestorWrapperPids(approvedPid, processes, paths);
|
|
380
|
+
const strays = wrapperProcessesAlive(processes, paths);
|
|
381
|
+
if (unreadableAncestorWrapperPids(approvedPid, processes).length > 0) {
|
|
382
|
+
return unknown("a possible scheduler wrapper has an unreadable command line");
|
|
383
|
+
}
|
|
384
|
+
const state = io.winTaskState();
|
|
385
|
+
if (state === "unknown") return unknown("the registered task's running state could not be proven");
|
|
386
|
+
if (state === "not-running") {
|
|
387
|
+
// A task with no running instance owes nothing ONLY when no wrapper survives.
|
|
388
|
+
// A live wrapper outside a task instance — whether it parents the approved
|
|
389
|
+
// PID or not — is unaccounted supervision: schtasks /end would report success
|
|
390
|
+
// on the inert task while the wrapper stayed alive to respawn the proxy.
|
|
391
|
+
return strays.length === 0
|
|
392
|
+
? { kind: "absent" }
|
|
393
|
+
: unknown("a surviving scheduler wrapper could not be tied to the registered task");
|
|
394
|
+
}
|
|
395
|
+
if (!io.winRegistrationOurs(xml, io.winTaskUserIds())) {
|
|
396
|
+
return unknown("the registered task is not a recognized OpenCodex definition");
|
|
397
|
+
}
|
|
398
|
+
if (ancestors.length === 0) {
|
|
399
|
+
return unknown(strays.length === 0
|
|
400
|
+
? "the scheduler task is running but does not own the approved process"
|
|
401
|
+
: "a surviving scheduler wrapper does not own the approved process");
|
|
402
|
+
}
|
|
403
|
+
// schtasks /end does not reliably cascade to the wrapper's child, so the bound
|
|
404
|
+
// manager stop cannot be trusted to end the proxy — the approved PID still gets
|
|
405
|
+
// its own graceful signal afterwards.
|
|
406
|
+
return {
|
|
407
|
+
kind: "bound", pid: approvedPid, managerPid: ancestors[0]!,
|
|
408
|
+
backend: "scheduler", childNeedsSeparateStop: true,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function inspectWindowsWinswManager(
|
|
413
|
+
approvedPid: number,
|
|
414
|
+
io: ResolvedWindowsDeps,
|
|
415
|
+
): GuardedManagerTarget {
|
|
416
|
+
const info = io.winService();
|
|
417
|
+
if (info === null) return unknown("the native service could not be queried");
|
|
418
|
+
if (info.state !== "Running" && info.state !== "Started") {
|
|
419
|
+
return unknown("the native service is registered but not running (" + info.state + ")");
|
|
420
|
+
}
|
|
421
|
+
if (!info.pathName || !commandLineHasPathToken(info.pathName, io.winWinswExePath())) {
|
|
422
|
+
return unknown("the native service binary is not this installation's WinSW executable");
|
|
423
|
+
}
|
|
424
|
+
if (info.pid === null) return unknown("the native service PID could not be proven");
|
|
425
|
+
const processes = io.winProcs();
|
|
426
|
+
if (processes === null) return unknown("the Windows process list could not be read");
|
|
427
|
+
return isDescendantOf(info.pid, approvedPid, processes)
|
|
428
|
+
? {
|
|
429
|
+
kind: "bound", pid: approvedPid, managerPid: info.pid,
|
|
430
|
+
backend: "winsw", childNeedsSeparateStop: true,
|
|
431
|
+
}
|
|
432
|
+
: unknown("the native service does not own the approved process");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Windows half of inspectGuardedManagerTarget. The "live" flag tells the default
|
|
437
|
+
* probes whether the platform is real Windows: a simulated win32 must never fire a
|
|
438
|
+
* real schtasks/PowerShell query at the host, so absent probes then degrade to
|
|
439
|
+
* their least-proving answer — the same fail-closed shape the platform showed
|
|
440
|
+
* before this module existed.
|
|
441
|
+
*/
|
|
442
|
+
export function inspectWindowsGuardedManager(
|
|
443
|
+
approvedPid: number,
|
|
444
|
+
scheduler: WindowsSchedulerTaskProbe,
|
|
445
|
+
native: WinswStatus,
|
|
446
|
+
deps: WindowsGuardedManagerDeps,
|
|
447
|
+
live: boolean,
|
|
448
|
+
): GuardedManagerTarget {
|
|
449
|
+
const io = resolveWindowsDeps(deps, live);
|
|
450
|
+
if (scheduler.status === "unknown") {
|
|
451
|
+
return unknown("the Task Scheduler state could not be read (" + scheduler.detail + ")");
|
|
452
|
+
}
|
|
453
|
+
if (native === "unknown") {
|
|
454
|
+
return unknown("the native WinSW service state could not be read");
|
|
455
|
+
}
|
|
456
|
+
if (scheduler.status === "present" && native !== "nonexistent") {
|
|
457
|
+
// Same dual-backend conflict the install verification already refuses: either
|
|
458
|
+
// manager could respawn the proxy, so neither may be stopped by name alone.
|
|
459
|
+
return unknown("the Task Scheduler task and the native WinSW service are both registered");
|
|
460
|
+
}
|
|
461
|
+
if (scheduler.status === "present") {
|
|
462
|
+
return inspectWindowsSchedulerManager(approvedPid, io);
|
|
463
|
+
}
|
|
464
|
+
if (native === "started") {
|
|
465
|
+
return inspectWindowsWinswManager(approvedPid, io);
|
|
466
|
+
}
|
|
467
|
+
if (native === "stopped") {
|
|
468
|
+
// A stopped service cannot respawn anything, but a detached wrapper can.
|
|
469
|
+
const processes = io.winProcs();
|
|
470
|
+
if (processes === null) return unknown("the Windows process list could not be read");
|
|
471
|
+
if (unreadableAncestorWrapperPids(approvedPid, processes).length > 0) {
|
|
472
|
+
return unknown("a possible scheduler wrapper has an unreadable command line");
|
|
473
|
+
}
|
|
474
|
+
return wrapperProcessesAlive(processes, wrapperPaths(io)).length === 0
|
|
475
|
+
? { kind: "absent" }
|
|
476
|
+
: unknown("a surviving scheduler wrapper could not be tied to an installed manager");
|
|
477
|
+
}
|
|
478
|
+
// scheduler absent + winsw nonexistent — still have to rule out a detached wrapper.
|
|
479
|
+
const processes = io.winProcs();
|
|
480
|
+
if (processes === null) return unknown("the Windows process list could not be read");
|
|
481
|
+
if (unreadableAncestorWrapperPids(approvedPid, processes).length > 0) {
|
|
482
|
+
return unknown("a possible scheduler wrapper has an unreadable command line");
|
|
483
|
+
}
|
|
484
|
+
return wrapperProcessesAlive(processes, wrapperPaths(io)).length === 0
|
|
485
|
+
? { kind: "absent" }
|
|
486
|
+
: unknown("a surviving scheduler wrapper could not be tied to an installed manager");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export interface WindowsGuardedStopDeps {
|
|
490
|
+
scheduler?: () => WindowsSchedulerTaskProbe;
|
|
491
|
+
winsw?: () => WinswStatus;
|
|
492
|
+
winProcs?: () => WindowsProcessEntry[] | null;
|
|
493
|
+
winTaskState?: () => WindowsTaskState;
|
|
494
|
+
winScriptPath?: () => string;
|
|
495
|
+
winLauncherPath?: () => string;
|
|
496
|
+
/** The bound manager process the pre-stop proof tied to the approved proxy, when there was one. */
|
|
497
|
+
formerManagerPid?: number;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Post-stop manager check for Windows. Registration PRESENCE is the wrong question —
|
|
502
|
+
* schtasks /end leaves the task registered forever — so inactivity means: the task
|
|
503
|
+
* is not running, no wrapper process carrying this home's canonical paths survives,
|
|
504
|
+
* and no WinSW service is started. An unreadable probe is unknown, never inactive.
|
|
505
|
+
*/
|
|
506
|
+
export function observeWindowsGuardedManagerStopped(
|
|
507
|
+
deps: WindowsGuardedStopDeps = {},
|
|
508
|
+
live = process.platform === "win32",
|
|
509
|
+
): GuardedManagerStopped {
|
|
510
|
+
const scheduler = (deps.scheduler ?? probeWindowsSchedulerTask)();
|
|
511
|
+
const native = (deps.winsw ?? statusWinswRaw)();
|
|
512
|
+
if (scheduler.status === "unknown" || native === "unknown") return "unknown";
|
|
513
|
+
const procs = (deps.winProcs ?? (live ? windowsProcessList : () => null))();
|
|
514
|
+
if (procs === null) return "unknown";
|
|
515
|
+
const paths = [
|
|
516
|
+
(deps.winLauncherPath ?? windowsLauncherVbsPath)(),
|
|
517
|
+
(deps.winScriptPath ?? windowsServiceScriptPath)(),
|
|
518
|
+
];
|
|
519
|
+
if (wrapperProcessesAlive(procs, paths).length > 0) return "active";
|
|
520
|
+
if (native === "started") return "active";
|
|
521
|
+
if (scheduler.status === "present") {
|
|
522
|
+
const state = (deps.winTaskState ?? (live ? () => windowsScheduledTaskState() : () => "unknown"))();
|
|
523
|
+
if (state === "unknown") return "unknown";
|
|
524
|
+
if (state === "running") return "active";
|
|
525
|
+
}
|
|
526
|
+
if (deps.formerManagerPid !== undefined && unreadableWrappersOfManager(deps.formerManagerPid, procs).length > 0) {
|
|
527
|
+
return "unknown";
|
|
528
|
+
}
|
|
529
|
+
return "inactive";
|
|
530
|
+
}
|
|
@@ -8,7 +8,7 @@ import { serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
|
8
8
|
import { windowsEnvIndirectBatchPathList, windowsEnvIndirectBatchValue } from "../lib/win-paths";
|
|
9
9
|
import { cachedCurrentWindowsIdentity, resolveCurrentWindowsPrincipal, WINDOWS_PRINCIPAL_LOOKUP_TIMEOUT_MS } from "../lib/windows-user-principal";
|
|
10
10
|
import { resolveServiceListenPort, resolvedProxyEnv } from "./health";
|
|
11
|
-
import { cliEntry, serviceLogPath, currentCodexSqliteHomeAbsolute } from "./state";
|
|
11
|
+
import { cliEntry, filterTransientServicePath, serviceLogPath, currentCodexSqliteHomeAbsolute } from "./state";
|
|
12
12
|
|
|
13
13
|
function windowsBatchValue(value: string): string {
|
|
14
14
|
return value
|
|
@@ -62,7 +62,7 @@ export function buildWindowsServiceScript(
|
|
|
62
62
|
// Provenance rides along with the entry: a second durableBunRuntime() call here could
|
|
63
63
|
// resolve differently from the binary the caller actually baked.
|
|
64
64
|
const { bun, bunRuntimeSource, cli } = entry;
|
|
65
|
-
const path = process.env.PATH ?? "";
|
|
65
|
+
const path = filterTransientServicePath(process.env.PATH ?? "", ";", "win32");
|
|
66
66
|
const lines = [
|
|
67
67
|
"@echo off",
|
|
68
68
|
"setlocal EnableExtensions DisableDelayedExpansion",
|
package/src/service.ts
CHANGED
|
@@ -26,6 +26,8 @@ export type { WindowsSchedulerRegistrationStageDeps, FreshWindowsSchedulerRegist
|
|
|
26
26
|
export { windowsListenPort, winswListenPort, writeServiceDefinitionFile, definitionCarriesCredential, stageWindowsSchedulerRegistrationXml, stageElevatedSchedulerRegistration, describeElevatedRegistrationFailure, registerFreshWindowsSchedulerTask, removeNativeWindowsServiceForScheduler, assertWindowsNativeServiceAccountSupported, isWindowsSchedulerEndBenign, stopWindows, stopWindowsChecked, classifyWindowsServiceStop } from "./service/windows-ops";
|
|
27
27
|
export type { ServiceRepairVerb, RepairServiceDeps } from "./service/repair";
|
|
28
28
|
export { repairService, foreignServiceOwnerRefusal, unknownServiceOwnerRefusal } from "./service/repair";
|
|
29
|
+
export type { ServiceChildOwnershipDecision } from "./service/service-child-ownership";
|
|
30
|
+
export { serviceChildOwnershipDecision, serviceChildStayOutExitCode } from "./service/service-child-ownership";
|
|
29
31
|
export type { ServiceInstallPreparationDeps, FreshWindowsSchedulerInstallDeps, ServiceStopOutcome, ServiceUninstallOutcome } from "./service/orchestration";
|
|
30
32
|
export { proxyStillLiveAfterStop, prepareServiceInstall, installServiceSafely, installFreshWindowsSchedulerSafely, installedServiceRespawnRisk, stopServiceIfInstalledDetailed, setUninstallServiceHooksForTests, uninstallServiceDetailed, uninstallServiceIfInstalled, isServiceInstalled, isServiceViable } from "./service/orchestration";
|
|
31
33
|
export type { ServiceDiagnostic, WindowsTaskDiagnosticIdentityDeps, WindowsServiceDiagnosticInputs, LaunchdServiceDiagnosticInputs } from "./service/diagnostics";
|
package/src/types/config.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { OcxProviderConfig } from "./provider";
|
|
2
2
|
import type { CodexAccount } from "./accounts";
|
|
3
3
|
|
|
4
|
+
export interface AnthropicModelRoute {
|
|
5
|
+
name: string;
|
|
6
|
+
match: string;
|
|
7
|
+
accounts: string[];
|
|
8
|
+
fallback?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
4
11
|
/** Public inference API exposure. Responses and Chat Completions are always served. */
|
|
5
12
|
export interface OcxApiSurfacesConfig {
|
|
6
13
|
/**
|
|
@@ -1197,6 +1204,8 @@ export interface OcxConfig {
|
|
|
1197
1204
|
stickyLimit?: number;
|
|
1198
1205
|
/** Usage window for quota-based scoring. Default "five-hour" (today's behaviour). */
|
|
1199
1206
|
quotaWindow?: OcxAccountPoolQuotaWindow;
|
|
1207
|
+
/** Ordered model allowlists; inactive while the pool is disabled. Stored account IDs only. */
|
|
1208
|
+
routes?: AnthropicModelRoute[];
|
|
1200
1209
|
};
|
|
1201
1210
|
/**
|
|
1202
1211
|
* Generic OAuth multi-account PROACTIVE account preference (#2568, #695).
|
|
@@ -1569,6 +1578,23 @@ export interface OcxCodexPoolConfig {
|
|
|
1569
1578
|
* operator never meant to exclude.
|
|
1570
1579
|
*/
|
|
1571
1580
|
excludedPlans?: string[];
|
|
1581
|
+
/** Optional per-account response to fresh quota observations at or above a usage percentage. */
|
|
1582
|
+
lowQuotaProtection?: CodexLowQuotaProtectionConfig;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
/** Optional policy for pausing accounts and notifying when selected quota windows are low. */
|
|
1586
|
+
export interface CodexLowQuotaProtectionConfig {
|
|
1587
|
+
enabled: boolean;
|
|
1588
|
+
/** Inclusive usage percentage from 1 to 100. */
|
|
1589
|
+
threshold: number;
|
|
1590
|
+
actions: {
|
|
1591
|
+
pause: boolean;
|
|
1592
|
+
notify: boolean;
|
|
1593
|
+
};
|
|
1594
|
+
windows: {
|
|
1595
|
+
short: boolean;
|
|
1596
|
+
weekly: boolean;
|
|
1597
|
+
};
|
|
1572
1598
|
}
|
|
1573
1599
|
|
|
1574
1600
|
/**
|
package/src/types/provider.ts
CHANGED
|
@@ -340,6 +340,8 @@ export interface OcxProviderConfig {
|
|
|
340
340
|
* version here instead of waiting for a code change. Absent uses the adapter's current default.
|
|
341
341
|
*/
|
|
342
342
|
commandCodeVersion?: string;
|
|
343
|
+
/** Include bounded repository context in Command Code envelopes. Default omitted/off sends empty memory/taste/skills. */
|
|
344
|
+
projectContext?: "off" | "on";
|
|
343
345
|
/**
|
|
344
346
|
* Responses upstream that stores nothing server-side (DeepSeek documents "the API
|
|
345
347
|
* is stateless"). Stateful request parameters are dropped, `store` is pinned false,
|
|
@@ -595,6 +597,8 @@ export interface OcxProviderConfig {
|
|
|
595
597
|
contextWindow?: number;
|
|
596
598
|
/** Per-model fallback when context metadata is absent; otherwise caps the reported window. */
|
|
597
599
|
modelContextWindows?: Record<string, number>;
|
|
600
|
+
/** Per-model Copilot upstream tier; only the github-copilot route sends it. */
|
|
601
|
+
modelContextTiers?: Record<string, "default" | "long_context">;
|
|
598
602
|
/** Model-specific Codex catalog input modalities, e.g. ["text"] or ["text", "image"]. */
|
|
599
603
|
modelInputModalities?: Record<string, string[]>;
|
|
600
604
|
modelCapabilities?: Record<string, ModelCapabilities>;
|
|
@@ -962,6 +966,14 @@ export interface OcxProviderConfig {
|
|
|
962
966
|
* Raw reasoning is never relabeled as a summary.
|
|
963
967
|
*/
|
|
964
968
|
showThinkingSummary?: boolean;
|
|
969
|
+
/**
|
|
970
|
+
* Keep raw content-channel reasoning out of client frames for this provider. Provider-authored
|
|
971
|
+
* summaries (thinking_delta) stay visible, so an opted-in operator loses no summary; an explicit
|
|
972
|
+
* wire summary:"none" still hides both. This is a display control, not a confidentiality boundary:
|
|
973
|
+
* a Responses bridge route still carries the text to the client in the base64 `ocxr1` replay
|
|
974
|
+
* envelope; direct Chat/Messages encoders send no copy and replay from the server-side cache.
|
|
975
|
+
*/
|
|
976
|
+
hideRawReasoning?: boolean;
|
|
965
977
|
/**
|
|
966
978
|
* Opt-in same-target 429 retry policy. Codex itself never retries 429 (it retries 5xx only,
|
|
967
979
|
* openai/codex#30471), and single-key pools have no failover, so the proxy waits and replays
|
package/src/types/request.ts
CHANGED
|
@@ -316,6 +316,8 @@ export interface OcxRequestOptions {
|
|
|
316
316
|
parallelToolCalls?: boolean;
|
|
317
317
|
reasoning?: string;
|
|
318
318
|
hideThinkingSummary?: boolean;
|
|
319
|
+
/** Provider policy: suppress raw content-channel reasoning while summaries stay visible. */
|
|
320
|
+
hideRawReasoning?: boolean;
|
|
319
321
|
serviceTier?: string;
|
|
320
322
|
/** Final outbound tier action, resolved after the provider/model wire is settled. */
|
|
321
323
|
tierDecision?: TierDecision;
|