lossless-openclaw-orchestrator 1.1.0 → 1.1.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/README.md +1 -1
- package/VISION.md +3 -3
- package/dist/packages/adapters/src/codex-jsonrpc.js +20 -0
- package/dist/packages/adapters/src/index.js +69 -7
- package/dist/packages/cli/src/live-control-smoke.js +157 -3
- package/dist/packages/mcp-server/src/tools.js +23 -9
- package/docs/RELEASE_NOTES_1.1.1.md +103 -0
- package/docs/RELEASE_NOTES_1.1.2.md +99 -0
- package/evals/scenarios/v1.1/desktop-first-daily-orchestration.json +81 -0
- package/openclaw.plugin.json +8 -2
- package/package.json +1 -1
- package/packages/adapters/src/codex-jsonrpc.ts +20 -0
- package/packages/adapters/src/index.ts +80 -8
- package/packages/cli/src/live-control-smoke.ts +226 -3
- package/packages/mcp-server/src/tools.ts +23 -9
- package/packages/openclaw-plugin/openclaw.plugin.json +8 -2
- package/skills/lossless-openclaw-orchestrator/SKILL.md +28 -0
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ safe action without rereading raw transcripts.
|
|
|
14
14
|
[](LICENSE)
|
|
15
15
|
[](CONTRIBUTING.md)
|
|
16
16
|
|
|
17
|
-
[Setup](docs/SETUP.md) · [Contributing](CONTRIBUTING.md) · [Agent Instructions](AGENTS.md) · [Agent Skill](skills/lossless-openclaw-orchestrator/SKILL.md) · [OpenClaw Plugin](docs/OPENCLAW_PLUGIN.md) · [Security](SECURITY.md) · [Code of Conduct](CODE_OF_CONDUCT.md) · [Vision](VISION.md) · [Privacy](docs/PRIVACY.md) · [Claude Boundary](docs/CLAUDE_ADAPTER_BOUNDARY.md) · [Claim Audit](docs/CLAIM_AUDIT.md) · [Release Notes](docs/RELEASE_NOTES_1.1.
|
|
17
|
+
[Setup](docs/SETUP.md) · [Contributing](CONTRIBUTING.md) · [Agent Instructions](AGENTS.md) · [Agent Skill](skills/lossless-openclaw-orchestrator/SKILL.md) · [OpenClaw Plugin](docs/OPENCLAW_PLUGIN.md) · [Security](SECURITY.md) · [Code of Conduct](CODE_OF_CONDUCT.md) · [Vision](VISION.md) · [Privacy](docs/PRIVACY.md) · [Claude Boundary](docs/CLAUDE_ADAPTER_BOUNDARY.md) · [Claim Audit](docs/CLAIM_AUDIT.md) · [Release Notes](docs/RELEASE_NOTES_1.1.2.md) · [1.0 Notes](docs/RELEASE_NOTES_1.0.0.md) · [License](LICENSE)
|
|
18
18
|
|
|
19
19
|
## Why It Matters
|
|
20
20
|
|
package/VISION.md
CHANGED
|
@@ -10,9 +10,9 @@ The stable product should feel like a local orchestration cockpit: OpenClaw can
|
|
|
10
10
|
|
|
11
11
|
## Current Milestone: 1.1 Desktop Collaboration Cockpit
|
|
12
12
|
|
|
13
|
-
The current release lane is [#
|
|
13
|
+
The current release lane is [#396](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/396), which publishes stable `1.1.2` on npm `latest` after the same full check, smoke, scenario, scorecard, release-status, finalization, and post-publish readiness gates used for `1.1.1`. This patch carries the same stable claim scope while adding the #381 issue-grade public-safe live-control smoke failure report.
|
|
14
14
|
|
|
15
|
-
The stable 1.0.0
|
|
15
|
+
The stable 1.0.0, 1.1.0, and 1.1.1 packages have shipped on npm `latest` and their GitHub Releases are published. The post-GA Desktop claim-validation lane [#306](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/306) records that Desktop-visible classification and fallback readiness/status are proven, while actual Codex GUI mutation remains excluded. Desktop parity [#307](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/307) added the coherence classifier; desktop fallback [#308](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/308) added the CUA-first and Peekaboo-secondary readiness report. The active product tracker is the 1.1 collaboration cockpit [#309](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/309), with the operating loop [#16](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/16).
|
|
16
16
|
|
|
17
17
|
The Codex Autonomy Cockpit [#254](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/254) and Eva Operating Picture [#255](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/255) P0 lanes are completed beta foundation, not the current active child-work list. Completed P0 children include shared contracts [#256](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/256), source authority [#258](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/258), watcher/resume requests [#259](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/259), visible Codex map joins [#260](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/260), deterministic GitHub operating inputs [#264](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/264)/[#265](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/265), current-lane source balancing [#269](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/269), GitHub check-state fidelity [#270](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/270), cockpit card cleanup [#271](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/271), and end-to-end Eva cockpit dogfood [#272](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/272). The sprint brief remains the historical handoff for that P0 work: [docs/sprints/brief-lco-codex-autonomy-cockpit-sprint-2026-07-01.md](docs/sprints/brief-lco-codex-autonomy-cockpit-sprint-2026-07-01.md).
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ The core Codex recall, M9 handoff paths, Codex Autonomy Cockpit, Eva Operating P
|
|
|
20
20
|
|
|
21
21
|
The current target is:
|
|
22
22
|
|
|
23
|
-
- Promote npm `latest` from stable `1.
|
|
23
|
+
- Promote npm `latest` from stable `1.1.1` to stable `1.1.2` only after #396 passes full check, smoke, scenario, scorecard, release-status, finalization, and post-publish readiness gates; keep `beta` aligned with beta trains and use `next` for future release candidates.
|
|
24
24
|
- Keep README, VISION, release notes, and scorecards current so completed release gates are recorded as completed proof, not active work.
|
|
25
25
|
- Treat #307/#308 as completed proof for Desktop-visible classification and fallback readiness/status, not as proof of Codex GUI mutation, prompt typing, clicking, refresh/restart automation, or unattended visible collaboration.
|
|
26
26
|
- Use #309 to design the next collaboration cockpit layer from structured public-safe cards, explicit action-bound proof gates, and clear excluded-claim wording.
|
|
@@ -209,6 +209,26 @@ export function createCodexMcpStdioClient(options = {}) {
|
|
|
209
209
|
finally {
|
|
210
210
|
await client.close();
|
|
211
211
|
}
|
|
212
|
+
},
|
|
213
|
+
async requestSequence(steps) {
|
|
214
|
+
const surface = options.surface ?? "control";
|
|
215
|
+
for (const step of steps)
|
|
216
|
+
assertCodexMethodAllowed(step.method, surface);
|
|
217
|
+
const client = new CodexJsonRpcClient(() => new LineProcessTransport(options.command ?? "codex", options.args ?? ["app-server", "--stdio"], options.timeoutMs), { timeoutMs: options.timeoutMs, surface });
|
|
218
|
+
await client.connect();
|
|
219
|
+
try {
|
|
220
|
+
const responses = [];
|
|
221
|
+
for (const step of steps) {
|
|
222
|
+
const response = await client.request(step.method, step.params);
|
|
223
|
+
responses.push(response);
|
|
224
|
+
if (!response.ok)
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
return responses;
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
await client.close();
|
|
231
|
+
}
|
|
212
232
|
}
|
|
213
233
|
};
|
|
214
234
|
}
|
|
@@ -74,7 +74,19 @@ export function createAuditStore(path) {
|
|
|
74
74
|
export function createCodexControl(options) {
|
|
75
75
|
const execute = async (spec) => {
|
|
76
76
|
assertCodexMethodAllowed(spec.method, "control");
|
|
77
|
-
const
|
|
77
|
+
const steps = spec.steps?.length ? spec.steps : [{ method: spec.method, params: spec.params }];
|
|
78
|
+
const methodSequence = steps.map((step) => step.method);
|
|
79
|
+
for (const step of steps)
|
|
80
|
+
assertCodexMethodAllowed(step.method, "control");
|
|
81
|
+
const connectionScope = steps.length > 1 ? "same_connection_sequence" : "single_request";
|
|
82
|
+
const paramsHash = options.audit.fingerprintValue({
|
|
83
|
+
action: spec.action,
|
|
84
|
+
method: spec.method,
|
|
85
|
+
methodSequence,
|
|
86
|
+
threadId: spec.threadId,
|
|
87
|
+
params: spec.params,
|
|
88
|
+
steps
|
|
89
|
+
});
|
|
78
90
|
const messageHash = spec.message === undefined ? undefined : options.audit.fingerprintText(spec.message);
|
|
79
91
|
if (spec.dryRun !== false) {
|
|
80
92
|
const record = options.audit.append({
|
|
@@ -84,7 +96,18 @@ export function createCodexControl(options) {
|
|
|
84
96
|
messageHash,
|
|
85
97
|
live: false
|
|
86
98
|
});
|
|
87
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
action: spec.action,
|
|
101
|
+
threadId: spec.threadId,
|
|
102
|
+
live: false,
|
|
103
|
+
approvalAuditId: record.id,
|
|
104
|
+
paramsHash,
|
|
105
|
+
messageHash,
|
|
106
|
+
method: spec.method,
|
|
107
|
+
methodSequence,
|
|
108
|
+
connectionScope,
|
|
109
|
+
loadedThreadReusable: spec.loadedThreadReusable
|
|
110
|
+
};
|
|
88
111
|
}
|
|
89
112
|
if (!spec.approvalAuditId) {
|
|
90
113
|
throw new Error("approval_audit_id is required for live Codex control actions");
|
|
@@ -103,12 +126,28 @@ export function createCodexControl(options) {
|
|
|
103
126
|
if (!Number.isFinite(dryRunCreatedAtMs) || dryRunCreatedAtMs + CODEX_CONTROL_DRY_RUN_TTL_MS <= Date.now()) {
|
|
104
127
|
throw new Error("approval_audit_id dry-run record expired");
|
|
105
128
|
}
|
|
106
|
-
const response =
|
|
129
|
+
const response = steps.length > 1
|
|
130
|
+
? await requestCodexControlSequence(options.client, steps)
|
|
131
|
+
: await options.client.request(spec.method, spec.params);
|
|
107
132
|
const liveRecord = options.audit.append({ action: spec.action, target: spec.threadId, paramsHash, messageHash, live: true });
|
|
108
|
-
return {
|
|
133
|
+
return {
|
|
134
|
+
action: spec.action,
|
|
135
|
+
threadId: spec.threadId,
|
|
136
|
+
live: true,
|
|
137
|
+
approvalAuditId: liveRecord.id,
|
|
138
|
+
paramsHash,
|
|
139
|
+
messageHash,
|
|
140
|
+
method: spec.method,
|
|
141
|
+
methodSequence,
|
|
142
|
+
connectionScope,
|
|
143
|
+
loadedThreadReusable: spec.loadedThreadReusable,
|
|
144
|
+
response: redactValue(response)
|
|
145
|
+
};
|
|
109
146
|
};
|
|
110
147
|
return {
|
|
111
148
|
sendMessage(input) {
|
|
149
|
+
const resumeParams = { threadId: input.threadId, excludeTurns: true };
|
|
150
|
+
const turnStartParams = { threadId: input.threadId, input: [{ type: "text", text: input.message }] };
|
|
112
151
|
return execute({
|
|
113
152
|
action: "codex_send_message",
|
|
114
153
|
method: "turn/start",
|
|
@@ -116,7 +155,12 @@ export function createCodexControl(options) {
|
|
|
116
155
|
message: input.message,
|
|
117
156
|
dryRun: input.dryRun,
|
|
118
157
|
approvalAuditId: input.approvalAuditId,
|
|
119
|
-
params:
|
|
158
|
+
params: turnStartParams,
|
|
159
|
+
steps: [
|
|
160
|
+
{ method: "thread/resume", params: resumeParams },
|
|
161
|
+
{ method: "turn/start", params: turnStartParams }
|
|
162
|
+
],
|
|
163
|
+
loadedThreadReusable: true
|
|
120
164
|
});
|
|
121
165
|
},
|
|
122
166
|
resumeThread(input) {
|
|
@@ -126,10 +170,13 @@ export function createCodexControl(options) {
|
|
|
126
170
|
threadId: input.threadId,
|
|
127
171
|
dryRun: input.dryRun,
|
|
128
172
|
approvalAuditId: input.approvalAuditId,
|
|
129
|
-
params: { threadId: input.threadId, excludeTurns: true }
|
|
173
|
+
params: { threadId: input.threadId, excludeTurns: true },
|
|
174
|
+
loadedThreadReusable: false
|
|
130
175
|
});
|
|
131
176
|
},
|
|
132
177
|
steerThread(input) {
|
|
178
|
+
if (!input.expectedTurnId)
|
|
179
|
+
throw new Error("expected_turn_id is required for steer actions");
|
|
133
180
|
return execute({
|
|
134
181
|
action: "codex_steer_thread",
|
|
135
182
|
method: "turn/steer",
|
|
@@ -137,7 +184,7 @@ export function createCodexControl(options) {
|
|
|
137
184
|
message: input.message,
|
|
138
185
|
dryRun: input.dryRun,
|
|
139
186
|
approvalAuditId: input.approvalAuditId,
|
|
140
|
-
params: { threadId: input.threadId, input: [{ type: "text", text: input.message }] }
|
|
187
|
+
params: { threadId: input.threadId, expectedTurnId: input.expectedTurnId, input: [{ type: "text", text: input.message }] }
|
|
141
188
|
});
|
|
142
189
|
},
|
|
143
190
|
interruptThread(input) {
|
|
@@ -152,6 +199,21 @@ export function createCodexControl(options) {
|
|
|
152
199
|
}
|
|
153
200
|
};
|
|
154
201
|
}
|
|
202
|
+
async function requestCodexControlSequence(client, steps) {
|
|
203
|
+
if (!client.requestSequence) {
|
|
204
|
+
throw new Error("same-connection control sequence is required for this Codex control action");
|
|
205
|
+
}
|
|
206
|
+
const responses = await client.requestSequence(steps);
|
|
207
|
+
for (let index = 0; index < responses.length; index += 1) {
|
|
208
|
+
if (asRecord(responses[index])?.ok === false) {
|
|
209
|
+
throw new Error(`Codex control sequence step failed: ${steps[index]?.method ?? "unknown"}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (responses.length !== steps.length) {
|
|
213
|
+
throw new Error(`Codex control sequence returned ${responses.length} response(s) for ${steps.length} step(s)`);
|
|
214
|
+
}
|
|
215
|
+
return responses.at(-1) ?? { ok: true };
|
|
216
|
+
}
|
|
155
217
|
export async function createCodexAppServerStatusReport(options) {
|
|
156
218
|
const transport = options.transport ?? codexTransportStatus({ command: options.command ?? process.env.LOO_CODEX_BIN ?? "codex" });
|
|
157
219
|
const remoteControl = await codexReadRequest(options.client, "remoteControl/status/read", {});
|
|
@@ -51,13 +51,52 @@ export async function runLiveControlSmoke(options) {
|
|
|
51
51
|
mkdirSync(options.evidenceDir, { recursive: true });
|
|
52
52
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
53
53
|
const message = options.message ?? DEFAULT_MESSAGE;
|
|
54
|
-
|
|
54
|
+
let targetState = {
|
|
55
|
+
threadId: options.threadId ?? null,
|
|
56
|
+
source: options.threadId ? "provided_thread" : "unknown"
|
|
57
|
+
};
|
|
58
|
+
let dryRunState = null;
|
|
59
|
+
let liveState = {
|
|
60
|
+
accepted: false,
|
|
61
|
+
approvalAuditId: null,
|
|
62
|
+
method: null,
|
|
63
|
+
completed: false,
|
|
64
|
+
status: null,
|
|
65
|
+
notificationMethods: [],
|
|
66
|
+
approvalRequestCount: 0,
|
|
67
|
+
serverRequestCount: 0
|
|
68
|
+
};
|
|
69
|
+
let failed = false;
|
|
55
70
|
try {
|
|
71
|
+
await options.client.connect();
|
|
56
72
|
const target = options.threadId
|
|
57
73
|
? { threadId: options.threadId, source: "provided_thread" }
|
|
58
74
|
: { threadId: await startEphemeralThread(options.client, options.cwd), source: "ephemeral_thread_start" };
|
|
59
|
-
|
|
75
|
+
targetState = target;
|
|
76
|
+
const control = createCodexControl({
|
|
77
|
+
audit: options.audit,
|
|
78
|
+
client: {
|
|
79
|
+
request: (method, params) => options.client.request(method, params),
|
|
80
|
+
requestSequence: async (steps) => {
|
|
81
|
+
const responses = [];
|
|
82
|
+
for (const step of steps) {
|
|
83
|
+
const response = await options.client.request(step.method, step.params);
|
|
84
|
+
responses.push(response);
|
|
85
|
+
if (isFailedCodexResponse(response)) {
|
|
86
|
+
throw new Error(`Codex control sequence step failed: ${step.method}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return responses;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
60
93
|
const dryRun = await control.sendMessage({ threadId: target.threadId, message, dryRun: true });
|
|
94
|
+
dryRunState = {
|
|
95
|
+
approvalAuditId: dryRun.approvalAuditId,
|
|
96
|
+
paramsHash: dryRun.paramsHash,
|
|
97
|
+
messageHash: dryRun.messageHash,
|
|
98
|
+
live: false
|
|
99
|
+
};
|
|
61
100
|
if (!dryRun.messageHash)
|
|
62
101
|
throw new Error("dry-run did not produce a message hash");
|
|
63
102
|
const live = await control.sendMessage({
|
|
@@ -66,10 +105,24 @@ export async function runLiveControlSmoke(options) {
|
|
|
66
105
|
dryRun: false,
|
|
67
106
|
approvalAuditId: dryRun.approvalAuditId
|
|
68
107
|
});
|
|
108
|
+
liveState = {
|
|
109
|
+
...liveState,
|
|
110
|
+
accepted: true,
|
|
111
|
+
approvalAuditId: live.approvalAuditId,
|
|
112
|
+
method: live.method ?? null
|
|
113
|
+
};
|
|
69
114
|
const turnId = extractTurnId(live.response);
|
|
70
115
|
if (!turnId)
|
|
71
116
|
throw new Error("live Codex control response did not include a turn id");
|
|
72
117
|
const completion = await options.client.waitForTurnCompletion({ threadId: target.threadId, turnId, timeoutMs });
|
|
118
|
+
liveState = {
|
|
119
|
+
...liveState,
|
|
120
|
+
completed: completion.completed,
|
|
121
|
+
status: completion.status,
|
|
122
|
+
notificationMethods: summarizeMethods(completion.notificationMethods),
|
|
123
|
+
approvalRequestCount: completion.approvalRequestCount,
|
|
124
|
+
serverRequestCount: completion.serverRequestCount
|
|
125
|
+
};
|
|
73
126
|
if (completion.approvalRequestCount > 0) {
|
|
74
127
|
throw new Error("live Codex control smoke observed a Codex approval request; harmless smoke must not require approvals");
|
|
75
128
|
}
|
|
@@ -122,8 +175,29 @@ export async function runLiveControlSmoke(options) {
|
|
|
122
175
|
writeJson(reportPath, report);
|
|
123
176
|
return report;
|
|
124
177
|
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
failed = true;
|
|
180
|
+
try {
|
|
181
|
+
writeFailureReport(options, {
|
|
182
|
+
error,
|
|
183
|
+
target: targetState,
|
|
184
|
+
dryRun: dryRunState,
|
|
185
|
+
live: liveState
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// Best-effort evidence: never let report-writing failures mask the original smoke failure.
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
125
193
|
finally {
|
|
126
|
-
|
|
194
|
+
try {
|
|
195
|
+
await options.client.close();
|
|
196
|
+
}
|
|
197
|
+
catch (closeError) {
|
|
198
|
+
if (!failed)
|
|
199
|
+
throw closeError;
|
|
200
|
+
}
|
|
127
201
|
}
|
|
128
202
|
}
|
|
129
203
|
export function defaultAppServerArgs() {
|
|
@@ -151,6 +225,9 @@ function extractTurnId(response) {
|
|
|
151
225
|
const turn = objectField(result.turn);
|
|
152
226
|
return stringField(turn, "id");
|
|
153
227
|
}
|
|
228
|
+
function isFailedCodexResponse(value) {
|
|
229
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && value.ok === false);
|
|
230
|
+
}
|
|
154
231
|
function unwrapResult(value) {
|
|
155
232
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
156
233
|
return {};
|
|
@@ -178,3 +255,80 @@ function summarizeMethods(methods) {
|
|
|
178
255
|
function writeJson(path, value) {
|
|
179
256
|
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
180
257
|
}
|
|
258
|
+
function writeFailureReport(options, state) {
|
|
259
|
+
const blocker = classifyFailureBlocker(state.error);
|
|
260
|
+
const report = {
|
|
261
|
+
kind: "loo_live_control_smoke_failure",
|
|
262
|
+
ok: false,
|
|
263
|
+
proofReady: false,
|
|
264
|
+
generatedAt: options.now ?? new Date().toISOString(),
|
|
265
|
+
blocker,
|
|
266
|
+
command: {
|
|
267
|
+
action: "send",
|
|
268
|
+
actionClass: "codex_live_control_send"
|
|
269
|
+
},
|
|
270
|
+
target: {
|
|
271
|
+
source: state.target.source,
|
|
272
|
+
refClass: state.target.threadId ? "codex_thread" : "unknown",
|
|
273
|
+
ref: state.target.threadId ? `codex_thread:${state.target.threadId}` : null
|
|
274
|
+
},
|
|
275
|
+
dryRun: {
|
|
276
|
+
attempted: state.dryRun !== null,
|
|
277
|
+
approvalAuditId: state.dryRun?.approvalAuditId ?? null,
|
|
278
|
+
paramsHash: state.dryRun?.paramsHash ?? null,
|
|
279
|
+
messageHash: state.dryRun?.messageHash ?? null,
|
|
280
|
+
live: state.dryRun ? false : null
|
|
281
|
+
},
|
|
282
|
+
transport: {
|
|
283
|
+
class: "codex_app_server",
|
|
284
|
+
connection: "single_connection_sequence"
|
|
285
|
+
},
|
|
286
|
+
live: state.live,
|
|
287
|
+
postActionRefresh: {
|
|
288
|
+
ran: false
|
|
289
|
+
},
|
|
290
|
+
nextDiagnosticStep: nextDiagnosticStep(blocker),
|
|
291
|
+
rawPromptIncluded: false,
|
|
292
|
+
rawTranscriptIncluded: false,
|
|
293
|
+
rawSecretIncluded: false,
|
|
294
|
+
screenshotIncluded: false,
|
|
295
|
+
sqliteIncluded: false
|
|
296
|
+
};
|
|
297
|
+
writeJson(join(options.evidenceDir, "live-control-smoke-failure-report.json"), report);
|
|
298
|
+
}
|
|
299
|
+
function classifyFailureBlocker(error) {
|
|
300
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
301
|
+
if (/connect|handshake|spawn|ENOENT|codex.*not found|not connected/i.test(message)) {
|
|
302
|
+
return "codex_app_server_setup_failed";
|
|
303
|
+
}
|
|
304
|
+
if (/thread(?:\/resume| not found)|control sequence step failed:\s*thread\/resume/i.test(message)) {
|
|
305
|
+
return "same_connection_resume_load_diagnostics_required";
|
|
306
|
+
}
|
|
307
|
+
if (/approval request/i.test(message))
|
|
308
|
+
return "codex_approval_request_observed";
|
|
309
|
+
if (/server request/i.test(message))
|
|
310
|
+
return "codex_server_request_observed";
|
|
311
|
+
if (/turn id/i.test(message))
|
|
312
|
+
return "live_control_turn_id_missing";
|
|
313
|
+
if (/did not complete cleanly|timeout/i.test(message))
|
|
314
|
+
return "live_control_smoke_not_completed_cleanly";
|
|
315
|
+
return "live_control_smoke_failed";
|
|
316
|
+
}
|
|
317
|
+
function nextDiagnosticStep(blocker) {
|
|
318
|
+
switch (blocker) {
|
|
319
|
+
case "codex_app_server_setup_failed":
|
|
320
|
+
return "Check Codex app-server command availability, stdio handshake, and local transport setup before retrying live control.";
|
|
321
|
+
case "same_connection_resume_load_diagnostics_required":
|
|
322
|
+
return "Run same-connection resume/load diagnostics for the selected thread before retrying live control.";
|
|
323
|
+
case "codex_approval_request_observed":
|
|
324
|
+
return "Inspect why the harmless smoke triggered a Codex approval request; do not treat this as approved live-control proof.";
|
|
325
|
+
case "codex_server_request_observed":
|
|
326
|
+
return "Inspect unexpected server requests from the app-server smoke; do not retry live control until the request class is understood.";
|
|
327
|
+
case "live_control_turn_id_missing":
|
|
328
|
+
return "Inspect the app-server turn/start response shape and protocol drift before retrying.";
|
|
329
|
+
case "live_control_smoke_not_completed_cleanly":
|
|
330
|
+
return "Inspect turn completion notifications, status, and timeout behavior before retrying live control.";
|
|
331
|
+
default:
|
|
332
|
+
return "Inspect the public-safe failure report and route a focused issue before retrying live control.";
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -492,11 +492,12 @@ export function createLooTools(options) {
|
|
|
492
492
|
tool("loo_codex_control_dry_run", "Create a dry-run audit id for a Codex control action.", {
|
|
493
493
|
action: { type: "string", enum: ["send", "resume", "steer", "interrupt"] },
|
|
494
494
|
thread_id: { type: "string" },
|
|
495
|
-
message: { type: "string" }
|
|
495
|
+
message: { type: "string" },
|
|
496
|
+
expected_turn_id: { type: "string" }
|
|
496
497
|
}, (input) => snakeCaseControlResult(dispatchControl(control, input, true))),
|
|
497
498
|
tool("loo_codex_resume_thread", "Resume or rejoin a Codex thread. Live mode requires approval_audit_id.", controlSchema(), (input) => snakeCaseControlResult(control.resumeThread(controlInput(input)))),
|
|
498
499
|
tool("loo_codex_send_message", "Send a message to a Codex thread. Live mode requires approval_audit_id.", controlSchema(true), (input) => snakeCaseControlResult(control.sendMessage(messageControlInput(input)))),
|
|
499
|
-
tool("loo_codex_steer_thread", "Steer a running Codex thread. Live mode requires approval_audit_id.", controlSchema(true), (input) => snakeCaseControlResult(control.steerThread(messageControlInput(input)))),
|
|
500
|
+
tool("loo_codex_steer_thread", "Steer a running Codex thread. Live mode requires approval_audit_id and expected_turn_id.", controlSchema(true, true), (input) => snakeCaseControlResult(control.steerThread(messageControlInput(input, true)))),
|
|
500
501
|
tool("loo_codex_interrupt_thread", "Interrupt a Codex thread. Live mode requires approval_audit_id.", controlSchema(), (input) => snakeCaseControlResult(control.interruptThread(controlInput(input)))),
|
|
501
502
|
tool("loo_desktop_see", "Inspect desktop fallback readiness through direct/CUA/Peekaboo backends.", {
|
|
502
503
|
backend: { type: "string", enum: ["direct", "cua-driver", "peekaboo"] },
|
|
@@ -680,7 +681,14 @@ function targetThreadIdFromCoherenceInput(input) {
|
|
|
680
681
|
}
|
|
681
682
|
function dispatchControl(control, input, dryRun) {
|
|
682
683
|
const action = requiredString(input.action, "action");
|
|
683
|
-
const common = {
|
|
684
|
+
const common = {
|
|
685
|
+
threadId: requiredString(input.thread_id, "thread_id"),
|
|
686
|
+
message: action === "send" || action === "steer"
|
|
687
|
+
? requiredString(input.message, "message")
|
|
688
|
+
: optionalString(input.message) ?? "continue",
|
|
689
|
+
expectedTurnId: optionalString(input.expected_turn_id),
|
|
690
|
+
dryRun
|
|
691
|
+
};
|
|
684
692
|
if (action === "send")
|
|
685
693
|
return control.sendMessage(common);
|
|
686
694
|
if (action === "resume")
|
|
@@ -691,10 +699,11 @@ function dispatchControl(control, input, dryRun) {
|
|
|
691
699
|
return control.interruptThread(common);
|
|
692
700
|
throw new Error(`Unsupported control action: ${action}`);
|
|
693
701
|
}
|
|
694
|
-
function controlSchema(message = false) {
|
|
702
|
+
function controlSchema(message = false, expectedTurn = false) {
|
|
695
703
|
return {
|
|
696
704
|
thread_id: { type: "string" },
|
|
697
705
|
...(message ? { message: { type: "string" } } : {}),
|
|
706
|
+
...(expectedTurn ? { expected_turn_id: { type: "string" } } : {}),
|
|
698
707
|
dry_run: { type: "boolean", default: true },
|
|
699
708
|
approval_audit_id: { type: "string" }
|
|
700
709
|
};
|
|
@@ -707,10 +716,11 @@ function controlInput(input, message = false) {
|
|
|
707
716
|
approvalAuditId: optionalString(input.approval_audit_id)
|
|
708
717
|
};
|
|
709
718
|
}
|
|
710
|
-
function messageControlInput(input) {
|
|
719
|
+
function messageControlInput(input, expectedTurn = false) {
|
|
711
720
|
return {
|
|
712
721
|
threadId: requiredString(input.thread_id, "thread_id"),
|
|
713
722
|
message: requiredString(input.message, "message"),
|
|
723
|
+
...(expectedTurn ? { expectedTurnId: requiredString(input.expected_turn_id, "expected_turn_id") } : {}),
|
|
714
724
|
dryRun: input.dry_run !== false,
|
|
715
725
|
approvalAuditId: optionalString(input.approval_audit_id)
|
|
716
726
|
};
|
|
@@ -728,14 +738,17 @@ async function snakeCaseControlResult(value) {
|
|
|
728
738
|
}
|
|
729
739
|
function approvalPacketFromControlResult(result) {
|
|
730
740
|
const action = String(result.action ?? "resume");
|
|
731
|
-
const packetAction = action === "send"
|
|
741
|
+
const packetAction = action === "send" || action === "codex_send_message"
|
|
732
742
|
? "send_message"
|
|
733
|
-
: action === "steer"
|
|
743
|
+
: action === "steer" || action === "codex_steer_thread"
|
|
734
744
|
? "steer_thread"
|
|
735
|
-
: action === "interrupt"
|
|
745
|
+
: action === "interrupt" || action === "codex_interrupt_thread"
|
|
736
746
|
? "interrupt_thread"
|
|
737
747
|
: "resume_session";
|
|
738
748
|
const expiresAt = new Date(Date.now() + 15 * 60 * 1000).toISOString();
|
|
749
|
+
const predictedMutation = Array.isArray(result.methodSequence) && result.methodSequence.length
|
|
750
|
+
? result.methodSequence.map((method) => String(method))
|
|
751
|
+
: [String(result.method ?? "codex_control")];
|
|
739
752
|
return {
|
|
740
753
|
schema: "lco.approvalPacket.v1",
|
|
741
754
|
packetId: `ap_${String(result.approvalAuditId ?? "unknown").replace(/^loo_audit_/, "")}`,
|
|
@@ -745,7 +758,8 @@ function approvalPacketFromControlResult(result) {
|
|
|
745
758
|
title: String(result.threadId ?? "unknown")
|
|
746
759
|
},
|
|
747
760
|
intent: `${packetAction} dry-run for ${String(result.threadId ?? "unknown")}`,
|
|
748
|
-
predictedMutation
|
|
761
|
+
predictedMutation,
|
|
762
|
+
...(Array.isArray(result.methodSequence) ? { methodSequence: result.methodSequence } : {}),
|
|
749
763
|
preconditions: ["dry_run_record_exists", "matching_params_hash_required", "approval_packet_not_expired"],
|
|
750
764
|
risk: {
|
|
751
765
|
level: action === "interrupt" ? "high" : "medium",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Release Notes 1.1.1
|
|
2
|
+
|
|
3
|
+
`1.1.1` is a stable patch release for the post-1.1 Codex collaboration
|
|
4
|
+
cockpit and OpenClaw gateway lane.
|
|
5
|
+
|
|
6
|
+
It keeps the `1.1.0` stable claim scope and adds three release-train fixes:
|
|
7
|
+
same-connection live-control failure handling, Desktop-first daily skill
|
|
8
|
+
guidance, and a runtime-required daily orchestration scenario contract.
|
|
9
|
+
|
|
10
|
+
## What Changed
|
|
11
|
+
|
|
12
|
+
- Hardens same-connection Codex live control from #380 / PR #390:
|
|
13
|
+
- `loo_codex_send_message` keeps `thread/resume` and `turn/start` on the
|
|
14
|
+
same Codex app-server connection.
|
|
15
|
+
- failed or incomplete multi-step responses now fail closed before a live
|
|
16
|
+
audit success record is appended.
|
|
17
|
+
- the live-control smoke helper stops after the first failed sequence step,
|
|
18
|
+
so a failed `thread/resume` cannot be followed by `turn/start`.
|
|
19
|
+
- Updates the packaged OpenClaw agent skill from #385 / PR #391:
|
|
20
|
+
- replaces private-name approval wording with explicit requesting-user
|
|
21
|
+
approval for the exact target and action.
|
|
22
|
+
- clarifies that autonomy tick steps are recommendations until separately
|
|
23
|
+
requested and approved.
|
|
24
|
+
- Adds the Desktop-first daily orchestration scenario from #382 / PR #392:
|
|
25
|
+
- app-server status, app-server thread signals, visible Codex map,
|
|
26
|
+
Desktop coherence, fallback status, collaboration cockpit, runtime Desktop
|
|
27
|
+
visibility, active-thread state, and autonomy tick appear in one
|
|
28
|
+
runtime-required scenario contract.
|
|
29
|
+
- the scenario requires public-safe evidence and explicitly blocks live
|
|
30
|
+
control, GUI mutation, screenshots, npm publish, and GitHub Release
|
|
31
|
+
creation during the scenario itself.
|
|
32
|
+
- Carries forward the #160 desktop proof-action release boundary:
|
|
33
|
+
`loo_desktop_proof_action` / `loo desktop proof-action` validates the
|
|
34
|
+
action-bound CUA Driver TextEdit scratch proof gate and does not prove
|
|
35
|
+
generic GUI mutation. The proof action still requires exact backend, target
|
|
36
|
+
app, target window, action hash, approval ref, permission state, scratch file
|
|
37
|
+
path, and `execute: true` before the backend can run. Generic gateway
|
|
38
|
+
invocation without exact proof args fails closed, using the same proof
|
|
39
|
+
boundary as beta.35.
|
|
40
|
+
The proof action still requires exact backend, target app, target window, action hash, approval ref, permission state, scratch file path, and `execute: true` before the backend can run.
|
|
41
|
+
generic gateway invocation without exact proof args fails closed.
|
|
42
|
+
This release uses the same proof boundary as beta.35.
|
|
43
|
+
- Carries forward strict OpenClaw gateway result handling:
|
|
44
|
+
plugin output with `output.details.ok: false` is reported as
|
|
45
|
+
`openclaw_tool_result_not_ok:<tool>` rather than successful tool proof.
|
|
46
|
+
|
|
47
|
+
## Stable Claim Scope
|
|
48
|
+
|
|
49
|
+
Allowed stable claim remains:
|
|
50
|
+
|
|
51
|
+
> Codex-first local orchestration through OpenClaw for local Codex sessions,
|
|
52
|
+
> including local indexing, search, describe, bounded expansion, dry-run control
|
|
53
|
+
> envelopes, read-only collaboration cockpit/autonomy cards, read-only Desktop
|
|
54
|
+
> visibility and fallback status, action-bound proof-packet validation,
|
|
55
|
+
> public-safe scorecards, and installed OpenClaw gateway dogfood.
|
|
56
|
+
|
|
57
|
+
This patch does not widen the live-control matrix, GUI mutation scope, Claude
|
|
58
|
+
adapter scope, P1 business-adapter scope, or enterprise/customer readiness
|
|
59
|
+
claim. The same excluded-claim boundaries from `1.1.0` remain in force.
|
|
60
|
+
|
|
61
|
+
## Release Gate Notes
|
|
62
|
+
|
|
63
|
+
- Stable issue: #393.
|
|
64
|
+
- Included implementation issues: #380, #385, and #382.
|
|
65
|
+
- Parent product tracker: #309.
|
|
66
|
+
- Operating-loop tracker: #16.
|
|
67
|
+
- Baseline stable release: `v1.1.0`.
|
|
68
|
+
- Candidate package: `lossless-openclaw-orchestrator@1.1.1`.
|
|
69
|
+
- Expected git tag: `v1.1.1`.
|
|
70
|
+
- Expected GitHub Release:
|
|
71
|
+
`https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/releases/tag/v1.1.1`.
|
|
72
|
+
- Required pre-publish stable gates: `npm run check`, `npm pack --dry-run`,
|
|
73
|
+
strict scenario sweep, strict scorecard sweep, release preflight, release
|
|
74
|
+
bundle, release demo-status, release status, OpenClaw dogfood, and OpenClaw
|
|
75
|
+
tool-smoke for the exact stable candidate SHA.
|
|
76
|
+
- Required PR gates: GitHub CI, CodeQL, current-head review threads clear, and
|
|
77
|
+
any actionable review feedback fixed before merge.
|
|
78
|
+
- Required post-publish stable gates: npm `latest` view, git tag, GitHub
|
|
79
|
+
non-prerelease Release, `loo release finalization-status --expected-dist-tag
|
|
80
|
+
latest --expected-github-prerelease false --strict`, fresh npm `@latest`
|
|
81
|
+
published-smoke, and strict general-readiness before #393 closes.
|
|
82
|
+
- `approved_live_control_smoke_missing` remains the expected blocker when a
|
|
83
|
+
working-app claim is attempted without approved live-control smoke evidence
|
|
84
|
+
for the exact candidate SHA.
|
|
85
|
+
- Working-app status example:
|
|
86
|
+
`loo release status --claim-scope codex-working-app-proof --runtime-proof-dir <path> --approved-live-control-evidence <path> --npm-publish-approval-evidence <path> --github-release-approval-evidence <path> --candidate-sha <sha> --github-ci-evidence <path> --codeql-evidence <path> --evidence-dir <path> --strict`
|
|
87
|
+
- Reduced-scope status example:
|
|
88
|
+
`loo release status --claim-scope codex-read-search-expand-dry-run --npm-publish-approval-evidence <path> --github-release-approval-evidence <path> --candidate-sha <sha> --github-ci-evidence <path> --codeql-evidence <path> --evidence-dir <path> --strict`
|
|
89
|
+
- Post-publish finalization example:
|
|
90
|
+
`loo release finalization-status --evidence-dir <path> --candidate-sha <sha> --npm-publish-evidence <path> --git-tag-evidence <path> --github-release-evidence <path> --expected-dist-tag latest --expected-github-prerelease false --strict`
|
|
91
|
+
|
|
92
|
+
## Explicit Non-Claims
|
|
93
|
+
|
|
94
|
+
No new live Codex control smoke is run by this release.
|
|
95
|
+
It does not run generic GUI mutation and does not run Codex GUI mutation.
|
|
96
|
+
No automatic gateway authorization.
|
|
97
|
+
No broad gateway scope approval. No prompt typing. No clicking. No arbitrary app control.
|
|
98
|
+
Claude Code remains an adapter stub, not an adapter-equivalence claim.
|
|
99
|
+
Bundle/status/finalization checks do not publish to npm and do not create a GitHub Release.
|
|
100
|
+
No release-grade enterprise security.
|
|
101
|
+
No Claude Code parity, no Notion/support-control/Stripe/Company Brain P1
|
|
102
|
+
adapter proof, no cloud sync, no unattended desktop takeover, and no
|
|
103
|
+
enterprise/customer-ready security is claimed.
|