sortie-dogs 0.13.9 → 0.13.10
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/.agents/skills/sortie-dogs/SKILL.md +9 -3
- package/.agents/skills/sortie-dogs/sortie-dogs.version +1 -1
- package/README.md +44 -18
- package/dist/asset-version.d.ts +2 -2
- package/dist/asset-version.js +2 -2
- package/dist/cli/codex-mission.d.ts +1 -1
- package/dist/cli/codex-mission.js +9 -3
- package/dist/codex/app-server.d.ts +9 -0
- package/dist/codex/app-server.js +24 -3
- package/dist/codex/mission-session.d.ts +3 -0
- package/dist/codex/mission-session.js +39 -15
- package/dist/codex/mission-settlement.js +4 -3
- package/dist/codex/mission-shell.d.ts +8 -0
- package/dist/codex/mission-shell.js +22 -0
- package/dist/core/operator-mission.d.ts +1 -0
- package/dist/core/operator-mission.js +20 -5
- package/package.json +1 -1
|
@@ -24,7 +24,7 @@ Check the selected entrypoint with `<entrypoint> codex --help` before starting w
|
|
|
24
24
|
|
|
25
25
|
## Choose one route
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Use the natural-language Mission route on POSIX and Windows:
|
|
28
28
|
|
|
29
29
|
```text
|
|
30
30
|
<entrypoint> codex mission --project-root <repository-root> --prompt <exact-user-request>
|
|
@@ -32,11 +32,17 @@ On a POSIX host with `/bin/bash`, use the natural-language Mission route:
|
|
|
32
32
|
|
|
33
33
|
Forward `--model`, `--effort`, or `--permissions` only when the user selected those values. Never silently widen the native permission profile. If an existing Mission is reported, resume only its exact root with `--resume`; do not launch replacement work.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Leave `--model` unset to retain role-specific defaults: Sol 6.1 Operator/Coordinator/Reviewer and Luna-fast Worker. The Codex adapter maps the shared Luna-fast alias to native `gpt-6-luna` plus the `priority` (Fast) service tier; it does not alter OpenCode defaults. Fast consumes subscription limits faster than Standard; no metered API fallback is allowed.
|
|
36
|
+
|
|
37
|
+
On Windows the same Mission command resolves existing PowerShell 7 automatically. Add `--trusted-pwsh <absolute-pwsh.exe>` only when an exact existing executable needs to be selected; do not add a separate shell preflight or approval step.
|
|
38
|
+
|
|
39
|
+
The compatibility tool named `bash` executes PowerShell commands on Windows and POSIX bash on POSIX. Do not supply POSIX commands to the Windows shell. Shell and file commands still use the configured native permissions, or an explicitly integrated host executor. A sandbox startup failure is not a validation failure or success: preserve the failed execution and report the environment blocker; do not widen permissions or stop unrelated Codex processes.
|
|
40
|
+
|
|
41
|
+
Use the manifest-bound `codex run` route only for an explicitly selected single-task manifest workflow. It is not the full multi-role Mission route:
|
|
36
42
|
|
|
37
43
|
1. Read repository instructions and inspect only enough to identify bounded project-relative read scope, write scope, and meaningful validation commands.
|
|
38
44
|
2. Create one temporary manifest under `.sortie-dogs/codex-skill/` with `version`, `task_id`, `read`, `write`, and non-empty `validation`. Reject external or uncertain write scope instead of broadening it.
|
|
39
|
-
3.
|
|
45
|
+
3. On Windows the manifest workflow needs the exact PowerShell executable for command-envelope observation. Resolve it with `(Get-Command pwsh.exe -ErrorAction Stop).Source`, then invoke:
|
|
40
46
|
|
|
41
47
|
```text
|
|
42
48
|
<entrypoint> codex run --project-root <repository-root> --manifest <temporary-manifest> --prompt <exact-user-request> --trusted-pwsh <resolved-pwsh.exe>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.13.
|
|
1
|
+
0.13.10-codex-skill-v2
|
package/README.md
CHANGED
|
@@ -35,10 +35,10 @@ implementation, validation, review, and model routing.
|
|
|
35
35
|
Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) ·
|
|
36
36
|
[Testing](docs/testing.md) · [CLI testing](docs/cli-testing.md)
|
|
37
37
|
|
|
38
|
-
**Current release: [v0.13.
|
|
39
|
-
([release notes](docs/release-v0.13.
|
|
38
|
+
**Current release: [v0.13.10](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.13.10)**
|
|
39
|
+
([release notes](docs/release-v0.13.10.md)). The default Mission runtime retains the `v010`
|
|
40
40
|
profile, command and configuration names for compatibility; these names do not mean v0.10 is installed.
|
|
41
|
-
The current asset marker is `0.13.
|
|
41
|
+
The current asset marker is `0.13.10-codex-windows-v1`.
|
|
42
42
|
|
|
43
43
|
## SWE-bench Lite: 170/300 (56.67%)
|
|
44
44
|
|
|
@@ -48,7 +48,7 @@ The fixed **Sortie-dogs v0.12.24** harness resolved **170 of 300 SWE-bench Lite
|
|
|
48
48
|
|
|
49
49
|
The single official 300-instance report and frozen predictions are hash-bound in the report. Confirmed inference expense was **$162.99**; a separate **$34.60** of usage has unknown pricing and is held against the campaign cap, **not** counted as known expense. Leaderboard registration and maintainer acceptance are separate from this official local evaluation.
|
|
50
50
|
|
|
51
|
-
Historical scores below belong to their fixed candidates, not v0.13.
|
|
51
|
+
Historical scores below belong to their fixed candidates, not v0.13.10. SWE-bench is a separate,
|
|
52
52
|
optional measurement rather than a mandatory release gate.
|
|
53
53
|
|
|
54
54
|
> **Beta:** v0.13.x is still stabilizing. Runtime behavior,
|
|
@@ -63,11 +63,11 @@ Use Node.js 22.6 or newer and an existing Codex CLI with ChatGPT authentication.
|
|
|
63
63
|
used Node.js 22.22.1 and Codex 0.160.1. Sortie neither installs Codex nor starts a login flow, copies
|
|
64
64
|
credentials, or creates a second host configuration. Mission execution refuses non-ChatGPT auth.
|
|
65
65
|
|
|
66
|
-
The Codex Mission adapter and explicit skill are included in v0.13.
|
|
66
|
+
The Codex Mission adapter and explicit skill are included in v0.13.10. Install the release in the
|
|
67
67
|
target project and inspect the entrypoint before running a prompt:
|
|
68
68
|
|
|
69
69
|
```sh
|
|
70
|
-
npm install --save-dev sortie-dogs@0.13.
|
|
70
|
+
npm install --save-dev sortie-dogs@0.13.10
|
|
71
71
|
npx --no-install sortie-dogs codex init .
|
|
72
72
|
npx --no-install sortie-dogs codex mission --help
|
|
73
73
|
```
|
|
@@ -87,9 +87,10 @@ $sortie-dogs Implement and verify the requested change
|
|
|
87
87
|
`$sortie-dogs` is the current direct Skills syntax. Codex does not expose arbitrary custom commands
|
|
88
88
|
as exact `/sortie-dogs` slash commands; `/skills` opens the skill picker. The skill is intentionally
|
|
89
89
|
explicit-only so child Codex Mission turns cannot invoke another Sortie Mission recursively.
|
|
90
|
-
On POSIX it selects the natural-language Mission route below.
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
On POSIX and Windows it selects the natural-language, multi-role Mission route below.
|
|
91
|
+
Windows uses existing PowerShell 7; it does not route natural-language Missions through the
|
|
92
|
+
single-task `codex run` manifest workflow. That workflow remains available when explicitly selected
|
|
93
|
+
and retains its post-execution observation boundary, not a pre-execution guard.
|
|
93
94
|
|
|
94
95
|
Sortie-dogs also exports a host adapter for the official Codex app-server stdio protocol. This is
|
|
95
96
|
separate from selecting an OpenAI model through OpenCode: OpenCode model routing still uses the
|
|
@@ -123,7 +124,7 @@ server-initiated requests fail closed.
|
|
|
123
124
|
For the existing Operator → Coordinator → Worker → Reviewer Mission workflow, use:
|
|
124
125
|
|
|
125
126
|
```sh
|
|
126
|
-
npx --no-install sortie-dogs codex mission --prompt "Implement and review the requested change"
|
|
127
|
+
npx --no-install sortie-dogs codex mission --prompt "Implement and review the requested change"
|
|
127
128
|
```
|
|
128
129
|
|
|
129
130
|
The equivalent public SDK entrypoint is:
|
|
@@ -133,8 +134,9 @@ import { CodexMissionSession } from "sortie-dogs";
|
|
|
133
134
|
|
|
134
135
|
const mission = await CodexMissionSession.create({
|
|
135
136
|
projectRoot: process.cwd(),
|
|
136
|
-
|
|
137
|
-
effort: "medium",
|
|
137
|
+
// Leave model/effort unset to preserve packaged role defaults.
|
|
138
|
+
// model: "gpt-6.1-sol", effort: "medium", // Explicit override for every role.
|
|
139
|
+
// trustedPowerShellExecutable: "C:/Program Files/PowerShell/7/pwsh.exe", // Windows, optional.
|
|
138
140
|
// resumeThreadID: "saved-root-thread-id", // Continue the same Mission when needed.
|
|
139
141
|
});
|
|
140
142
|
try {
|
|
@@ -146,14 +148,26 @@ try {
|
|
|
146
148
|
}
|
|
147
149
|
```
|
|
148
150
|
|
|
149
|
-
This
|
|
151
|
+
This route uses `CodexMissionSession` and the existing Mission tools, correction grants,
|
|
150
152
|
validation evidence, and final Operator acceptance. It runs saved native Codex threads with existing
|
|
151
153
|
ChatGPT authentication; it does not import OpenCode settings or introduce a second Mission ledger.
|
|
152
|
-
Commands run through `/bin/bash`
|
|
154
|
+
Commands run through `/bin/bash` on POSIX or existing `pwsh.exe -NoProfile -NonInteractive -Command`
|
|
155
|
+
on Windows, using the native app-server's configured permissions. The compatibility tool remains
|
|
156
|
+
named `bash`, but its Windows input must be PowerShell syntax. Windows callers can pin an absolute
|
|
157
|
+
PowerShell 7 path with SDK `trustedPowerShellExecutable` or CLI `--trusted-pwsh`.
|
|
158
|
+
Packaged roles retain Sol 6.1/xhigh for Operator, Coordinator and Reviewer. The shared Luna-fast/max
|
|
159
|
+
Worker alias maps only in Codex to native `gpt-6-luna`/max plus `serviceTier: "priority"` (Fast).
|
|
160
|
+
OpenCode routing is unchanged. Fast uses subscription limits faster than Standard; no metered API
|
|
161
|
+
fallback is introduced. CLI progress exposes each role's native model, effort and separate service tier.
|
|
162
|
+
Sortie does not
|
|
153
163
|
replace them with a fixed repository-only or network-disabled policy, change host configuration, or select
|
|
154
164
|
full access. Thread turns retain native thread permissions; standalone `command/exec` uses the server's
|
|
155
165
|
configured policy, not a thread's temporary grants. A parent application's in-memory approval is not
|
|
156
166
|
automatically transferred to a separately launched app-server.
|
|
167
|
+
Windows recovery can reclaim a killed adapter only when its recorded Windows PID is absent.
|
|
168
|
+
A live/reused PID, access denial, legacy owner without platform identity, or unresolved native
|
|
169
|
+
execution remains unknown; no command is replayed. Sandbox startup errors must be resolved at the
|
|
170
|
+
native host, not by silently widening Sortie's permissions or stopping unrelated Codex processes.
|
|
157
171
|
SDK hosts can forward native command/file approval requests through `approval` and permission-subset
|
|
158
172
|
requests through `permissionsApproval`. The native host remains responsible for deciding and enforcing
|
|
159
173
|
the grant. The CLI reports approval requests but has no interactive approval bridge; without a connected
|
|
@@ -289,7 +303,19 @@ existing local bridge loads enforcement and model routing. OpenCode can reload w
|
|
|
289
303
|
but replacing an installed dependency may require a full restart. A new chat session alone does not
|
|
290
304
|
prove the newly installed plugin is loaded.
|
|
291
305
|
|
|
292
|
-
## v0.13.
|
|
306
|
+
## v0.13.10 runtime updates
|
|
307
|
+
|
|
308
|
+
PR #169 connects natural-language Windows Codex Missions to the same multi-role lifecycle using
|
|
309
|
+
existing PowerShell 7, preserves command failure exits and native resume settings, and maps the
|
|
310
|
+
Luna-fast Worker alias to native Luna/max with the separate priority tier. Directory reads list
|
|
311
|
+
direct entries instead of requiring filename guesses. OpenCode model settings and native permission
|
|
312
|
+
profiles remain unchanged. PR #168 provides one host-aware Anko runner, reuses unchanged setup,
|
|
313
|
+
removes mandatory paid diagnostic probes, and preserves prior attempts, unknown usage and service
|
|
314
|
+
evidence. Its recorded v0.13.9 Anko completion and PR #169's Windows Codex host-executor completion
|
|
315
|
+
are historical fixed-candidate observations, not new v0.13.10 benchmark or native sandbox results.
|
|
316
|
+
See [release notes](docs/release-v0.13.10.md).
|
|
317
|
+
|
|
318
|
+
## v0.13.9 runtime updates (retained)
|
|
293
319
|
|
|
294
320
|
PRs #163–#166 connect native Codex sessions to the existing Mission lifecycle, add the explicit
|
|
295
321
|
`$sortie-dogs` skill and `codex init`, restore operation Reviewer correction within existing write
|
|
@@ -654,7 +680,7 @@ register stable and `v010` from the same package installation path in one host.
|
|
|
654
680
|
Project-local installation is recommended. To expose the current Mission assets globally:
|
|
655
681
|
|
|
656
682
|
```sh
|
|
657
|
-
npm install --global sortie-dogs@0.13.
|
|
683
|
+
npm install --global sortie-dogs@0.13.10
|
|
658
684
|
sortie-dogs init --global --profile v010
|
|
659
685
|
```
|
|
660
686
|
|
|
@@ -666,7 +692,7 @@ can resolve a **separate dependency** under that config root. Updating npm-globa
|
|
|
666
692
|
it. For that layout, also install the same release at the actual config root, then rerun global init:
|
|
667
693
|
|
|
668
694
|
```sh
|
|
669
|
-
npm install --prefix "$HOME/.config/opencode" sortie-dogs@0.13.
|
|
695
|
+
npm install --prefix "$HOME/.config/opencode" sortie-dogs@0.13.10
|
|
670
696
|
sortie-dogs init --global --profile v010
|
|
671
697
|
```
|
|
672
698
|
|
|
@@ -689,7 +715,7 @@ version, preserves user configuration, and stops safely on unknown ownership or
|
|
|
689
715
|
conflicting files.
|
|
690
716
|
|
|
691
717
|
Align any exact version pin or separate bridge dependency with the intended release too. An installed
|
|
692
|
-
marker of `0.13.
|
|
718
|
+
marker of `0.13.10-codex-windows-v1` identifies the assets; it does not prove an already-running
|
|
693
719
|
OpenCode process has reloaded the plugin.
|
|
694
720
|
|
|
695
721
|
There is no supported uninstall command. Remove the npm dependency separately,
|
package/dist/asset-version.d.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
5
|
export declare const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
|
|
6
|
-
export declare const V010_RUNTIME_ASSET_VERSION = "0.13.
|
|
7
|
-
export declare const CODEX_SKILL_ASSET_VERSION = "0.13.
|
|
6
|
+
export declare const V010_RUNTIME_ASSET_VERSION = "0.13.10-codex-windows-v1";
|
|
7
|
+
export declare const CODEX_SKILL_ASSET_VERSION = "0.13.10-codex-skill-v2";
|
|
8
8
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION | typeof CODEX_SKILL_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
5
|
export const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
|
|
6
|
-
export const V010_RUNTIME_ASSET_VERSION = "0.13.
|
|
7
|
-
export const CODEX_SKILL_ASSET_VERSION = "0.13.
|
|
6
|
+
export const V010_RUNTIME_ASSET_VERSION = "0.13.10-codex-windows-v1";
|
|
7
|
+
export const CODEX_SKILL_ASSET_VERSION = "0.13.10-codex-skill-v2";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CodexTurnEvent } from "../codex/app-server.js";
|
|
2
|
-
export declare const CODEX_MISSION_USAGE = "Usage: sortie-dogs codex mission --prompt <text>\n [--resume <root-thread-id>] [--project-root <path>] [--executable <codex>] [--model <model>] [--effort <effort>] [--permissions <native-profile>]\n\nRuns the existing Mission Operator, Coordinator, Worker and Reviewer through Codex.\n--model and --effort explicitly override all roles; otherwise packaged role defaults apply.\n--permissions explicitly selects an existing native profile; omission retains native defaults.\nUses existing ChatGPT authentication and native Codex permissions.";
|
|
2
|
+
export declare const CODEX_MISSION_USAGE = "Usage: sortie-dogs codex mission --prompt <text>\n [--resume <root-thread-id>] [--project-root <path>] [--executable <codex>] [--model <model>] [--effort <effort>] [--permissions <native-profile>] [--trusted-pwsh <absolute-pwsh.exe>]\n\nRuns the existing Mission Operator, Coordinator, Worker and Reviewer through Codex.\n--model and --effort explicitly override all roles; otherwise packaged role defaults apply.\n--permissions explicitly selects an existing native profile; omission retains native defaults.\nWindows uses existing PowerShell 7; --trusted-pwsh pins its absolute executable path.\nThe packaged Luna-fast worker maps to native gpt-6-luna with the priority (Fast) service tier.\nUses existing ChatGPT authentication and native Codex permissions.";
|
|
3
3
|
export declare function runCodexMissionCommand(argv: readonly string[]): Promise<number>;
|
|
4
4
|
/** Bounded user-facing progress; do not dump control packets or hidden reasoning. */
|
|
5
5
|
export declare function codexMissionProgress(event: CodexTurnEvent & {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { CodexMissionSession } from "../codex/mission-session.js";
|
|
2
2
|
export const CODEX_MISSION_USAGE = `Usage: sortie-dogs codex mission --prompt <text>
|
|
3
|
-
[--resume <root-thread-id>] [--project-root <path>] [--executable <codex>] [--model <model>] [--effort <effort>] [--permissions <native-profile>]
|
|
3
|
+
[--resume <root-thread-id>] [--project-root <path>] [--executable <codex>] [--model <model>] [--effort <effort>] [--permissions <native-profile>] [--trusted-pwsh <absolute-pwsh.exe>]
|
|
4
4
|
|
|
5
5
|
Runs the existing Mission Operator, Coordinator, Worker and Reviewer through Codex.
|
|
6
6
|
--model and --effort explicitly override all roles; otherwise packaged role defaults apply.
|
|
7
7
|
--permissions explicitly selects an existing native profile; omission retains native defaults.
|
|
8
|
+
Windows uses existing PowerShell 7; --trusted-pwsh pins its absolute executable path.
|
|
9
|
+
The packaged Luna-fast worker maps to native gpt-6-luna with the priority (Fast) service tier.
|
|
8
10
|
Uses existing ChatGPT authentication and native Codex permissions.`;
|
|
9
11
|
export async function runCodexMissionCommand(argv) {
|
|
10
12
|
if (argv.length === 1 && argv[0] === "--help") {
|
|
@@ -12,7 +14,7 @@ export async function runCodexMissionCommand(argv) {
|
|
|
12
14
|
return 0;
|
|
13
15
|
}
|
|
14
16
|
const values = new Map();
|
|
15
|
-
const allowed = new Set(["--prompt", "--resume", "--project-root", "--executable", "--model", "--effort", "--permissions"]);
|
|
17
|
+
const allowed = new Set(["--prompt", "--resume", "--project-root", "--executable", "--model", "--effort", "--permissions", "--trusted-pwsh"]);
|
|
16
18
|
for (let index = 0; index < argv.length; index += 2) {
|
|
17
19
|
const key = argv[index], value = argv[index + 1];
|
|
18
20
|
if (!key || !allowed.has(key) || !value || value.startsWith("--") || values.has(key)) {
|
|
@@ -34,7 +36,8 @@ export async function runCodexMissionCommand(argv) {
|
|
|
34
36
|
process.on("SIGINT", onInt);
|
|
35
37
|
try {
|
|
36
38
|
adapter = await CodexMissionSession.create({ projectRoot: values.get("--project-root") ?? process.cwd(),
|
|
37
|
-
resumeThreadID: values.get("--resume"), permissions: values.get("--permissions"), executable: values.get("--executable"), model: values.get("--model"), effort: values.get("--effort"),
|
|
39
|
+
resumeThreadID: values.get("--resume"), permissions: values.get("--permissions"), executable: values.get("--executable"), model: values.get("--model"), effort: values.get("--effort"),
|
|
40
|
+
trustedPowerShellExecutable: values.get("--trusted-pwsh"), onEvent: event => {
|
|
38
41
|
const progress = codexMissionProgress(event);
|
|
39
42
|
if (progress)
|
|
40
43
|
process.stderr.write(`${JSON.stringify(progress)}\n`);
|
|
@@ -63,6 +66,9 @@ export async function runCodexMissionCommand(argv) {
|
|
|
63
66
|
}
|
|
64
67
|
/** Bounded user-facing progress; do not dump control packets or hidden reasoning. */
|
|
65
68
|
export function codexMissionProgress(event) {
|
|
69
|
+
if (event.method === "sortie/modelRoute")
|
|
70
|
+
return { thread_id: event.threadId, phase: "model-route", agent: event.params.agent, model: event.params.model,
|
|
71
|
+
effort: event.params.effort, service_tier: event.params.serviceTier };
|
|
66
72
|
if (event.method === "sortie/permissions")
|
|
67
73
|
return { thread_id: event.threadId, phase: "permissions", requested_profile: event.params.requestedProfile,
|
|
68
74
|
effective_profile: event.params.profile, sandbox: event.params.sandbox, network_access: event.params.networkAccess,
|
|
@@ -34,6 +34,8 @@ export interface CodexTurnOptions {
|
|
|
34
34
|
readonly approvalPolicy?: "untrusted" | "on-failure" | "on-request" | "never" | "unlessTrusted" | "onRequest";
|
|
35
35
|
readonly model?: string;
|
|
36
36
|
readonly effort?: string;
|
|
37
|
+
/** Native service tier, separate from the model slug (for example priority/Fast). */
|
|
38
|
+
readonly serviceTier?: string;
|
|
37
39
|
readonly outputSchema?: JsonObject;
|
|
38
40
|
readonly onEvent?: (event: CodexTurnEvent) => void | Promise<void>;
|
|
39
41
|
}
|
|
@@ -90,6 +92,8 @@ export declare class CodexAppServerHost {
|
|
|
90
92
|
private initialized;
|
|
91
93
|
private readonly permissions;
|
|
92
94
|
private readonly models;
|
|
95
|
+
private readonly serviceTiers;
|
|
96
|
+
private readonly efforts;
|
|
93
97
|
private closed;
|
|
94
98
|
private closing?;
|
|
95
99
|
private fault?;
|
|
@@ -104,6 +108,7 @@ export declare class CodexAppServerHost {
|
|
|
104
108
|
startThread(params: {
|
|
105
109
|
cwd: string;
|
|
106
110
|
model?: string;
|
|
111
|
+
serviceTier?: string;
|
|
107
112
|
ephemeral?: boolean;
|
|
108
113
|
permissions?: string;
|
|
109
114
|
developerInstructions?: string;
|
|
@@ -111,6 +116,10 @@ export declare class CodexAppServerHost {
|
|
|
111
116
|
config?: JsonObject;
|
|
112
117
|
}): Promise<string>;
|
|
113
118
|
threadModel(threadID: string): string | undefined;
|
|
119
|
+
/** Null means the native default was observed; undefined means no observation. */
|
|
120
|
+
threadServiceTier(threadID: string): string | null | undefined;
|
|
121
|
+
threadEffort(threadID: string): string | null | undefined;
|
|
122
|
+
private captureModelSettings;
|
|
114
123
|
threadPermissions(threadID: string): JsonObject | undefined;
|
|
115
124
|
private capturePermissions;
|
|
116
125
|
/** Read only the authentication mode and plan class; never expose tokens or account identifiers. */
|
package/dist/codex/app-server.js
CHANGED
|
@@ -76,6 +76,8 @@ export class CodexAppServerHost {
|
|
|
76
76
|
initialized = false;
|
|
77
77
|
permissions = new Map();
|
|
78
78
|
models = new Map();
|
|
79
|
+
serviceTiers = new Map();
|
|
80
|
+
efforts = new Map();
|
|
79
81
|
closed = false;
|
|
80
82
|
closing;
|
|
81
83
|
fault;
|
|
@@ -93,7 +95,7 @@ export class CodexAppServerHost {
|
|
|
93
95
|
async initialize() {
|
|
94
96
|
if (this.initialized)
|
|
95
97
|
return;
|
|
96
|
-
await this.request("initialize", { clientInfo: { name: "sortie_dogs", title: "Sortie-dogs", version: this.options.clientVersion ?? "0.13.
|
|
98
|
+
await this.request("initialize", { clientInfo: { name: "sortie_dogs", title: "Sortie-dogs", version: this.options.clientVersion ?? "0.13.10" },
|
|
97
99
|
...(this.options.dynamicTool || this.options.experimentalApi ? { capabilities: { experimentalApi: true } } : {}) });
|
|
98
100
|
this.transport.send({ method: "initialized", params: {} });
|
|
99
101
|
this.initialized = true;
|
|
@@ -102,6 +104,7 @@ export class CodexAppServerHost {
|
|
|
102
104
|
await this.initialize();
|
|
103
105
|
const result = await this.request("thread/start", { cwd: params.cwd, ...(params.model ? { model: params.model, allowProviderModelFallback: false } : {}),
|
|
104
106
|
...(params.ephemeral === undefined ? {} : { ephemeral: params.ephemeral }),
|
|
107
|
+
...(params.serviceTier ? { serviceTier: params.serviceTier } : {}),
|
|
105
108
|
...(params.permissions ? { permissions: params.permissions } : {}),
|
|
106
109
|
...(params.developerInstructions ? { developerInstructions: params.developerInstructions } : {}),
|
|
107
110
|
...(params.dynamicTools ? { dynamicTools: params.dynamicTools } : {}),
|
|
@@ -109,12 +112,28 @@ export class CodexAppServerHost {
|
|
|
109
112
|
const id = object(result) && object(result.thread) ? text(result.thread.id) : undefined;
|
|
110
113
|
if (!id)
|
|
111
114
|
throw new CodexHostError("protocol-invalid-response", "thread/start returned no thread id.");
|
|
112
|
-
|
|
113
|
-
this.models.set(id, result.model);
|
|
115
|
+
this.captureModelSettings(id, result);
|
|
114
116
|
this.capturePermissions(id, result);
|
|
115
117
|
return id;
|
|
116
118
|
}
|
|
117
119
|
threadModel(threadID) { return this.models.get(threadID); }
|
|
120
|
+
/** Null means the native default was observed; undefined means no observation. */
|
|
121
|
+
threadServiceTier(threadID) { return this.serviceTiers.get(threadID); }
|
|
122
|
+
threadEffort(threadID) { return this.efforts.get(threadID); }
|
|
123
|
+
captureModelSettings(threadID, result) {
|
|
124
|
+
if (!object(result))
|
|
125
|
+
return;
|
|
126
|
+
if (typeof result.model === "string")
|
|
127
|
+
this.models.set(threadID, result.model);
|
|
128
|
+
if (typeof result.serviceTier === "string")
|
|
129
|
+
this.serviceTiers.set(threadID, result.serviceTier);
|
|
130
|
+
else if (result.serviceTier === null)
|
|
131
|
+
this.serviceTiers.set(threadID, null);
|
|
132
|
+
if (typeof result.reasoningEffort === "string")
|
|
133
|
+
this.efforts.set(threadID, result.reasoningEffort);
|
|
134
|
+
else if (result.reasoningEffort === null)
|
|
135
|
+
this.efforts.set(threadID, null);
|
|
136
|
+
}
|
|
118
137
|
threadPermissions(threadID) { return this.permissions.get(threadID); }
|
|
119
138
|
capturePermissions(threadID, result) {
|
|
120
139
|
if (!object(result))
|
|
@@ -149,6 +168,7 @@ export class CodexAppServerHost {
|
|
|
149
168
|
const id = object(result) && object(result.thread) ? text(result.thread.id) : undefined;
|
|
150
169
|
if (id !== threadID)
|
|
151
170
|
throw new CodexHostError("protocol-invalid-response", "thread/resume returned a different thread id.");
|
|
171
|
+
this.captureModelSettings(id, result);
|
|
152
172
|
this.capturePermissions(threadID, result);
|
|
153
173
|
}
|
|
154
174
|
async runTurn(threadID, prompt, options) {
|
|
@@ -160,6 +180,7 @@ export class CodexAppServerHost {
|
|
|
160
180
|
...(options.sandboxPolicy ? { sandboxPolicy: options.sandboxPolicy } : {}),
|
|
161
181
|
...(options.approvalPolicy ? { approvalPolicy: options.approvalPolicy } : {}),
|
|
162
182
|
...(options.model ? { model: options.model } : {}), ...(options.effort ? { effort: options.effort } : {}),
|
|
183
|
+
...(options.serviceTier ? { serviceTier: options.serviceTier } : {}),
|
|
163
184
|
...(options.outputSchema ? { outputSchema: options.outputSchema } : {}) });
|
|
164
185
|
if (this.closed)
|
|
165
186
|
throw new CodexHostError("server-closed", "Codex host was closed.");
|
|
@@ -34,6 +34,8 @@ export interface CodexMissionSessionOptions {
|
|
|
34
34
|
}>>;
|
|
35
35
|
readonly effort?: string;
|
|
36
36
|
readonly executable?: string;
|
|
37
|
+
/** Windows only: existing absolute PowerShell 7 executable; otherwise resolve pwsh.exe on PATH. */
|
|
38
|
+
readonly trustedPowerShellExecutable?: string;
|
|
37
39
|
readonly transportFactory?: () => CodexAppServerTransport;
|
|
38
40
|
/** Delegate exact post-hook commands to the parent host; omission retains native command/exec. */
|
|
39
41
|
readonly executeCommand?: CodexMissionCommandExecutor;
|
|
@@ -46,6 +48,7 @@ export interface CodexMissionSessionOptions {
|
|
|
46
48
|
/** Native Codex transport for the existing Mission tools, roles and acceptance state. */
|
|
47
49
|
export declare class CodexMissionSession {
|
|
48
50
|
private readonly options;
|
|
51
|
+
private readonly shell;
|
|
49
52
|
private hooks;
|
|
50
53
|
private readonly sessions;
|
|
51
54
|
private closed;
|
|
@@ -8,10 +8,12 @@ import { V010_RUNTIME_PROFILE as profile } from "../core/runtime-profile.js";
|
|
|
8
8
|
import { runtimeAssets } from "../runtime-assets-v010.js";
|
|
9
9
|
import { OperatorRuntime } from "../core/operator-runtime.js";
|
|
10
10
|
import { OperatorMissionRuntime, codexProcessOwner, codexOwnerGone } from "../core/operator-mission.js";
|
|
11
|
+
import { resolveCodexMissionShell } from "./mission-shell.js";
|
|
11
12
|
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12
13
|
/** Native Codex transport for the existing Mission tools, roles and acceptance state. */
|
|
13
14
|
export class CodexMissionSession {
|
|
14
15
|
options;
|
|
16
|
+
shell;
|
|
15
17
|
hooks;
|
|
16
18
|
sessions = new Map();
|
|
17
19
|
closed = false;
|
|
@@ -23,12 +25,13 @@ export class CodexMissionSession {
|
|
|
23
25
|
ownedRoots = new Set();
|
|
24
26
|
toolQueues = new Map();
|
|
25
27
|
directory;
|
|
26
|
-
constructor(options) {
|
|
28
|
+
constructor(options, shell) {
|
|
27
29
|
this.options = options;
|
|
30
|
+
this.shell = shell;
|
|
28
31
|
this.directory = resolve(options.projectRoot);
|
|
29
32
|
}
|
|
30
33
|
static async create(options) {
|
|
31
|
-
const adapter = new CodexMissionSession(options);
|
|
34
|
+
const adapter = new CodexMissionSession(options, await resolveCodexMissionShell(options.trustedPowerShellExecutable));
|
|
32
35
|
adapter.owner = await codexProcessOwner();
|
|
33
36
|
const id = (request) => request.path.id;
|
|
34
37
|
const info = (session) => ({ id: session.id, agent: session.agent, parentID: session.parentID,
|
|
@@ -284,7 +287,13 @@ export class CodexMissionSession {
|
|
|
284
287
|
throw new Error(`Codex Mission requires an OpenAI model for ${agent}. Set model or roleModels explicitly.`);
|
|
285
288
|
const effort = this.options.roleModels?.[agent]?.effort || this.options.effort || selection?.variant || embeddedEffort ||
|
|
286
289
|
stored?.variant || /^variant: (.+)$/m.exec(header)?.[1];
|
|
287
|
-
|
|
290
|
+
// The shared OpenCode alias is not a native Codex model. Keep its speed
|
|
291
|
+
// selection separate, including after shared Task routing and cold resume.
|
|
292
|
+
const fast = model === "gpt-6-luna-fast";
|
|
293
|
+
// A model/effort override does not silently change the independent native speed setting.
|
|
294
|
+
const serviceTier = fast ? "priority" : stored?.serviceTier;
|
|
295
|
+
return { providerID: "openai", modelID: fast ? "gpt-6-luna" : model,
|
|
296
|
+
...(effort ? { variant: effort } : {}), ...(serviceTier ? { serviceTier } : {}) };
|
|
288
297
|
}
|
|
289
298
|
async reportPermissions(host, threadId) {
|
|
290
299
|
await this.options.onEvent?.({ method: "sortie/permissions", threadId,
|
|
@@ -298,25 +307,29 @@ export class CodexMissionSession {
|
|
|
298
307
|
if (!content)
|
|
299
308
|
throw new Error(`Codex Mission role not supported: ${agent}`);
|
|
300
309
|
const route = this.modelRoute(agent, undefined, selection);
|
|
301
|
-
const model = route.modelID
|
|
310
|
+
const model = route.modelID;
|
|
302
311
|
const host = this.createHost();
|
|
303
312
|
try {
|
|
304
313
|
const auth = await host.authenticationState();
|
|
305
314
|
if (auth.type !== "chatgpt")
|
|
306
315
|
throw new Error("Codex Mission requires existing ChatGPT authentication.");
|
|
307
|
-
const thread = await host.startThread({ cwd: this.directory, model, ephemeral: false, permissions: this.options.permissions,
|
|
316
|
+
const thread = await host.startThread({ cwd: this.directory, model, serviceTier: route.serviceTier, ephemeral: false, permissions: this.options.permissions,
|
|
308
317
|
developerInstructions: content.replace(/^---\n[\s\S]*?\n---\n/, "") +
|
|
309
318
|
"\nHost transport: use the supplied bash/read/write/task functions and sortie tools. They invoke the existing Mission hooks. " +
|
|
310
319
|
"Use task to run or resume the returned native Task with its exact prompt and subagent_type. Tool failures are feedback for the same Mission. " +
|
|
311
320
|
"Native shell and file operations outside these functions do not provide Mission validation evidence. " +
|
|
321
|
+
(this.options.executeCommand ? "Native apply_patch still uses the native sandbox, not the configured host executor; use the supplied write function for Mission edits. " : "") +
|
|
322
|
+
`The bash function runs ${this.shell.description}; supply commands in that shell's syntax. ` +
|
|
312
323
|
"Declare formal validation as exact executable shell commands from the project root, without prose annotations such as (workdir: ...). " +
|
|
313
324
|
"Use a root-relative command or an explicit shell cd for a subdirectory formal check. bash workdir changes that invocation only; " +
|
|
314
325
|
"a command run in another directory does not validate a root-directory entry.",
|
|
315
326
|
dynamicTools: this.tools(content), config: { "features.shell_tool": false, "features.unified_exec": false } });
|
|
316
327
|
if (this.closed)
|
|
317
328
|
throw new Error("Codex Mission adapter is closed.");
|
|
329
|
+
const serviceTier = host.threadServiceTier(thread) ?? route.serviceTier;
|
|
318
330
|
const session = { id: thread, agent, parentID, outcome: "idle", host, history: [], usageBaseline: emptyCodexUsage(), createdAt: Date.now(),
|
|
319
|
-
model: {
|
|
331
|
+
model: { ...route, modelID: host.threadModel(thread) ?? model,
|
|
332
|
+
...(serviceTier ? { serviceTier } : {}) } };
|
|
320
333
|
this.sessions.set(thread, session);
|
|
321
334
|
await this.reportPermissions(host, thread);
|
|
322
335
|
return session;
|
|
@@ -366,9 +379,16 @@ export class CodexMissionSession {
|
|
|
366
379
|
}
|
|
367
380
|
await host.resumeThread(id, this.options.permissions);
|
|
368
381
|
await this.reportPermissions(host, id);
|
|
382
|
+
const nativeModel = host.threadModel(id) ?? (typeof thread.model === "string" ? thread.model : undefined);
|
|
383
|
+
const observedTier = host.threadServiceTier(id), observedEffort = host.threadEffort(id);
|
|
384
|
+
// An observed native default is authoritative, not a missing value to fill
|
|
385
|
+
// from an older thread/read snapshot. Older hosts may omit these fields.
|
|
386
|
+
const nativeTier = observedTier === undefined ? (typeof thread.serviceTier === "string" ? thread.serviceTier : undefined) : observedTier;
|
|
387
|
+
const nativeEffort = observedEffort === undefined ? (typeof thread.reasoningEffort === "string" ? thread.reasoningEffort : undefined) : observedEffort;
|
|
369
388
|
const session = { id, agent, parentID, outcome: turns.at(-1)?.status === "completed" ? "succeeded" : "interrupted", host, history: [], createdAt: codexNativeTime(thread.createdAt),
|
|
370
|
-
model: this.modelRoute(agent, { providerID: String(thread.modelProvider), modelID:
|
|
371
|
-
...(
|
|
389
|
+
model: this.modelRoute(agent, nativeModel ? { providerID: String(thread.modelProvider), modelID: nativeModel,
|
|
390
|
+
...(nativeTier ? { serviceTier: nativeTier } : {}),
|
|
391
|
+
...(nativeEffort ? { variant: nativeEffort } : {}) } : undefined) };
|
|
372
392
|
const lastUsage = this.observations.find(item => item.threadID === id && item.turnID === turns.at(-1)?.id && item.terminal);
|
|
373
393
|
session.usageBaseline = codexUsageTotal(lastUsage?.total);
|
|
374
394
|
if (lastUsage?.total)
|
|
@@ -421,9 +441,9 @@ export class CodexMissionSession {
|
|
|
421
441
|
description: tool.description, inputSchema: toolSchema(tool.args) }));
|
|
422
442
|
const define = (name, description, properties, required = Object.keys(properties)) => ({ type: "function", name, description, inputSchema: { type: "object", properties, required, additionalProperties: false } });
|
|
423
443
|
const string = { type: "string" };
|
|
424
|
-
tools.push(define("bash",
|
|
425
|
-
tools.push(define("read", "Read a UTF-8 file through the
|
|
426
|
-
tools.push(define("write", "Write a UTF-8 file through the
|
|
444
|
+
tools.push(define("bash", `Run a foreground ${this.shell.description} command through the configured executor. Preserve formal validation commands exactly.`, { command: string, workdir: string, timeout: { type: "integer", minimum: 1, maximum: 1200000 } }, ["command"]));
|
|
445
|
+
tools.push(define("read", "Read a UTF-8 file or list a directory's direct entries through the configured executor. Read directories to discover filenames instead of guessing them.", { filePath: string }));
|
|
446
|
+
tools.push(define("write", "Write a UTF-8 file through the configured executor and existing Mission hooks.", { filePath: string, content: string }));
|
|
427
447
|
tools.push(define("task", "Run a returned Mission Task, or resume its existing task_id. Preserve the host's prompt and subagent_type.", { description: string, prompt: string, subagent_type: string, task_id: string, model: string, variant: string }, ["description", "prompt", "subagent_type"]));
|
|
428
448
|
return tools;
|
|
429
449
|
}
|
|
@@ -434,10 +454,10 @@ export class CodexMissionSession {
|
|
|
434
454
|
const parts = [{ type: "text", text: prompt }];
|
|
435
455
|
await this.hooks["chat.message"]?.({ sessionID: session.id, messageID, agent: session.agent }, { message, parts });
|
|
436
456
|
// Shared Task routing may select a model; explicit adapter overrides still govern every native turn.
|
|
437
|
-
if (this.options.model || this.options.effort || this.options.roleModels?.[session.agent])
|
|
438
|
-
message.model = this.modelRoute(session.agent, message.model);
|
|
439
457
|
if (message.model.providerID !== "openai")
|
|
440
458
|
throw new Error("Codex Mission cannot use a non-OpenAI model route.");
|
|
459
|
+
message.model = this.modelRoute(session.agent, { ...message.model,
|
|
460
|
+
serviceTier: message.model.serviceTier ?? session.model.serviceTier });
|
|
441
461
|
session.model = message.model;
|
|
442
462
|
session.history.push({ info: { ...message, role: "user", sessionID: session.id }, parts });
|
|
443
463
|
session.outcome = "running";
|
|
@@ -446,9 +466,13 @@ export class CodexMissionSession {
|
|
|
446
466
|
const accounting = { info: { ...codexUsageInfo(observation), time: { created: startedAt } }, parts: [] };
|
|
447
467
|
session.history.push(accounting);
|
|
448
468
|
try {
|
|
469
|
+
await this.options.onEvent?.({ method: "sortie/modelRoute", threadId: session.id,
|
|
470
|
+
params: { agent: session.agent, model: session.model.modelID, effort: session.model.variant ?? null,
|
|
471
|
+
serviceTier: session.model.serviceTier ?? null } });
|
|
449
472
|
await onDispatch?.(messageID);
|
|
450
473
|
const result = await session.host.runTurn(session.id, parts.map(part => part.text).join("\n"), { cwd: this.directory, clientUserMessageId: messageID,
|
|
451
474
|
model: message.model.modelID === "unknown" ? undefined : message.model.modelID, effort: message.model.variant ?? this.options.effort,
|
|
475
|
+
serviceTier: message.model.serviceTier,
|
|
452
476
|
onEvent: event => {
|
|
453
477
|
if (event.method === "thread/tokenUsage/updated" && object(event.params.tokenUsage)) {
|
|
454
478
|
observation.turnID = typeof event.params.turnId === "string" ? event.params.turnId : observation.turnID;
|
|
@@ -523,8 +547,8 @@ export class CodexMissionSession {
|
|
|
523
547
|
metadata = { sessionId: child.id, status: result.status };
|
|
524
548
|
}
|
|
525
549
|
else {
|
|
526
|
-
const command = call.tool === "bash" ?
|
|
527
|
-
: call.tool === "read" ? [process.execPath, "-e", "
|
|
550
|
+
const command = call.tool === "bash" ? this.shell.command(String(args.command))
|
|
551
|
+
: call.tool === "read" ? [process.execPath, "-e", "const fs=require('node:fs'),p=process.argv[1];process.stdout.write(fs.statSync(p).isDirectory()?fs.readdirSync(p,{withFileTypes:true}).map(e=>e.name+(e.isDirectory()?'/':'')).sort().join('\\n')+'\\n':fs.readFileSync(p,'utf8'))", resolve(this.directory, String(args.filePath))]
|
|
528
552
|
: [process.execPath, "-e", "require('node:fs').writeFileSync(process.argv[1],process.argv[2])", resolve(this.directory, String(args.filePath)), String(args.content)];
|
|
529
553
|
const executor = this.options.executeCommand ? "host" : "native";
|
|
530
554
|
await this.options.onEvent?.({ method: "sortie/commandExecution", threadId: session.id,
|
|
@@ -12,10 +12,11 @@ const commandText = (item) => {
|
|
|
12
12
|
const powershellEnvelopePayload = (raw, trustedExecutable) => {
|
|
13
13
|
if (!trustedExecutable)
|
|
14
14
|
return undefined;
|
|
15
|
-
// Windows Codex 0.160 reports a quoted absolute pwsh.exe plus one single-quoted
|
|
15
|
+
// Windows Codex 0.160/0.162 reports a quoted absolute pwsh.exe plus one single-quoted
|
|
16
16
|
// -Command payload. It may protocol-escape the boundary quotes/backslashes.
|
|
17
|
-
//
|
|
18
|
-
|
|
17
|
+
// Only the observed optional -NoProfile flag is admitted. Other options,
|
|
18
|
+
// nested quotes and trailing tokens remain rejected.
|
|
19
|
+
const match = /^(?:"|\\")([A-Za-z]:[\\/](?:[^"'\r\n]|\\\\)+[\\/]pwsh\.exe)(?:"|\\") (?:-NoProfile )?-Command '([^'`\r\n]*)'$/iu.exec(raw);
|
|
19
20
|
if (!match)
|
|
20
21
|
return undefined;
|
|
21
22
|
const identity = (value) => value.replaceAll("\\\\", "\\").replaceAll("/", "\\").toLowerCase();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface CodexMissionShell {
|
|
2
|
+
readonly description: string;
|
|
3
|
+
command(source: string): readonly string[];
|
|
4
|
+
}
|
|
5
|
+
/** Keep the source literal; only the host-owned exit trailer is appended. */
|
|
6
|
+
export declare function codexPowerShellCommand(executable: string, source: string): readonly string[];
|
|
7
|
+
/** Resolve the existing host shell, without installation or permission changes. */
|
|
8
|
+
export declare function resolveCodexMissionShell(trustedPowerShellExecutable?: string): Promise<CodexMissionShell>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { basename, isAbsolute } from "node:path";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import { resolveValidationExecutable } from "../core/worktree-commit-artifact.js";
|
|
4
|
+
/** Keep the source literal; only the host-owned exit trailer is appended. */
|
|
5
|
+
export function codexPowerShellCommand(executable, source) {
|
|
6
|
+
return [executable, "-NoProfile", "-NonInteractive", "-Command", source +
|
|
7
|
+
"\nif (-not $?) { if ($LASTEXITCODE) { exit $LASTEXITCODE }; exit 1 }"];
|
|
8
|
+
}
|
|
9
|
+
/** Resolve the existing host shell, without installation or permission changes. */
|
|
10
|
+
export async function resolveCodexMissionShell(trustedPowerShellExecutable) {
|
|
11
|
+
if (process.platform !== "win32") {
|
|
12
|
+
if (trustedPowerShellExecutable)
|
|
13
|
+
throw new Error("--trusted-pwsh is only supported on Windows.");
|
|
14
|
+
return { description: "POSIX bash", command: source => ["/bin/bash", "-c", source] };
|
|
15
|
+
}
|
|
16
|
+
if (trustedPowerShellExecutable && (!isAbsolute(trustedPowerShellExecutable) || basename(trustedPowerShellExecutable).toLowerCase() !== "pwsh.exe"))
|
|
17
|
+
throw new Error("Codex Mission requires an absolute existing pwsh.exe path.");
|
|
18
|
+
const executable = await resolveValidationExecutable(trustedPowerShellExecutable ?? "pwsh.exe");
|
|
19
|
+
if (!executable || !(await stat(executable)).isFile())
|
|
20
|
+
throw new Error("Codex Mission requires existing PowerShell 7 (pwsh.exe). Select it with --trusted-pwsh; no shell is installed automatically.");
|
|
21
|
+
return { description: "Windows PowerShell 7 (not POSIX bash)", command: source => codexPowerShellCommand(executable, source) };
|
|
22
|
+
}
|
|
@@ -165,6 +165,7 @@ export interface CodexMissionOwner {
|
|
|
165
165
|
startTicks?: string;
|
|
166
166
|
generation: string;
|
|
167
167
|
closed?: boolean;
|
|
168
|
+
platform?: NodeJS.Platform;
|
|
168
169
|
}
|
|
169
170
|
export declare function codexProcessOwner(): Promise<CodexMissionOwner>;
|
|
170
171
|
export declare function codexOwnerGone(owner: CodexMissionOwner | undefined): Promise<boolean>;
|
|
@@ -27,7 +27,7 @@ async function codexProcessStart(pid) {
|
|
|
27
27
|
return ticks;
|
|
28
28
|
}
|
|
29
29
|
export async function codexProcessOwner() {
|
|
30
|
-
const owner = { pid: process.pid, generation: randomUUID() };
|
|
30
|
+
const owner = { pid: process.pid, generation: randomUUID(), platform: process.platform };
|
|
31
31
|
if (process.platform === "linux") {
|
|
32
32
|
owner.bootID = (await readFile("/proc/sys/kernel/random/boot_id", "utf8")).trim();
|
|
33
33
|
owner.startTicks = await codexProcessStart(process.pid);
|
|
@@ -39,7 +39,20 @@ export async function codexOwnerGone(owner) {
|
|
|
39
39
|
return false;
|
|
40
40
|
if (owner.closed)
|
|
41
41
|
return true;
|
|
42
|
-
if (
|
|
42
|
+
if (!Number.isSafeInteger(owner.pid) || owner.pid < 1 || owner.platform && owner.platform !== process.platform)
|
|
43
|
+
return false;
|
|
44
|
+
if (process.platform === "win32" && owner.platform === "win32") {
|
|
45
|
+
// Windows has no /proc identity. Only an absent PID proves this owner gone;
|
|
46
|
+
// PID reuse, access denial and legacy/unidentified owners stay unresolved.
|
|
47
|
+
try {
|
|
48
|
+
process.kill(owner.pid, 0);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return error.code === "ESRCH";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (process.platform !== "linux" || !owner.bootID || !owner.startTicks)
|
|
43
56
|
return false;
|
|
44
57
|
try {
|
|
45
58
|
if ((await readFile("/proc/sys/kernel/random/boot_id", "utf8")).trim() !== owner.bootID)
|
|
@@ -267,7 +280,7 @@ export class OperatorMissionRuntime {
|
|
|
267
280
|
async codexRecovery(root, action) {
|
|
268
281
|
const lock = `${this.file(root)}.codex-recovery.lock`;
|
|
269
282
|
const owner = await codexProcessOwner();
|
|
270
|
-
const marker = `owner.${owner.pid}.${owner.bootID ?? "unknown"}.${owner.startTicks ?? "unknown"}.${owner.generation}`;
|
|
283
|
+
const marker = `owner.${owner.pid}.${owner.platform === "win32" ? "win32." : ""}${owner.bootID ?? "unknown"}.${owner.startTicks ?? "unknown"}.${owner.generation}`;
|
|
271
284
|
for (let attempt = 0; attempt < 200; attempt++) {
|
|
272
285
|
let acquired = false;
|
|
273
286
|
const staging = `${lock}.${owner.generation}.tmp`;
|
|
@@ -294,8 +307,10 @@ export class OperatorMissionRuntime {
|
|
|
294
307
|
await unlink(join(lock, marker)).catch(() => undefined);
|
|
295
308
|
const names = await readdir(lock).catch(() => []);
|
|
296
309
|
if (names.length === 1) {
|
|
297
|
-
|
|
298
|
-
|
|
310
|
+
// Retain legacy Linux markers; old Windows "unknown" owners are not proof.
|
|
311
|
+
const match = /^owner\.(\d+)\.(?:(linux|win32)\.)?([a-f0-9-]+|unknown)\.(\d+|unknown)\.([a-f0-9-]+)$/.exec(names[0]);
|
|
312
|
+
if (match && await codexOwnerGone({ pid: Number(match[1]), platform: match[2],
|
|
313
|
+
bootID: match[3] === "unknown" ? undefined : match[3], startTicks: match[4] === "unknown" ? undefined : match[4], generation: match[5] })) {
|
|
299
314
|
// Remove only the dead owner's unique marker; never unlink a replacement owner's file.
|
|
300
315
|
await unlink(join(lock, names[0])).catch(() => undefined);
|
|
301
316
|
}
|