lossless-openclaw-orchestrator 1.1.1 → 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/cli/src/live-control-smoke.js +137 -2
- package/docs/RELEASE_NOTES_1.1.2.md +99 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/packages/cli/src/live-control-smoke.ts +205 -2
- package/packages/openclaw-plugin/openclaw.plugin.json +1 -1
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 and 1.1.
|
|
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.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.
|
|
@@ -51,11 +51,28 @@ 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" };
|
|
75
|
+
targetState = target;
|
|
59
76
|
const control = createCodexControl({
|
|
60
77
|
audit: options.audit,
|
|
61
78
|
client: {
|
|
@@ -74,6 +91,12 @@ export async function runLiveControlSmoke(options) {
|
|
|
74
91
|
}
|
|
75
92
|
});
|
|
76
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
|
+
};
|
|
77
100
|
if (!dryRun.messageHash)
|
|
78
101
|
throw new Error("dry-run did not produce a message hash");
|
|
79
102
|
const live = await control.sendMessage({
|
|
@@ -82,10 +105,24 @@ export async function runLiveControlSmoke(options) {
|
|
|
82
105
|
dryRun: false,
|
|
83
106
|
approvalAuditId: dryRun.approvalAuditId
|
|
84
107
|
});
|
|
108
|
+
liveState = {
|
|
109
|
+
...liveState,
|
|
110
|
+
accepted: true,
|
|
111
|
+
approvalAuditId: live.approvalAuditId,
|
|
112
|
+
method: live.method ?? null
|
|
113
|
+
};
|
|
85
114
|
const turnId = extractTurnId(live.response);
|
|
86
115
|
if (!turnId)
|
|
87
116
|
throw new Error("live Codex control response did not include a turn id");
|
|
88
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
|
+
};
|
|
89
126
|
if (completion.approvalRequestCount > 0) {
|
|
90
127
|
throw new Error("live Codex control smoke observed a Codex approval request; harmless smoke must not require approvals");
|
|
91
128
|
}
|
|
@@ -138,8 +175,29 @@ export async function runLiveControlSmoke(options) {
|
|
|
138
175
|
writeJson(reportPath, report);
|
|
139
176
|
return report;
|
|
140
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
|
+
}
|
|
141
193
|
finally {
|
|
142
|
-
|
|
194
|
+
try {
|
|
195
|
+
await options.client.close();
|
|
196
|
+
}
|
|
197
|
+
catch (closeError) {
|
|
198
|
+
if (!failed)
|
|
199
|
+
throw closeError;
|
|
200
|
+
}
|
|
143
201
|
}
|
|
144
202
|
}
|
|
145
203
|
export function defaultAppServerArgs() {
|
|
@@ -197,3 +255,80 @@ function summarizeMethods(methods) {
|
|
|
197
255
|
function writeJson(path, value) {
|
|
198
256
|
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
199
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
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Release Notes 1.1.2
|
|
2
|
+
|
|
3
|
+
`1.1.2` 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.1` stable claim scope and adds the #381 live-control smoke
|
|
7
|
+
failure-evidence hardening.
|
|
8
|
+
|
|
9
|
+
## What Changed
|
|
10
|
+
|
|
11
|
+
- Failed `loo codex live-control-smoke` runs now write
|
|
12
|
+
`live-control-smoke-failure-report.json` before exiting non-zero.
|
|
13
|
+
- The failure report is public-safe and issue-ready:
|
|
14
|
+
- blocker code
|
|
15
|
+
- command/action class
|
|
16
|
+
- target ref class
|
|
17
|
+
- dry-run/audit/hash state
|
|
18
|
+
- live acceptance state
|
|
19
|
+
- post-action refresh state
|
|
20
|
+
- next diagnostic step
|
|
21
|
+
- Setup/connect failures now use the same guarded failure-report path, so
|
|
22
|
+
missing Codex app-server binaries, bad app-server args, and handshake failures
|
|
23
|
+
can produce structured public-safe evidence.
|
|
24
|
+
- Failure-report write errors no longer mask the original live-control smoke
|
|
25
|
+
failure.
|
|
26
|
+
- Regression coverage now proves both setup-failure reporting and original
|
|
27
|
+
error preservation.
|
|
28
|
+
- Carries forward the #160 desktop proof-action release boundary:
|
|
29
|
+
`loo_desktop_proof_action` / `loo desktop proof-action` validates the
|
|
30
|
+
action-bound CUA Driver TextEdit scratch proof gate and does not prove
|
|
31
|
+
generic GUI mutation. The proof action still requires exact backend, target
|
|
32
|
+
app, target window, action hash, approval ref, permission state, scratch file
|
|
33
|
+
path, and `execute: true` before the backend can run. Generic gateway
|
|
34
|
+
invocation without exact proof args fails closed, using the same proof
|
|
35
|
+
boundary as `1.1.1`.
|
|
36
|
+
Exact gate phrase: exact backend, target app, target window, action hash, approval ref, permission state, scratch file path, and `execute: true`.
|
|
37
|
+
Exact fail-closed phrase: generic gateway invocation without exact proof args fails closed.
|
|
38
|
+
The desktop proof action keeps the same proof boundary as beta.35.
|
|
39
|
+
- Carries forward strict OpenClaw gateway result handling:
|
|
40
|
+
plugin output with `output.details.ok: false` is reported as
|
|
41
|
+
`openclaw_tool_result_not_ok:<tool>` rather than successful tool proof.
|
|
42
|
+
|
|
43
|
+
## Stable Claim Scope
|
|
44
|
+
|
|
45
|
+
Allowed stable claim remains:
|
|
46
|
+
|
|
47
|
+
> Codex-first local orchestration through OpenClaw for local Codex sessions,
|
|
48
|
+
> including local indexing, search, describe, bounded expansion, dry-run control
|
|
49
|
+
> envelopes, read-only collaboration cockpit/autonomy cards, read-only Desktop
|
|
50
|
+
> visibility and fallback status, action-bound proof-packet validation,
|
|
51
|
+
> public-safe scorecards, and installed OpenClaw gateway dogfood.
|
|
52
|
+
|
|
53
|
+
This patch does not widen the live-control matrix, GUI mutation scope, Claude
|
|
54
|
+
adapter scope, P1 business-adapter scope, or enterprise/customer readiness
|
|
55
|
+
claim. The same excluded-claim boundaries from `1.1.1` remain in force.
|
|
56
|
+
|
|
57
|
+
## Release Gate Notes
|
|
58
|
+
|
|
59
|
+
- Stable issue: #396.
|
|
60
|
+
- Included implementation issue: #381.
|
|
61
|
+
- Parent product tracker: #309.
|
|
62
|
+
- Operating-loop tracker: #16.
|
|
63
|
+
- Baseline stable release: `v1.1.1`.
|
|
64
|
+
- Candidate package: `lossless-openclaw-orchestrator@1.1.2`.
|
|
65
|
+
- Expected git tag: `v1.1.2`.
|
|
66
|
+
- Expected GitHub Release:
|
|
67
|
+
`https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/releases/tag/v1.1.2`.
|
|
68
|
+
- Required pre-publish stable gates: `npm run check`, `npm pack --dry-run`,
|
|
69
|
+
strict scenario sweep, strict scorecard sweep, release preflight, release
|
|
70
|
+
bundle, release demo-status, release status, OpenClaw dogfood, and OpenClaw
|
|
71
|
+
tool-smoke for the exact stable candidate SHA.
|
|
72
|
+
- Required PR gates: GitHub CI, CodeQL, current-head review threads clear, and
|
|
73
|
+
any actionable review feedback fixed before merge.
|
|
74
|
+
- Required post-publish stable gates: npm `latest` view, git tag, GitHub
|
|
75
|
+
non-prerelease Release, `loo release finalization-status --expected-dist-tag
|
|
76
|
+
latest --expected-github-prerelease false --strict`, fresh npm `@latest`
|
|
77
|
+
published-smoke, and strict general-readiness before #396 closes.
|
|
78
|
+
- `approved_live_control_smoke_missing` remains the expected blocker when a
|
|
79
|
+
working-app claim is attempted without approved live-control smoke evidence
|
|
80
|
+
for the exact candidate SHA.
|
|
81
|
+
- Working-app status example:
|
|
82
|
+
`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`
|
|
83
|
+
- Reduced-scope status example:
|
|
84
|
+
`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`
|
|
85
|
+
- Post-publish finalization example:
|
|
86
|
+
`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`
|
|
87
|
+
|
|
88
|
+
## Explicit Non-Claims
|
|
89
|
+
|
|
90
|
+
No new live Codex control smoke is run by this release.
|
|
91
|
+
It does not run generic GUI mutation and does not run Codex GUI mutation.
|
|
92
|
+
No automatic gateway authorization.
|
|
93
|
+
No broad gateway scope approval. No prompt typing. No clicking. No arbitrary app control.
|
|
94
|
+
Claude Code remains an adapter stub, not an adapter-equivalence claim.
|
|
95
|
+
Bundle/status/finalization checks do not publish to npm and do not create a GitHub Release.
|
|
96
|
+
No release-grade enterprise security.
|
|
97
|
+
No Claude Code parity, no Notion/support-control/Stripe/Company Brain P1
|
|
98
|
+
adapter proof, no cloud sync, no unattended desktop takeover, and no
|
|
99
|
+
enterprise/customer-ready security is claimed.
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "lossless-openclaw-orchestrator",
|
|
3
3
|
"name": "Lossless OpenClaw Orchestrator",
|
|
4
4
|
"description": "Control and collaborate with local Codex sessions through OpenClaw using local indexing, bounded recall, and approval-gated controls.",
|
|
5
|
-
"version": "1.1.
|
|
5
|
+
"version": "1.1.2",
|
|
6
6
|
"kind": "tool",
|
|
7
7
|
"tools": {
|
|
8
8
|
"prefix": "loo_"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lossless-openclaw-orchestrator",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Index, search, and control local Codex sessions through OpenClaw with approval-gated safety; Claude Code remains an experimental adapter stub.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
@@ -63,6 +63,53 @@ export type LiveControlSmokeReport = {
|
|
|
63
63
|
rawPromptIncluded: false;
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
export type LiveControlSmokeFailureReport = {
|
|
67
|
+
kind: "loo_live_control_smoke_failure";
|
|
68
|
+
ok: false;
|
|
69
|
+
proofReady: false;
|
|
70
|
+
generatedAt: string;
|
|
71
|
+
blocker: string;
|
|
72
|
+
command: {
|
|
73
|
+
action: "send";
|
|
74
|
+
actionClass: "codex_live_control_send";
|
|
75
|
+
};
|
|
76
|
+
target: {
|
|
77
|
+
source: "ephemeral_thread_start" | "provided_thread" | "unknown";
|
|
78
|
+
refClass: "codex_thread" | "unknown";
|
|
79
|
+
ref: string | null;
|
|
80
|
+
};
|
|
81
|
+
dryRun: {
|
|
82
|
+
attempted: boolean;
|
|
83
|
+
approvalAuditId: string | null;
|
|
84
|
+
paramsHash: string | null;
|
|
85
|
+
messageHash: string | null;
|
|
86
|
+
live: false | null;
|
|
87
|
+
};
|
|
88
|
+
transport: {
|
|
89
|
+
class: "codex_app_server";
|
|
90
|
+
connection: "single_connection_sequence";
|
|
91
|
+
};
|
|
92
|
+
live: {
|
|
93
|
+
accepted: boolean;
|
|
94
|
+
approvalAuditId: string | null;
|
|
95
|
+
method: string | null;
|
|
96
|
+
completed: boolean;
|
|
97
|
+
status: string | null;
|
|
98
|
+
notificationMethods: string[];
|
|
99
|
+
approvalRequestCount: number;
|
|
100
|
+
serverRequestCount: number;
|
|
101
|
+
};
|
|
102
|
+
postActionRefresh: {
|
|
103
|
+
ran: false;
|
|
104
|
+
};
|
|
105
|
+
nextDiagnosticStep: string;
|
|
106
|
+
rawPromptIncluded: false;
|
|
107
|
+
rawTranscriptIncluded: false;
|
|
108
|
+
rawSecretIncluded: false;
|
|
109
|
+
screenshotIncluded: false;
|
|
110
|
+
sqliteIncluded: false;
|
|
111
|
+
};
|
|
112
|
+
|
|
66
113
|
export type LiveControlSmokeOptions = {
|
|
67
114
|
client: LiveControlSmokeClient;
|
|
68
115
|
audit: AuditStore;
|
|
@@ -133,11 +180,37 @@ export async function runLiveControlSmoke(options: LiveControlSmokeOptions): Pro
|
|
|
133
180
|
mkdirSync(options.evidenceDir, { recursive: true });
|
|
134
181
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
135
182
|
const message = options.message ?? DEFAULT_MESSAGE;
|
|
136
|
-
|
|
183
|
+
let targetState: { threadId: string | null; source: "ephemeral_thread_start" | "provided_thread" | "unknown" } = {
|
|
184
|
+
threadId: options.threadId ?? null,
|
|
185
|
+
source: options.threadId ? "provided_thread" : "unknown"
|
|
186
|
+
};
|
|
187
|
+
let dryRunState: { approvalAuditId: string; paramsHash: string; messageHash?: string; live: false } | null = null;
|
|
188
|
+
let liveState: {
|
|
189
|
+
accepted: boolean;
|
|
190
|
+
approvalAuditId: string | null;
|
|
191
|
+
method: string | null;
|
|
192
|
+
completed: boolean;
|
|
193
|
+
status: string | null;
|
|
194
|
+
notificationMethods: string[];
|
|
195
|
+
approvalRequestCount: number;
|
|
196
|
+
serverRequestCount: number;
|
|
197
|
+
} = {
|
|
198
|
+
accepted: false,
|
|
199
|
+
approvalAuditId: null,
|
|
200
|
+
method: null,
|
|
201
|
+
completed: false,
|
|
202
|
+
status: null,
|
|
203
|
+
notificationMethods: [],
|
|
204
|
+
approvalRequestCount: 0,
|
|
205
|
+
serverRequestCount: 0
|
|
206
|
+
};
|
|
207
|
+
let failed = false;
|
|
137
208
|
try {
|
|
209
|
+
await options.client.connect();
|
|
138
210
|
const target = options.threadId
|
|
139
211
|
? { threadId: options.threadId, source: "provided_thread" as const }
|
|
140
212
|
: { threadId: await startEphemeralThread(options.client, options.cwd), source: "ephemeral_thread_start" as const };
|
|
213
|
+
targetState = target;
|
|
141
214
|
const control = createCodexControl({
|
|
142
215
|
audit: options.audit,
|
|
143
216
|
client: {
|
|
@@ -156,6 +229,12 @@ export async function runLiveControlSmoke(options: LiveControlSmokeOptions): Pro
|
|
|
156
229
|
}
|
|
157
230
|
});
|
|
158
231
|
const dryRun = await control.sendMessage({ threadId: target.threadId, message, dryRun: true });
|
|
232
|
+
dryRunState = {
|
|
233
|
+
approvalAuditId: dryRun.approvalAuditId,
|
|
234
|
+
paramsHash: dryRun.paramsHash,
|
|
235
|
+
messageHash: dryRun.messageHash,
|
|
236
|
+
live: false
|
|
237
|
+
};
|
|
159
238
|
if (!dryRun.messageHash) throw new Error("dry-run did not produce a message hash");
|
|
160
239
|
const live = await control.sendMessage({
|
|
161
240
|
threadId: target.threadId,
|
|
@@ -163,9 +242,23 @@ export async function runLiveControlSmoke(options: LiveControlSmokeOptions): Pro
|
|
|
163
242
|
dryRun: false,
|
|
164
243
|
approvalAuditId: dryRun.approvalAuditId
|
|
165
244
|
});
|
|
245
|
+
liveState = {
|
|
246
|
+
...liveState,
|
|
247
|
+
accepted: true,
|
|
248
|
+
approvalAuditId: live.approvalAuditId,
|
|
249
|
+
method: live.method ?? null
|
|
250
|
+
};
|
|
166
251
|
const turnId = extractTurnId(live.response);
|
|
167
252
|
if (!turnId) throw new Error("live Codex control response did not include a turn id");
|
|
168
253
|
const completion = await options.client.waitForTurnCompletion({ threadId: target.threadId, turnId, timeoutMs });
|
|
254
|
+
liveState = {
|
|
255
|
+
...liveState,
|
|
256
|
+
completed: completion.completed,
|
|
257
|
+
status: completion.status,
|
|
258
|
+
notificationMethods: summarizeMethods(completion.notificationMethods),
|
|
259
|
+
approvalRequestCount: completion.approvalRequestCount,
|
|
260
|
+
serverRequestCount: completion.serverRequestCount
|
|
261
|
+
};
|
|
169
262
|
if (completion.approvalRequestCount > 0) {
|
|
170
263
|
throw new Error("live Codex control smoke observed a Codex approval request; harmless smoke must not require approvals");
|
|
171
264
|
}
|
|
@@ -218,8 +311,25 @@ export async function runLiveControlSmoke(options: LiveControlSmokeOptions): Pro
|
|
|
218
311
|
writeJson(proofPath, proof);
|
|
219
312
|
writeJson(reportPath, report);
|
|
220
313
|
return report;
|
|
314
|
+
} catch (error) {
|
|
315
|
+
failed = true;
|
|
316
|
+
try {
|
|
317
|
+
writeFailureReport(options, {
|
|
318
|
+
error,
|
|
319
|
+
target: targetState,
|
|
320
|
+
dryRun: dryRunState,
|
|
321
|
+
live: liveState
|
|
322
|
+
});
|
|
323
|
+
} catch {
|
|
324
|
+
// Best-effort evidence: never let report-writing failures mask the original smoke failure.
|
|
325
|
+
}
|
|
326
|
+
throw error;
|
|
221
327
|
} finally {
|
|
222
|
-
|
|
328
|
+
try {
|
|
329
|
+
await options.client.close();
|
|
330
|
+
} catch (closeError) {
|
|
331
|
+
if (!failed) throw closeError;
|
|
332
|
+
}
|
|
223
333
|
}
|
|
224
334
|
}
|
|
225
335
|
|
|
@@ -283,3 +393,96 @@ function summarizeMethods(methods: string[]): string[] {
|
|
|
283
393
|
function writeJson(path: string, value: unknown): void {
|
|
284
394
|
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
285
395
|
}
|
|
396
|
+
|
|
397
|
+
function writeFailureReport(
|
|
398
|
+
options: LiveControlSmokeOptions,
|
|
399
|
+
state: {
|
|
400
|
+
error: unknown;
|
|
401
|
+
target: { threadId: string | null; source: "ephemeral_thread_start" | "provided_thread" | "unknown" };
|
|
402
|
+
dryRun: { approvalAuditId: string; paramsHash: string; messageHash?: string; live: false } | null;
|
|
403
|
+
live: {
|
|
404
|
+
accepted: boolean;
|
|
405
|
+
approvalAuditId: string | null;
|
|
406
|
+
method: string | null;
|
|
407
|
+
completed: boolean;
|
|
408
|
+
status: string | null;
|
|
409
|
+
notificationMethods: string[];
|
|
410
|
+
approvalRequestCount: number;
|
|
411
|
+
serverRequestCount: number;
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
): void {
|
|
415
|
+
const blocker = classifyFailureBlocker(state.error);
|
|
416
|
+
const report: LiveControlSmokeFailureReport = {
|
|
417
|
+
kind: "loo_live_control_smoke_failure",
|
|
418
|
+
ok: false,
|
|
419
|
+
proofReady: false,
|
|
420
|
+
generatedAt: options.now ?? new Date().toISOString(),
|
|
421
|
+
blocker,
|
|
422
|
+
command: {
|
|
423
|
+
action: "send",
|
|
424
|
+
actionClass: "codex_live_control_send"
|
|
425
|
+
},
|
|
426
|
+
target: {
|
|
427
|
+
source: state.target.source,
|
|
428
|
+
refClass: state.target.threadId ? "codex_thread" : "unknown",
|
|
429
|
+
ref: state.target.threadId ? `codex_thread:${state.target.threadId}` : null
|
|
430
|
+
},
|
|
431
|
+
dryRun: {
|
|
432
|
+
attempted: state.dryRun !== null,
|
|
433
|
+
approvalAuditId: state.dryRun?.approvalAuditId ?? null,
|
|
434
|
+
paramsHash: state.dryRun?.paramsHash ?? null,
|
|
435
|
+
messageHash: state.dryRun?.messageHash ?? null,
|
|
436
|
+
live: state.dryRun ? false : null
|
|
437
|
+
},
|
|
438
|
+
transport: {
|
|
439
|
+
class: "codex_app_server",
|
|
440
|
+
connection: "single_connection_sequence"
|
|
441
|
+
},
|
|
442
|
+
live: state.live,
|
|
443
|
+
postActionRefresh: {
|
|
444
|
+
ran: false
|
|
445
|
+
},
|
|
446
|
+
nextDiagnosticStep: nextDiagnosticStep(blocker),
|
|
447
|
+
rawPromptIncluded: false,
|
|
448
|
+
rawTranscriptIncluded: false,
|
|
449
|
+
rawSecretIncluded: false,
|
|
450
|
+
screenshotIncluded: false,
|
|
451
|
+
sqliteIncluded: false
|
|
452
|
+
};
|
|
453
|
+
writeJson(join(options.evidenceDir, "live-control-smoke-failure-report.json"), report);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function classifyFailureBlocker(error: unknown): string {
|
|
457
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
458
|
+
if (/connect|handshake|spawn|ENOENT|codex.*not found|not connected/i.test(message)) {
|
|
459
|
+
return "codex_app_server_setup_failed";
|
|
460
|
+
}
|
|
461
|
+
if (/thread(?:\/resume| not found)|control sequence step failed:\s*thread\/resume/i.test(message)) {
|
|
462
|
+
return "same_connection_resume_load_diagnostics_required";
|
|
463
|
+
}
|
|
464
|
+
if (/approval request/i.test(message)) return "codex_approval_request_observed";
|
|
465
|
+
if (/server request/i.test(message)) return "codex_server_request_observed";
|
|
466
|
+
if (/turn id/i.test(message)) return "live_control_turn_id_missing";
|
|
467
|
+
if (/did not complete cleanly|timeout/i.test(message)) return "live_control_smoke_not_completed_cleanly";
|
|
468
|
+
return "live_control_smoke_failed";
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function nextDiagnosticStep(blocker: string): string {
|
|
472
|
+
switch (blocker) {
|
|
473
|
+
case "codex_app_server_setup_failed":
|
|
474
|
+
return "Check Codex app-server command availability, stdio handshake, and local transport setup before retrying live control.";
|
|
475
|
+
case "same_connection_resume_load_diagnostics_required":
|
|
476
|
+
return "Run same-connection resume/load diagnostics for the selected thread before retrying live control.";
|
|
477
|
+
case "codex_approval_request_observed":
|
|
478
|
+
return "Inspect why the harmless smoke triggered a Codex approval request; do not treat this as approved live-control proof.";
|
|
479
|
+
case "codex_server_request_observed":
|
|
480
|
+
return "Inspect unexpected server requests from the app-server smoke; do not retry live control until the request class is understood.";
|
|
481
|
+
case "live_control_turn_id_missing":
|
|
482
|
+
return "Inspect the app-server turn/start response shape and protocol drift before retrying.";
|
|
483
|
+
case "live_control_smoke_not_completed_cleanly":
|
|
484
|
+
return "Inspect turn completion notifications, status, and timeout behavior before retrying live control.";
|
|
485
|
+
default:
|
|
486
|
+
return "Inspect the public-safe failure report and route a focused issue before retrying live control.";
|
|
487
|
+
}
|
|
488
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "lossless-openclaw-orchestrator",
|
|
3
3
|
"name": "Lossless OpenClaw Orchestrator",
|
|
4
4
|
"description": "Control and collaborate with local Codex sessions through OpenClaw using local indexing, bounded recall, and approval-gated controls.",
|
|
5
|
-
"version": "1.1.
|
|
5
|
+
"version": "1.1.2",
|
|
6
6
|
"kind": "tool",
|
|
7
7
|
"tools": {
|
|
8
8
|
"prefix": "loo_"
|