lossless-openclaw-orchestrator 1.1.1 → 1.1.3
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/index.js +169 -0
- package/dist/packages/cli/src/live-control-smoke.js +137 -2
- package/dist/packages/cli/src/runtime-issue-packet.js +357 -0
- package/dist/packages/cli/src/runtime-sweep-summary.js +267 -0
- package/docs/RELEASE_NOTES_1.1.2.md +99 -0
- package/docs/RELEASE_NOTES_1.1.3.md +113 -0
- package/evals/scenarios/v1/failed-runtime-proof-issue-packet.json +52 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/packages/cli/src/index.ts +166 -0
- package/packages/cli/src/live-control-smoke.ts +205 -2
- package/packages/cli/src/runtime-issue-packet.ts +448 -0
- package/packages/cli/src/runtime-sweep-summary.ts +355 -0
- 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.3.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.
|
|
@@ -16,7 +16,9 @@ import { runOpenClawGatewayLiveControlSmoke } from "./openclaw-live-control-smok
|
|
|
16
16
|
import { runOpenClawPostActionRefreshSmoke } from "./openclaw-post-action-refresh-smoke.js";
|
|
17
17
|
import { createScorecardSweep } from "./scorecard-sweep.js";
|
|
18
18
|
import { createScenarioSweep } from "./scenario-sweep.js";
|
|
19
|
+
import { createRuntimeProofIssuePacket } from "./runtime-issue-packet.js";
|
|
19
20
|
import { createOnboardingStatusReport, writeOnboardingStatusReport } from "./onboarding-status.js";
|
|
21
|
+
import { createRuntimeSweepSummary } from "./runtime-sweep-summary.js";
|
|
20
22
|
import { normalizeReleaseClaimScope } from "./release-claim-scope.js";
|
|
21
23
|
import { AppServerLiveControlSmokeClient, runLiveControlSmoke } from "./live-control-smoke.js";
|
|
22
24
|
import { createLocalMacSearchUiShell, REQUIRED_LOCAL_MAC_SEARCH_UI_TOOLS, sampleLocalMacSearchUiShell, writeLocalMacSearchUiEvidence } from "../../local-mac-ui/src/shell.js";
|
|
@@ -380,6 +382,18 @@ async function main() {
|
|
|
380
382
|
process.exitCode = 1;
|
|
381
383
|
return;
|
|
382
384
|
}
|
|
385
|
+
if (command === "runtime" && args[0] === "sweep-summary") {
|
|
386
|
+
if (hasHelpFlag(args.slice(1))) {
|
|
387
|
+
printRuntimeSweepSummaryHelp();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const parsed = parseRuntimeSweepSummaryArgs(args.slice(1));
|
|
391
|
+
const report = createRuntimeSweepSummary(parsed);
|
|
392
|
+
console.log(JSON.stringify(report, null, 2));
|
|
393
|
+
if (parsed.strict && (!report.summaryReady || report.claimBoundary.supportedClaimScope === "none"))
|
|
394
|
+
process.exitCode = 1;
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
383
397
|
if (command === "ui" && args[0] === "local-mac-search") {
|
|
384
398
|
if (hasHelpFlag(args.slice(1))) {
|
|
385
399
|
printLocalMacSearchUiHelp();
|
|
@@ -456,6 +470,25 @@ async function main() {
|
|
|
456
470
|
process.exitCode = 1;
|
|
457
471
|
return;
|
|
458
472
|
}
|
|
473
|
+
if (command === "runtime" && args[0] === "issue-packet") {
|
|
474
|
+
if (hasHelpFlag(args.slice(1))) {
|
|
475
|
+
printRuntimeIssuePacketHelp();
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const parsed = parseRuntimeIssuePacketArgs(args.slice(1));
|
|
479
|
+
const report = createRuntimeProofIssuePacket({
|
|
480
|
+
evidenceDir: parsed.evidenceDir,
|
|
481
|
+
failureReport: parsed.failureReport,
|
|
482
|
+
parentIssue: parsed.parentIssue,
|
|
483
|
+
operatingLoopIssue: parsed.operatingLoopIssue,
|
|
484
|
+
milestone: parsed.milestone,
|
|
485
|
+
now: parsed.now
|
|
486
|
+
});
|
|
487
|
+
console.log(JSON.stringify(report, null, 2));
|
|
488
|
+
if (parsed.strict && !report.issuePacketReady)
|
|
489
|
+
process.exitCode = 1;
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
459
492
|
if (command === "release" && args[0] === "preflight") {
|
|
460
493
|
if (hasHelpFlag(args.slice(1))) {
|
|
461
494
|
printReleasePreflightHelp();
|
|
@@ -750,9 +783,11 @@ function mainUsageText() {
|
|
|
750
783
|
" loo openclaw live-control-smoke --evidence-dir path --thread-id id [--openclaw-bin path] [--dev] [--profile name] [--gateway-url ws://127.0.0.1:port] [--token token] [--gateway-timeout-ms ms] [--session-key key] [--message text] [--strict]",
|
|
751
784
|
" loo openclaw post-action-refresh-smoke --evidence-dir path --thread-id id --live-proof-report path [--openclaw-bin path] [--dev] [--profile name] [--gateway-url ws://127.0.0.1:port] [--token token] [--gateway-timeout-ms ms] [--session-key key] [--query text] [--expand-profile metadata|brief|evidence] [--token-budget n] [--strict]",
|
|
752
785
|
" loo scorecards sweep --evidence-dir path [--scorecard-dir path] [--claim-scope codex-live-control|codex-read-search-expand-dry-run|codex-working-app-proof] [--strict]",
|
|
786
|
+
" loo runtime sweep-summary --evidence-dir path --dry-run-scenarios path --runtime-scenarios path --scorecard-sweep path --published-smoke path [--runtime-proof-dir path] [--now iso] [--strict]",
|
|
753
787
|
" loo ui local-mac-search --evidence-dir path [--sample] [--strict]",
|
|
754
788
|
" loo eval retrieval --scenario-file path [--evidence-path path] [--strict]",
|
|
755
789
|
" loo eval scenarios --evidence-dir path [--scenario-dir path] [--runtime-proof-dir path] [--strict]",
|
|
790
|
+
" loo runtime issue-packet --evidence-dir path --failure-report path [--parent-issue #n] [--operating-loop #n] [--milestone name] [--now iso] [--strict]",
|
|
756
791
|
" loo release preflight [--evidence-dir path] [--claim-scope codex-live-control|codex-read-search-expand-dry-run|codex-working-app-proof] [--approved-live-control-evidence path] [--runtime-proof-dir path] [--strict]",
|
|
757
792
|
" loo release bundle --evidence-dir path [--claim-scope codex-live-control|codex-read-search-expand-dry-run|codex-working-app-proof] [--approved-live-control-evidence path] [--runtime-proof-dir path] [--strict]",
|
|
758
793
|
" loo release status --evidence-dir path --candidate-sha sha [--claim-scope codex-live-control|codex-read-search-expand-dry-run|codex-working-app-proof] [--approved-live-control-evidence path] [--runtime-proof-dir path] [--npm-publish-approval-evidence path] [--github-release-approval-evidence path] [--github-ci-evidence path] [--codeql-evidence path] [--desktop-gui-required --desktop-gui-approval-evidence path] [--now iso] [--strict]",
|
|
@@ -906,6 +941,48 @@ function printScenarioSweepHelp() {
|
|
|
906
941
|
" It does not read raw Codex transcripts, run live Codex control, mutate a desktop GUI, publish npm, or create a GitHub Release."
|
|
907
942
|
].join("\n"));
|
|
908
943
|
}
|
|
944
|
+
function printRuntimeIssuePacketHelp() {
|
|
945
|
+
console.log([
|
|
946
|
+
"Usage:",
|
|
947
|
+
" loo runtime issue-packet --evidence-dir path --failure-report path [--parent-issue #n] [--operating-loop #n] [--milestone name] [--now iso] [--strict]",
|
|
948
|
+
"",
|
|
949
|
+
"Writes a public-safe issue-ready handoff packet from a failed runtime proof or scenario sweep report.",
|
|
950
|
+
"",
|
|
951
|
+
"Required:",
|
|
952
|
+
" --evidence-dir is required and receives runtime-proof-issue-packet.json.",
|
|
953
|
+
" --failure-report points to the failed public-safe runtime proof, smoke, or scenario-sweep JSON report.",
|
|
954
|
+
"",
|
|
955
|
+
"Strict mode:",
|
|
956
|
+
" --strict exits non-zero when the failure report is missing, malformed, lacks blocker codes, or when packet redaction fails.",
|
|
957
|
+
"",
|
|
958
|
+
"Safety boundary:",
|
|
959
|
+
" The command never runs gh issue create and never writes to GitHub.",
|
|
960
|
+
" It records only blocker codes, scenario ids, duplicate-check query, acceptance criteria, proof boundary, and redaction categories.",
|
|
961
|
+
" It does not read raw Codex transcripts, run live Codex control, mutate a GUI, publish npm, or create a GitHub Release."
|
|
962
|
+
].join("\n"));
|
|
963
|
+
}
|
|
964
|
+
function printRuntimeSweepSummaryHelp() {
|
|
965
|
+
console.log([
|
|
966
|
+
"Usage:",
|
|
967
|
+
" loo runtime sweep-summary --evidence-dir path --dry-run-scenarios path --runtime-scenarios path --scorecard-sweep path --published-smoke path [--runtime-proof-dir path] [--now iso] [--strict]",
|
|
968
|
+
"",
|
|
969
|
+
"Writes a public-safe summary that separates dry-run scenario readiness from missing runtime proof markers.",
|
|
970
|
+
"",
|
|
971
|
+
"Required:",
|
|
972
|
+
" --dry-run-scenarios points to the v1 dry-run scenario sweep report.",
|
|
973
|
+
" --runtime-scenarios points to the v1.1 runtime-required scenario sweep report.",
|
|
974
|
+
" --scorecard-sweep points to the working-app scorecard sweep report.",
|
|
975
|
+
" --published-smoke points to the published-package or gateway setup smoke report.",
|
|
976
|
+
"",
|
|
977
|
+
"Strict mode:",
|
|
978
|
+
" --strict exits non-zero when the summary itself cannot be produced safely or no claim scope is supported.",
|
|
979
|
+
" Missing runtime markers remain claim-boundary blockers, not packet-generation failures.",
|
|
980
|
+
"",
|
|
981
|
+
"Safety boundary:",
|
|
982
|
+
" The command consumes public-safe reports only.",
|
|
983
|
+
" It does not read raw Codex transcripts, run live Codex control, mutate a GUI, publish npm, create tags, or create a GitHub Release."
|
|
984
|
+
].join("\n"));
|
|
985
|
+
}
|
|
909
986
|
function printOnboardingStatusHelp() {
|
|
910
987
|
console.log([
|
|
911
988
|
"Usage:",
|
|
@@ -1640,6 +1717,47 @@ function parseScenarioSweepArgs(input) {
|
|
|
1640
1717
|
throw new Error("eval scenarios requires --evidence-dir");
|
|
1641
1718
|
return { evidenceDir, scenarioDir, runtimeProofDir, scenarioIds: scenarioIds.length ? scenarioIds : undefined, strict };
|
|
1642
1719
|
}
|
|
1720
|
+
function parseRuntimeIssuePacketArgs(input) {
|
|
1721
|
+
let evidenceDir = "";
|
|
1722
|
+
let failureReport = "";
|
|
1723
|
+
let parentIssue;
|
|
1724
|
+
let operatingLoopIssue;
|
|
1725
|
+
let milestone;
|
|
1726
|
+
let now;
|
|
1727
|
+
let strict = false;
|
|
1728
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
1729
|
+
const arg = input[index];
|
|
1730
|
+
if (arg === "--evidence-dir") {
|
|
1731
|
+
evidenceDir = requireOptionValue(input[++index], arg);
|
|
1732
|
+
}
|
|
1733
|
+
else if (arg === "--failure-report") {
|
|
1734
|
+
failureReport = requireOptionValue(input[++index], arg);
|
|
1735
|
+
}
|
|
1736
|
+
else if (arg === "--parent-issue") {
|
|
1737
|
+
parentIssue = requireOptionValue(input[++index], arg);
|
|
1738
|
+
}
|
|
1739
|
+
else if (arg === "--operating-loop") {
|
|
1740
|
+
operatingLoopIssue = requireOptionValue(input[++index], arg);
|
|
1741
|
+
}
|
|
1742
|
+
else if (arg === "--milestone") {
|
|
1743
|
+
milestone = requireOptionValue(input[++index], arg);
|
|
1744
|
+
}
|
|
1745
|
+
else if (arg === "--now") {
|
|
1746
|
+
now = requireOptionValue(input[++index], arg);
|
|
1747
|
+
}
|
|
1748
|
+
else if (arg === "--strict") {
|
|
1749
|
+
strict = true;
|
|
1750
|
+
}
|
|
1751
|
+
else {
|
|
1752
|
+
throw new Error(`Unknown runtime issue-packet option: ${arg}`);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
if (!evidenceDir)
|
|
1756
|
+
throw new Error("runtime issue-packet requires --evidence-dir");
|
|
1757
|
+
if (!failureReport)
|
|
1758
|
+
throw new Error("runtime issue-packet requires --failure-report");
|
|
1759
|
+
return { evidenceDir, failureReport, parentIssue, operatingLoopIssue, milestone, now, strict };
|
|
1760
|
+
}
|
|
1643
1761
|
function readRetrievalScenarioFile(path) {
|
|
1644
1762
|
const scenarioPath = resolve(path);
|
|
1645
1763
|
if (!existsSync(scenarioPath))
|
|
@@ -2066,6 +2184,57 @@ function parseScorecardSweepArgs(input) {
|
|
|
2066
2184
|
throw new Error("scorecards sweep requires --evidence-dir");
|
|
2067
2185
|
return { evidenceDir, scorecardDir, claimScope, strict };
|
|
2068
2186
|
}
|
|
2187
|
+
function parseRuntimeSweepSummaryArgs(input) {
|
|
2188
|
+
let evidenceDir;
|
|
2189
|
+
let dryRunScenarios;
|
|
2190
|
+
let runtimeScenarios;
|
|
2191
|
+
let scorecardSweep;
|
|
2192
|
+
let publishedSmoke;
|
|
2193
|
+
let runtimeProofDir;
|
|
2194
|
+
let now;
|
|
2195
|
+
let strict = false;
|
|
2196
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
2197
|
+
const arg = input[index];
|
|
2198
|
+
if (arg === "--evidence-dir") {
|
|
2199
|
+
evidenceDir = requireOptionValue(input[++index], arg);
|
|
2200
|
+
}
|
|
2201
|
+
else if (arg === "--dry-run-scenarios") {
|
|
2202
|
+
dryRunScenarios = requireOptionValue(input[++index], arg);
|
|
2203
|
+
}
|
|
2204
|
+
else if (arg === "--runtime-scenarios") {
|
|
2205
|
+
runtimeScenarios = requireOptionValue(input[++index], arg);
|
|
2206
|
+
}
|
|
2207
|
+
else if (arg === "--scorecard-sweep") {
|
|
2208
|
+
scorecardSweep = requireOptionValue(input[++index], arg);
|
|
2209
|
+
}
|
|
2210
|
+
else if (arg === "--published-smoke") {
|
|
2211
|
+
publishedSmoke = requireOptionValue(input[++index], arg);
|
|
2212
|
+
}
|
|
2213
|
+
else if (arg === "--runtime-proof-dir") {
|
|
2214
|
+
runtimeProofDir = requireOptionValue(input[++index], arg);
|
|
2215
|
+
}
|
|
2216
|
+
else if (arg === "--now") {
|
|
2217
|
+
now = requireOptionValue(input[++index], arg);
|
|
2218
|
+
}
|
|
2219
|
+
else if (arg === "--strict") {
|
|
2220
|
+
strict = true;
|
|
2221
|
+
}
|
|
2222
|
+
else {
|
|
2223
|
+
throw new Error(`Unknown runtime sweep-summary option: ${arg}`);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
if (!evidenceDir)
|
|
2227
|
+
throw new Error("runtime sweep-summary requires --evidence-dir");
|
|
2228
|
+
if (!dryRunScenarios)
|
|
2229
|
+
throw new Error("runtime sweep-summary requires --dry-run-scenarios");
|
|
2230
|
+
if (!runtimeScenarios)
|
|
2231
|
+
throw new Error("runtime sweep-summary requires --runtime-scenarios");
|
|
2232
|
+
if (!scorecardSweep)
|
|
2233
|
+
throw new Error("runtime sweep-summary requires --scorecard-sweep");
|
|
2234
|
+
if (!publishedSmoke)
|
|
2235
|
+
throw new Error("runtime sweep-summary requires --published-smoke");
|
|
2236
|
+
return { evidenceDir, dryRunScenarios, runtimeScenarios, scorecardSweep, publishedSmoke, runtimeProofDir, now, strict };
|
|
2237
|
+
}
|
|
2069
2238
|
function parsePositiveInteger(value, name, max) {
|
|
2070
2239
|
const parsed = Number(value);
|
|
2071
2240
|
if (!Number.isInteger(parsed) || parsed < 1 || (max !== undefined && parsed > max)) {
|
|
@@ -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
|
+
}
|