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 CHANGED
@@ -14,7 +14,7 @@ safe action without rereading raw transcripts.
14
14
  [![License](https://img.shields.io/badge/license-PolyForm%20Noncommercial-blue)](LICENSE)
15
15
  [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](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.0.md) · [1.0 Notes](docs/RELEASE_NOTES_1.0.0.md) · [License](LICENSE)
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 [#387](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/387), which promotes the proven post-1.0 Codex collaboration cockpit and OpenClaw gateway work into stable `1.1.0` on npm `latest` after full check, smoke, scenario, scorecard, release-status, finalization, and post-publish readiness gates pass.
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 package has shipped on npm `latest` and the GitHub Release is 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).
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.0.0` to stable `1.1.0` only after #387 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.
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 paramsHash = options.audit.fingerprintValue({ action: spec.action, method: spec.method, threadId: spec.threadId, params: spec.params });
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 { action: spec.action, threadId: spec.threadId, live: false, approvalAuditId: record.id, paramsHash, messageHash, method: spec.method };
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 = await options.client.request(spec.method, spec.params);
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 { action: spec.action, threadId: spec.threadId, live: true, approvalAuditId: liveRecord.id, paramsHash, messageHash, method: spec.method, response: redactValue(response) };
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: { threadId: input.threadId, input: [{ type: "text", text: input.message }] }
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
- await options.client.connect();
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
- const control = createCodexControl({ audit: options.audit, client: { request: (method, params) => options.client.request(method, params) } });
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
- await options.client.close();
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 = { threadId: requiredString(input.thread_id, "thread_id"), message: optionalString(input.message) ?? "continue", dryRun };
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: [String(result.method ?? "codex_control")],
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.