lossless-openclaw-orchestrator 1.1.3 → 1.1.4

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.3.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.4.md) · [1.0 Notes](docs/RELEASE_NOTES_1.0.0.md) · [License](LICENSE)
18
18
 
19
19
  ## Why It Matters
20
20
 
@@ -780,7 +780,7 @@ function mainUsageText() {
780
780
  " loo openclaw dogfood [--dev] [--profile name] [--install-source path] [--link] [--force-install] [--evidence-path path] [--strict]",
781
781
  " loo openclaw tool-smoke [--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] [--thread-id id] [--expand-profile metadata|brief|evidence] [--token-budget n] [--required-tool name] [--evidence-path path] [--strict]",
782
782
  " loo openclaw published-smoke --evidence-dir path --dogfood-report path --tool-smoke-report path [--configured-tool-smoke-report path] [--npm-install-diagnostic-report path] [--registry-version version] [--registry-beta-version version] [--root path] [--now iso] [--strict]",
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]",
783
+ " loo openclaw live-control-smoke --evidence-dir path --thread-id id [--action send|resume] [--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]",
784
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]",
785
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
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]",
@@ -1172,9 +1172,9 @@ function printLiveControlSmokeHelp() {
1172
1172
  function printOpenClawLiveControlSmokeHelp() {
1173
1173
  console.log([
1174
1174
  "Usage:",
1175
- " 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]",
1175
+ " loo openclaw live-control-smoke --evidence-dir path --thread-id id [--action send|resume] [--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]",
1176
1176
  "",
1177
- "Runs one approval-gated live Codex send through the installed OpenClaw gateway tools.invoke path.",
1177
+ "Runs one approval-gated live Codex send or resume through the installed OpenClaw gateway tools.invoke path.",
1178
1178
  "",
1179
1179
  "Outputs:",
1180
1180
  " openclaw-gateway-live-codex-v1-1.runtime-proof.json",
@@ -1182,7 +1182,7 @@ function printOpenClawLiveControlSmokeHelp() {
1182
1182
  "",
1183
1183
  "Safety boundary:",
1184
1184
  " The command requires an explicit --thread-id target.",
1185
- " It invokes loo_codex_control_dry_run first, then uses the matching approval_audit_id for loo_codex_send_message with dry_run:false.",
1185
+ " It invokes loo_codex_control_dry_run first, then uses the matching approval_audit_id for the selected live tool with dry_run:false.",
1186
1186
  " It reads loo_audit_tail to prove matching dry-run/live audit metadata.",
1187
1187
  " Evidence contains refs, audit ids, hashes, tool names, and status only.",
1188
1188
  " It does not write raw prompt text, raw transcript spans, screenshots, SQLite DBs, tokens, or credentials.",
@@ -1999,6 +1999,9 @@ function parseOpenClawLiveControlSmokeArgs(input) {
1999
1999
  else if (arg === "--thread-id") {
2000
2000
  parsed.threadId = requireOptionValue(input[++index], arg);
2001
2001
  }
2002
+ else if (arg === "--action") {
2003
+ parsed.action = parseOpenClawLiveControlAction(requireOptionValue(input[++index], arg));
2004
+ }
2002
2005
  else if (arg === "--message") {
2003
2006
  parsed.message = requireOptionValue(input[++index], arg);
2004
2007
  }
@@ -2018,6 +2021,11 @@ function parseOpenClawLiveControlSmokeArgs(input) {
2018
2021
  throw new Error("openclaw live-control-smoke requires --thread-id");
2019
2022
  return parsed;
2020
2023
  }
2024
+ function parseOpenClawLiveControlAction(value) {
2025
+ if (value === "send" || value === "resume")
2026
+ return value;
2027
+ throw new Error("--action must be send or resume");
2028
+ }
2021
2029
  function parseOpenClawPostActionRefreshSmokeArgs(input) {
2022
2030
  const parsed = {};
2023
2031
  for (let index = 0; index < input.length; index += 1) {
@@ -4,7 +4,6 @@ import { basename, dirname, join } from "node:path";
4
4
  const SCENARIO_ID = "openclaw-gateway-live-codex-v1-1";
5
5
  const ACCEPTED_LIVE_TURN_STATUSES = new Set(["accepted", "completed", "in_progress", "pending", "queued", "running"]);
6
6
  const DEFAULT_MESSAGE = "LCO OpenClaw gateway live-control smoke. Reply with exactly: LCO gateway live smoke acknowledged. Do not run commands, edit files, or use tools.";
7
- const REQUIRED_TOOLS = ["loo_codex_control_dry_run", "loo_codex_send_message", "loo_audit_tail"];
8
7
  const PRIVATE_DATA_EXCLUSIONS = [
9
8
  "raw OpenClaw gateway stdout/stderr",
10
9
  "raw tool output",
@@ -32,48 +31,42 @@ export function runOpenClawGatewayLiveControlSmoke(options) {
32
31
  ];
33
32
  const callOptions = { timeoutMs: gatewayTimeoutMs, env: options.token ? { OPENCLAW_GATEWAY_TOKEN: options.token } : undefined };
34
33
  const sessionKey = options.sessionKey || "agent:main:lco-live-control-smoke";
34
+ const action = normalizeAction(options.action);
35
+ const liveToolName = liveToolForAction(action);
36
+ const requiredTools = ["loo_codex_control_dry_run", liveToolName, "loo_audit_tail"];
35
37
  const message = options.message ?? DEFAULT_MESSAGE;
36
38
  const targetRef = `codex_thread:${options.threadId}`;
37
39
  const blockers = [];
38
40
  const catalog = callGatewayJson(openclawBin, baseArgs, gatewayOptions, "tools.catalog", {}, callOptions);
39
41
  const catalogTools = catalog.status === 0 && catalog.parsed !== undefined ? extractCatalogToolNames(unwrapGatewayPayload(catalog.parsed)) : [];
40
42
  blockers.push(...gatewayCallBlockers(catalog, "openclaw_live_catalog_failed"));
41
- blockers.push(...REQUIRED_TOOLS.filter((tool) => !catalogTools.includes(tool)).map((tool) => `openclaw_live_catalog_missing_tool:${tool}`));
43
+ blockers.push(...requiredTools.filter((tool) => !catalogTools.includes(tool)).map((tool) => `openclaw_live_catalog_missing_tool:${tool}`));
42
44
  const dryRun = blockers.length === 0
43
45
  ? callGatewayJson(openclawBin, baseArgs, gatewayOptions, "tools.invoke", {
44
46
  name: "loo_codex_control_dry_run",
45
- args: {
46
- action: "send",
47
- thread_id: options.threadId,
48
- message
49
- },
47
+ args: liveDryRunArgs(action, options.threadId, message),
50
48
  sessionKey,
51
49
  confirm: false,
52
- idempotencyKey: `loo-live-smoke-dry-run-${options.threadId}`
50
+ idempotencyKey: `loo-live-smoke-dry-run-${action}-${options.threadId}`
53
51
  }, callOptions)
54
52
  : null;
55
53
  blockers.push(...(dryRun ? gatewayCallBlockers(dryRun, "openclaw_live_dry_run_failed") : []));
56
54
  const dryRunSummary = summarizeControl(dryRun);
57
- if (dryRun && !validDryRun(dryRunSummary))
55
+ if (dryRun && !validDryRun(action, dryRunSummary))
58
56
  blockers.push("openclaw_live_dry_run_not_proven");
59
57
  const live = blockers.length === 0
60
58
  ? callGatewayJson(openclawBin, baseArgs, gatewayOptions, "tools.invoke", {
61
- name: "loo_codex_send_message",
62
- args: {
63
- thread_id: options.threadId,
64
- message,
65
- dry_run: false,
66
- approval_audit_id: dryRunSummary.approvalAuditId
67
- },
59
+ name: liveToolName,
60
+ args: liveArgs(action, options.threadId, message, dryRunSummary.approvalAuditId),
68
61
  sessionKey,
69
62
  confirm: false,
70
- idempotencyKey: `loo-live-smoke-send-${options.threadId}-${dryRunSummary.approvalAuditId}`
63
+ idempotencyKey: `loo-live-smoke-${action}-${options.threadId}-${dryRunSummary.approvalAuditId}`
71
64
  }, callOptions)
72
65
  : null;
73
- blockers.push(...(live ? gatewayCallBlockers(live, "openclaw_live_send_failed") : []));
66
+ blockers.push(...(live ? gatewayCallBlockers(live, "openclaw_live_control_failed") : []));
74
67
  const liveSummary = summarizeControl(live);
75
- if (live && !validLive(liveSummary))
76
- blockers.push("openclaw_live_send_not_proven");
68
+ if (live && !validLive(action, liveSummary))
69
+ blockers.push(action === "resume" ? "openclaw_live_resume_not_proven" : "openclaw_live_send_not_proven");
77
70
  if (live && dryRunSummary.paramsHash && liveSummary.paramsHash && liveSummary.paramsHash !== dryRunSummary.paramsHash)
78
71
  blockers.push("openclaw_live_params_hash_mismatch");
79
72
  if (live && dryRunSummary.messageHash && liveSummary.messageHash && liveSummary.messageHash !== dryRunSummary.messageHash)
@@ -89,7 +82,7 @@ export function runOpenClawGatewayLiveControlSmoke(options) {
89
82
  args: { limit: 20 },
90
83
  sessionKey,
91
84
  confirm: false,
92
- idempotencyKey: `loo-live-smoke-audit-tail-${options.threadId}`
85
+ idempotencyKey: `loo-live-smoke-audit-tail-${action}-${options.threadId}`
93
86
  }, callOptions)
94
87
  : null;
95
88
  blockers.push(...(auditTail ? gatewayCallBlockers(auditTail, "openclaw_live_audit_tail_failed") : []));
@@ -110,7 +103,8 @@ export function runOpenClawGatewayLiveControlSmoke(options) {
110
103
  publicSafe: true,
111
104
  generatedAt: options.now ?? new Date().toISOString(),
112
105
  command: `${sanitizeCommandBinary(openclawBin)} ${[...baseArgs, "gateway", "call", "tools.invoke", "--json", "--params", "<redacted>"].join(" ")}`,
113
- requiredTools: REQUIRED_TOOLS,
106
+ action,
107
+ requiredTools,
114
108
  targetRef,
115
109
  dryRun: {
116
110
  approvalAuditId: dryRunSummary.approvalAuditId,
@@ -139,7 +133,7 @@ export function runOpenClawGatewayLiveControlSmoke(options) {
139
133
  rawTranscriptRead: false
140
134
  },
141
135
  privateDataExclusions: PRIVATE_DATA_EXCLUSIONS,
142
- proofBoundary: "This proves one approved harmless live Codex send through the installed OpenClaw gateway path only; it does not prove unattended live control, broad gateway scope approval, GUI mutation, Claude parity, or bypassed Codex approvals.",
136
+ proofBoundary: "This proves one approved harmless live Codex send/resume through the installed OpenClaw gateway path only; it does not prove unattended live control, broad gateway scope approval, GUI mutation, Claude parity, or bypassed Codex approvals.",
143
137
  nextAction: uniqueBlockers.length === 0
144
138
  ? "Run the v1.1 runtime scenario sweep against this runtime-proof directory, then continue #159 post-action refresh proof."
145
139
  : "Resolve the listed gateway live-control blockers before claiming #158 runtime proof."
@@ -170,19 +164,52 @@ function runtimeProofForReport(report) {
170
164
  raw_prompt_chars: 0
171
165
  };
172
166
  }
173
- function validDryRun(summary) {
167
+ function liveDryRunArgs(action, threadId, message) {
168
+ return action === "send"
169
+ ? {
170
+ action,
171
+ thread_id: threadId,
172
+ message
173
+ }
174
+ : {
175
+ action,
176
+ thread_id: threadId
177
+ };
178
+ }
179
+ function liveArgs(action, threadId, message, approvalAuditId) {
180
+ const common = {
181
+ thread_id: threadId,
182
+ dry_run: false,
183
+ approval_audit_id: approvalAuditId
184
+ };
185
+ return action === "send" ? { ...common, message } : common;
186
+ }
187
+ function liveToolForAction(action) {
188
+ return action === "send" ? "loo_codex_send_message" : "loo_codex_resume_thread";
189
+ }
190
+ function normalizeAction(action) {
191
+ if (action === undefined || action === "send")
192
+ return "send";
193
+ if (action === "resume")
194
+ return "resume";
195
+ throw new Error("action must be send or resume");
196
+ }
197
+ function validDryRun(action, summary) {
174
198
  return summary.live === false
175
199
  && safeAuditId(summary.approvalAuditId)
176
200
  && safeHash(summary.paramsHash)
177
- && safeHash(summary.messageHash);
201
+ && (action !== "send" || safeHash(summary.messageHash));
178
202
  }
179
- function validLive(summary) {
203
+ function validLive(action, summary) {
204
+ const actionAccepted = action === "resume"
205
+ ? summary.method === "thread/resume"
206
+ : liveTurnStatusProvesSendAccepted(summary.turnStatus);
180
207
  return summary.live === true
181
208
  && safeAuditId(summary.approvalAuditId)
182
209
  && safeHash(summary.paramsHash)
183
- && safeHash(summary.messageHash)
210
+ && (action !== "send" || safeHash(summary.messageHash))
184
211
  && summary.responseOk === true
185
- && liveTurnStatusProvesSendAccepted(summary.turnStatus);
212
+ && actionAccepted;
186
213
  }
187
214
  function liveTurnStatusProvesSendAccepted(value) {
188
215
  if (typeof value !== "string")
@@ -228,6 +228,7 @@ function readRuntimeScenario(scenario, file, evidenceDir, runtimeProofDir) {
228
228
  const fallbackId = safeEvidenceStem(basename(file, ".json")) || "scenario";
229
229
  const id = isSafeScenarioId(requestedId) ? requestedId : fallbackId;
230
230
  const requiredTools = stringArray(scenario.required_tools);
231
+ const allowedTools = stringArray(scenario.allowed_tools);
231
232
  const forbiddenBehaviors = stringArray(scenario.forbidden_behaviors);
232
233
  const expectedPublicSafeEvidence = stringArray(scenario.expected_public_safe_evidence);
233
234
  const privateDataExclusions = stringArray(scenario.private_data_exclusions).length
@@ -270,7 +271,7 @@ function readRuntimeScenario(scenario, file, evidenceDir, runtimeProofDir) {
270
271
  surface: stringValue(scenario.surface) || "unknown",
271
272
  status: contractBlockers.length > 0 ? "invalid" : proof.blockers.length > 0 ? "runtime_proof_required" : "runtime_proof_ready",
272
273
  evidencePath: join(evidenceDir, `${id}.json`),
273
- allowedTools: requiredTools,
274
+ allowedTools: allowedTools.length > 0 ? allowedTools : requiredTools,
274
275
  forbiddenBehaviors,
275
276
  expectedPublicSafeEvidence,
276
277
  privateDataExclusions,
@@ -0,0 +1,112 @@
1
+ # Release Notes 1.1.4
2
+
3
+ `1.1.4` is a stable patch release for the Codex-first collaboration cockpit and
4
+ OpenClaw gateway lane.
5
+
6
+ It keeps the `1.1.3` stable claim scope and adds the approved live-resume
7
+ gateway proof from #383 / #402. The release proves one harmless approved
8
+ `thread/resume` path through the installed OpenClaw gateway plus post-action
9
+ safe refresh/reasoning evidence.
10
+
11
+ ## What Changed
12
+
13
+ - Added `--action send|resume` to the OpenClaw live-control smoke command so
14
+ release/runtime proof can validate either a message send or a no-message
15
+ Codex resume action through the same gateway surface.
16
+ - `loo openclaw live-control-smoke --action resume` now requires
17
+ `loo_codex_control_dry_run`, `loo_codex_resume_thread`, and `loo_audit_tail`
18
+ instead of the send-only live tool.
19
+ - Resume proof accepts the Codex app-server `thread/resume` response shape when
20
+ `ok: true` and `method: "thread/resume"` are present, even when there is no
21
+ turn-status payload.
22
+ - Resume proof fails closed when the live response is not ok, reports the wrong
23
+ method, or cannot be matched back to the dry-run approval audit record.
24
+ - Runtime action parsing now rejects malformed action values instead of
25
+ silently falling back to the higher-side-effect send path.
26
+ - The `openclaw-gateway-live-codex-v1-1` runtime scenario now keeps only the
27
+ common dry-run/audit tools in `required_tools` and lists `loo_codex_send_message`
28
+ and `loo_codex_resume_thread` as allowed alternatives, so scenario consumers
29
+ do not try to perform two live actions while the contract caps live actions at
30
+ one.
31
+ - Scenario sweep output preserves `allowed_tools` separately from the required
32
+ dry-run tool sequence for runtime-required scenarios.
33
+ - Regression tests cover the send path, resume happy path, status-less resume
34
+ success, resume negative paths, malformed actions, and scenario-contract
35
+ alternate live actions.
36
+ - Carries forward the #160 desktop proof-action release boundary:
37
+ `loo_desktop_proof_action` / `loo desktop proof-action` validates the
38
+ action-bound CUA Driver TextEdit scratch proof gate and does not prove
39
+ generic GUI mutation. The proof action still requires exact backend, target
40
+ app, target window, action hash, approval ref, permission state, scratch file
41
+ path, and `execute: true` before the backend can run. Generic gateway
42
+ invocation without exact proof args fails closed, using the same proof
43
+ boundary as beta.35.
44
+ Exact gate phrase: exact backend, target app, target window, action hash, approval ref, permission state, scratch file path, and `execute: true`.
45
+ Exact fail-closed phrase: generic gateway invocation without exact proof args fails closed.
46
+ The desktop proof action keeps the same proof boundary as beta.35.
47
+ - Carries forward strict OpenClaw gateway result handling:
48
+ plugin output with `output.details.ok: false` is reported as
49
+ `openclaw_tool_result_not_ok:<tool>` rather than successful tool proof.
50
+
51
+ ## Stable Claim Scope
52
+
53
+ Allowed stable claim:
54
+
55
+ > Codex-first local orchestration through OpenClaw for local Codex sessions,
56
+ > including local indexing, search, describe, bounded expansion, dry-run control
57
+ > envelopes, read-only collaboration cockpit/autonomy cards, read-only Desktop
58
+ > visibility and fallback status, action-bound proof-packet validation,
59
+ > public-safe scorecards, runtime proof handoff packets, runtime sweep
60
+ > summaries, installed OpenClaw gateway dogfood, and one approved harmless live
61
+ > Codex resume proof through the installed OpenClaw gateway.
62
+
63
+ This release still does not claim generic GUI mutation, Codex GUI mutation,
64
+ unattended control, Claude Code parity, P1 business-adapter parity, cloud sync,
65
+ or enterprise/customer-ready security.
66
+
67
+ ## Release Gate Notes
68
+
69
+ - Stable issue: #403.
70
+ - Included implementation issue: #383.
71
+ - Included implementation PR: #402.
72
+ - Parent product tracker: #309.
73
+ - Operating-loop tracker: #16.
74
+ - Baseline stable release: `v1.1.3`.
75
+ - Candidate package: `lossless-openclaw-orchestrator@1.1.4`.
76
+ - Expected git tag: `v1.1.4`.
77
+ - Expected GitHub Release:
78
+ `https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/releases/tag/v1.1.4`.
79
+ - Required pre-publish stable gates: `npm run check`, `npm pack --dry-run`,
80
+ strict runtime-required scenario sweep for `openclaw-gateway-live-codex-v1-1`
81
+ and `post-action-refresh-reasoning-v1-1`, strict scorecard sweep, release
82
+ preflight, release bundle, release demo-status, release status, OpenClaw
83
+ dogfood, and OpenClaw tool-smoke for the exact stable candidate SHA.
84
+ - Required PR gates: GitHub CI, CodeQL, current-head review threads clear, and
85
+ any actionable review feedback fixed before merge.
86
+ - Required post-publish stable gates: npm `latest` view, git tag, GitHub
87
+ non-prerelease Release, `loo release finalization-status --expected-dist-tag
88
+ latest --expected-github-prerelease false --strict`, fresh npm `@latest`
89
+ published-smoke, and strict general-readiness before #403 closes.
90
+ - Working-app status example:
91
+ `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`
92
+ - Reduced-scope status example:
93
+ `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`
94
+ - Post-publish finalization example:
95
+ `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`
96
+ - `approved_live_control_smoke_missing` remains the expected blocker when a
97
+ working-app claim is attempted without approved live-control smoke evidence
98
+ for the exact candidate SHA.
99
+
100
+ ## Explicit Non-Claims
101
+
102
+ No new live Codex control smoke is run by this release.
103
+ It does not run generic GUI mutation and does not run Codex GUI mutation.
104
+ No screenshot workflow.
105
+ No prompt typing. No clicking. No arbitrary app control.
106
+ No unattended desktop takeover.
107
+ No automatic gateway authorization and no broad gateway scope approval.
108
+ Claude Code remains an adapter stub, not adapter equivalence.
109
+ No Notion, support-control, Stripe, or Company Brain P1 adapter proof.
110
+ No cloud sync.
111
+ Bundle/status/finalization checks do not publish to npm and do not create a GitHub Release.
112
+ No release-grade enterprise security or customer-ready security claim.
@@ -9,16 +9,16 @@
9
9
  "surface": "installed-openclaw-gateway",
10
10
  "required_tools": [
11
11
  "loo_codex_control_dry_run",
12
- "loo_codex_send_message",
13
12
  "loo_audit_tail"
14
13
  ],
15
14
  "allowed_tools": [
16
15
  "loo_codex_control_dry_run",
17
16
  "loo_codex_send_message",
17
+ "loo_codex_resume_thread",
18
18
  "loo_audit_tail"
19
19
  ],
20
20
  "allowed_live_behaviors": [
21
- "one approved harmless Codex turn on a selected or disposable target thread"
21
+ "one approved harmless Codex send or resume on a selected or disposable target thread"
22
22
  ],
23
23
  "forbidden_behaviors": [
24
24
  "unauthorized_live_control",
@@ -40,9 +40,9 @@
40
40
  "action type",
41
41
  "approval audit id prefix",
42
42
  "params hash",
43
- "message hash",
44
- "Codex notification count and types",
45
- "completion status",
43
+ "message hash when the live action sends a message",
44
+ "Codex notification count and types when available",
45
+ "completion status or response ok marker",
46
46
  "rawPromptIncluded false"
47
47
  ],
48
48
  "metrics": {
@@ -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.3",
5
+ "version": "1.1.4",
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",
3
+ "version": "1.1.4",
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",
@@ -44,7 +44,7 @@ import { createGeneralReleaseReadiness } from "./general-release-readiness.js";
44
44
  import { runOpenClawDogfood } from "./openclaw-dogfood.js";
45
45
  import { DEFAULT_REQUIRED_TOOL_CALLS, runOpenClawToolSmoke } from "./openclaw-tool-smoke.js";
46
46
  import { createPublishedPackageSmokeReport } from "./published-package-smoke.js";
47
- import { runOpenClawGatewayLiveControlSmoke } from "./openclaw-live-control-smoke.js";
47
+ import { runOpenClawGatewayLiveControlSmoke, type OpenClawGatewayLiveControlAction } from "./openclaw-live-control-smoke.js";
48
48
  import { runOpenClawPostActionRefreshSmoke } from "./openclaw-post-action-refresh-smoke.js";
49
49
  import { createScorecardSweep } from "./scorecard-sweep.js";
50
50
  import { createScenarioSweep } from "./scenario-sweep.js";
@@ -803,7 +803,7 @@ function mainUsageText(): string {
803
803
  " loo openclaw dogfood [--dev] [--profile name] [--install-source path] [--link] [--force-install] [--evidence-path path] [--strict]",
804
804
  " loo openclaw tool-smoke [--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] [--thread-id id] [--expand-profile metadata|brief|evidence] [--token-budget n] [--required-tool name] [--evidence-path path] [--strict]",
805
805
  " loo openclaw published-smoke --evidence-dir path --dogfood-report path --tool-smoke-report path [--configured-tool-smoke-report path] [--npm-install-diagnostic-report path] [--registry-version version] [--registry-beta-version version] [--root path] [--now iso] [--strict]",
806
- " 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]",
806
+ " loo openclaw live-control-smoke --evidence-dir path --thread-id id [--action send|resume] [--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]",
807
807
  " 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]",
808
808
  " 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]",
809
809
  " 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]",
@@ -1212,9 +1212,9 @@ function printLiveControlSmokeHelp(): void {
1212
1212
  function printOpenClawLiveControlSmokeHelp(): void {
1213
1213
  console.log([
1214
1214
  "Usage:",
1215
- " 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]",
1215
+ " loo openclaw live-control-smoke --evidence-dir path --thread-id id [--action send|resume] [--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]",
1216
1216
  "",
1217
- "Runs one approval-gated live Codex send through the installed OpenClaw gateway tools.invoke path.",
1217
+ "Runs one approval-gated live Codex send or resume through the installed OpenClaw gateway tools.invoke path.",
1218
1218
  "",
1219
1219
  "Outputs:",
1220
1220
  " openclaw-gateway-live-codex-v1-1.runtime-proof.json",
@@ -1222,7 +1222,7 @@ function printOpenClawLiveControlSmokeHelp(): void {
1222
1222
  "",
1223
1223
  "Safety boundary:",
1224
1224
  " The command requires an explicit --thread-id target.",
1225
- " It invokes loo_codex_control_dry_run first, then uses the matching approval_audit_id for loo_codex_send_message with dry_run:false.",
1225
+ " It invokes loo_codex_control_dry_run first, then uses the matching approval_audit_id for the selected live tool with dry_run:false.",
1226
1226
  " It reads loo_audit_tail to prove matching dry-run/live audit metadata.",
1227
1227
  " Evidence contains refs, audit ids, hashes, tool names, and status only.",
1228
1228
  " It does not write raw prompt text, raw transcript spans, screenshots, SQLite DBs, tokens, or credentials.",
@@ -2026,6 +2026,7 @@ function parseOpenClawLiveControlSmokeArgs(input: string[]): {
2026
2026
  token?: string;
2027
2027
  sessionKey?: string;
2028
2028
  threadId: string;
2029
+ action?: OpenClawGatewayLiveControlAction;
2029
2030
  message?: string;
2030
2031
  evidenceDir: string;
2031
2032
  gatewayTimeoutMs?: number;
@@ -2050,6 +2051,8 @@ function parseOpenClawLiveControlSmokeArgs(input: string[]): {
2050
2051
  parsed.sessionKey = requireOptionValue(input[++index], arg);
2051
2052
  } else if (arg === "--thread-id") {
2052
2053
  parsed.threadId = requireOptionValue(input[++index], arg);
2054
+ } else if (arg === "--action") {
2055
+ parsed.action = parseOpenClawLiveControlAction(requireOptionValue(input[++index], arg));
2053
2056
  } else if (arg === "--message") {
2054
2057
  parsed.message = requireOptionValue(input[++index], arg);
2055
2058
  } else if (arg === "--evidence-dir") {
@@ -2065,6 +2068,11 @@ function parseOpenClawLiveControlSmokeArgs(input: string[]): {
2065
2068
  return parsed as ReturnType<typeof parseOpenClawLiveControlSmokeArgs>;
2066
2069
  }
2067
2070
 
2071
+ function parseOpenClawLiveControlAction(value: string): OpenClawGatewayLiveControlAction {
2072
+ if (value === "send" || value === "resume") return value;
2073
+ throw new Error("--action must be send or resume");
2074
+ }
2075
+
2068
2076
  function parseOpenClawPostActionRefreshSmokeArgs(input: string[]): {
2069
2077
  openclawBin?: string;
2070
2078
  dev?: boolean;
@@ -10,18 +10,22 @@ export type OpenClawGatewayLiveControlSmokeOptions = {
10
10
  token?: string;
11
11
  sessionKey?: string;
12
12
  threadId: string;
13
+ action?: OpenClawGatewayLiveControlAction;
13
14
  message?: string;
14
15
  evidenceDir: string;
15
16
  gatewayTimeoutMs?: number;
16
17
  now?: string;
17
18
  };
18
19
 
20
+ export type OpenClawGatewayLiveControlAction = "send" | "resume";
21
+
19
22
  export type OpenClawGatewayLiveControlSmokeReport = {
20
23
  ok: boolean;
21
24
  proofReady: boolean;
22
25
  publicSafe: boolean;
23
26
  generatedAt: string;
24
27
  command: string;
28
+ action: OpenClawGatewayLiveControlAction;
25
29
  requiredTools: string[];
26
30
  targetRef: string;
27
31
  dryRun: {
@@ -84,7 +88,6 @@ type ControlSummary = {
84
88
  const SCENARIO_ID = "openclaw-gateway-live-codex-v1-1";
85
89
  const ACCEPTED_LIVE_TURN_STATUSES = new Set(["accepted", "completed", "in_progress", "pending", "queued", "running"]);
86
90
  const DEFAULT_MESSAGE = "LCO OpenClaw gateway live-control smoke. Reply with exactly: LCO gateway live smoke acknowledged. Do not run commands, edit files, or use tools.";
87
- const REQUIRED_TOOLS = ["loo_codex_control_dry_run", "loo_codex_send_message", "loo_audit_tail"];
88
91
  const PRIVATE_DATA_EXCLUSIONS = [
89
92
  "raw OpenClaw gateway stdout/stderr",
90
93
  "raw tool output",
@@ -113,6 +116,9 @@ export function runOpenClawGatewayLiveControlSmoke(options: OpenClawGatewayLiveC
113
116
  ];
114
117
  const callOptions = { timeoutMs: gatewayTimeoutMs, env: options.token ? { OPENCLAW_GATEWAY_TOKEN: options.token } : undefined };
115
118
  const sessionKey = options.sessionKey || "agent:main:lco-live-control-smoke";
119
+ const action = normalizeAction(options.action);
120
+ const liveToolName = liveToolForAction(action);
121
+ const requiredTools = ["loo_codex_control_dry_run", liveToolName, "loo_audit_tail"];
116
122
  const message = options.message ?? DEFAULT_MESSAGE;
117
123
  const targetRef = `codex_thread:${options.threadId}`;
118
124
  const blockers: string[] = [];
@@ -120,42 +126,33 @@ export function runOpenClawGatewayLiveControlSmoke(options: OpenClawGatewayLiveC
120
126
  const catalog = callGatewayJson(openclawBin, baseArgs, gatewayOptions, "tools.catalog", {}, callOptions);
121
127
  const catalogTools = catalog.status === 0 && catalog.parsed !== undefined ? extractCatalogToolNames(unwrapGatewayPayload(catalog.parsed)) : [];
122
128
  blockers.push(...gatewayCallBlockers(catalog, "openclaw_live_catalog_failed"));
123
- blockers.push(...REQUIRED_TOOLS.filter((tool) => !catalogTools.includes(tool)).map((tool) => `openclaw_live_catalog_missing_tool:${tool}`));
129
+ blockers.push(...requiredTools.filter((tool) => !catalogTools.includes(tool)).map((tool) => `openclaw_live_catalog_missing_tool:${tool}`));
124
130
 
125
131
  const dryRun = blockers.length === 0
126
132
  ? callGatewayJson(openclawBin, baseArgs, gatewayOptions, "tools.invoke", {
127
133
  name: "loo_codex_control_dry_run",
128
- args: {
129
- action: "send",
130
- thread_id: options.threadId,
131
- message
132
- },
134
+ args: liveDryRunArgs(action, options.threadId, message),
133
135
  sessionKey,
134
136
  confirm: false,
135
- idempotencyKey: `loo-live-smoke-dry-run-${options.threadId}`
137
+ idempotencyKey: `loo-live-smoke-dry-run-${action}-${options.threadId}`
136
138
  }, callOptions)
137
139
  : null;
138
140
  blockers.push(...(dryRun ? gatewayCallBlockers(dryRun, "openclaw_live_dry_run_failed") : []));
139
141
  const dryRunSummary = summarizeControl(dryRun);
140
- if (dryRun && !validDryRun(dryRunSummary)) blockers.push("openclaw_live_dry_run_not_proven");
142
+ if (dryRun && !validDryRun(action, dryRunSummary)) blockers.push("openclaw_live_dry_run_not_proven");
141
143
 
142
144
  const live = blockers.length === 0
143
145
  ? callGatewayJson(openclawBin, baseArgs, gatewayOptions, "tools.invoke", {
144
- name: "loo_codex_send_message",
145
- args: {
146
- thread_id: options.threadId,
147
- message,
148
- dry_run: false,
149
- approval_audit_id: dryRunSummary.approvalAuditId
150
- },
146
+ name: liveToolName,
147
+ args: liveArgs(action, options.threadId, message, dryRunSummary.approvalAuditId),
151
148
  sessionKey,
152
149
  confirm: false,
153
- idempotencyKey: `loo-live-smoke-send-${options.threadId}-${dryRunSummary.approvalAuditId}`
150
+ idempotencyKey: `loo-live-smoke-${action}-${options.threadId}-${dryRunSummary.approvalAuditId}`
154
151
  }, callOptions)
155
152
  : null;
156
- blockers.push(...(live ? gatewayCallBlockers(live, "openclaw_live_send_failed") : []));
153
+ blockers.push(...(live ? gatewayCallBlockers(live, "openclaw_live_control_failed") : []));
157
154
  const liveSummary = summarizeControl(live);
158
- if (live && !validLive(liveSummary)) blockers.push("openclaw_live_send_not_proven");
155
+ if (live && !validLive(action, liveSummary)) blockers.push(action === "resume" ? "openclaw_live_resume_not_proven" : "openclaw_live_send_not_proven");
159
156
  if (live && dryRunSummary.paramsHash && liveSummary.paramsHash && liveSummary.paramsHash !== dryRunSummary.paramsHash) blockers.push("openclaw_live_params_hash_mismatch");
160
157
  if (live && dryRunSummary.messageHash && liveSummary.messageHash && liveSummary.messageHash !== dryRunSummary.messageHash) blockers.push("openclaw_live_message_hash_mismatch");
161
158
  const approvalAuditIdUsed = live ? dryRunSummary.approvalAuditId : null;
@@ -172,7 +169,7 @@ export function runOpenClawGatewayLiveControlSmoke(options: OpenClawGatewayLiveC
172
169
  args: { limit: 20 },
173
170
  sessionKey,
174
171
  confirm: false,
175
- idempotencyKey: `loo-live-smoke-audit-tail-${options.threadId}`
172
+ idempotencyKey: `loo-live-smoke-audit-tail-${action}-${options.threadId}`
176
173
  }, callOptions)
177
174
  : null;
178
175
  blockers.push(...(auditTail ? gatewayCallBlockers(auditTail, "openclaw_live_audit_tail_failed") : []));
@@ -196,7 +193,8 @@ export function runOpenClawGatewayLiveControlSmoke(options: OpenClawGatewayLiveC
196
193
  publicSafe: true,
197
194
  generatedAt: options.now ?? new Date().toISOString(),
198
195
  command: `${sanitizeCommandBinary(openclawBin)} ${[...baseArgs, "gateway", "call", "tools.invoke", "--json", "--params", "<redacted>"].join(" ")}`,
199
- requiredTools: REQUIRED_TOOLS,
196
+ action,
197
+ requiredTools,
200
198
  targetRef,
201
199
  dryRun: {
202
200
  approvalAuditId: dryRunSummary.approvalAuditId,
@@ -225,7 +223,7 @@ export function runOpenClawGatewayLiveControlSmoke(options: OpenClawGatewayLiveC
225
223
  rawTranscriptRead: false
226
224
  },
227
225
  privateDataExclusions: PRIVATE_DATA_EXCLUSIONS,
228
- proofBoundary: "This proves one approved harmless live Codex send through the installed OpenClaw gateway path only; it does not prove unattended live control, broad gateway scope approval, GUI mutation, Claude parity, or bypassed Codex approvals.",
226
+ proofBoundary: "This proves one approved harmless live Codex send/resume through the installed OpenClaw gateway path only; it does not prove unattended live control, broad gateway scope approval, GUI mutation, Claude parity, or bypassed Codex approvals.",
229
227
  nextAction: uniqueBlockers.length === 0
230
228
  ? "Run the v1.1 runtime scenario sweep against this runtime-proof directory, then continue #159 post-action refresh proof."
231
229
  : "Resolve the listed gateway live-control blockers before claiming #158 runtime proof."
@@ -259,20 +257,55 @@ function runtimeProofForReport(report: OpenClawGatewayLiveControlSmokeReport): R
259
257
  };
260
258
  }
261
259
 
262
- function validDryRun(summary: ControlSummary): boolean {
260
+ function liveDryRunArgs(action: OpenClawGatewayLiveControlAction, threadId: string, message: string): Record<string, unknown> {
261
+ return action === "send"
262
+ ? {
263
+ action,
264
+ thread_id: threadId,
265
+ message
266
+ }
267
+ : {
268
+ action,
269
+ thread_id: threadId
270
+ };
271
+ }
272
+
273
+ function liveArgs(action: OpenClawGatewayLiveControlAction, threadId: string, message: string, approvalAuditId: string | null): Record<string, unknown> {
274
+ const common = {
275
+ thread_id: threadId,
276
+ dry_run: false,
277
+ approval_audit_id: approvalAuditId
278
+ };
279
+ return action === "send" ? { ...common, message } : common;
280
+ }
281
+
282
+ function liveToolForAction(action: OpenClawGatewayLiveControlAction): string {
283
+ return action === "send" ? "loo_codex_send_message" : "loo_codex_resume_thread";
284
+ }
285
+
286
+ function normalizeAction(action: unknown): OpenClawGatewayLiveControlAction {
287
+ if (action === undefined || action === "send") return "send";
288
+ if (action === "resume") return "resume";
289
+ throw new Error("action must be send or resume");
290
+ }
291
+
292
+ function validDryRun(action: OpenClawGatewayLiveControlAction, summary: ControlSummary): boolean {
263
293
  return summary.live === false
264
294
  && safeAuditId(summary.approvalAuditId)
265
295
  && safeHash(summary.paramsHash)
266
- && safeHash(summary.messageHash);
296
+ && (action !== "send" || safeHash(summary.messageHash));
267
297
  }
268
298
 
269
- function validLive(summary: ControlSummary): boolean {
299
+ function validLive(action: OpenClawGatewayLiveControlAction, summary: ControlSummary): boolean {
300
+ const actionAccepted = action === "resume"
301
+ ? summary.method === "thread/resume"
302
+ : liveTurnStatusProvesSendAccepted(summary.turnStatus);
270
303
  return summary.live === true
271
304
  && safeAuditId(summary.approvalAuditId)
272
305
  && safeHash(summary.paramsHash)
273
- && safeHash(summary.messageHash)
306
+ && (action !== "send" || safeHash(summary.messageHash))
274
307
  && summary.responseOk === true
275
- && liveTurnStatusProvesSendAccepted(summary.turnStatus);
308
+ && actionAccepted;
276
309
  }
277
310
 
278
311
  function liveTurnStatusProvesSendAccepted(value: string | null): boolean {
@@ -351,6 +351,7 @@ function readRuntimeScenario(scenario: ScenarioJson, file: string, evidenceDir:
351
351
  const fallbackId = safeEvidenceStem(basename(file, ".json")) || "scenario";
352
352
  const id = isSafeScenarioId(requestedId) ? requestedId : fallbackId;
353
353
  const requiredTools = stringArray(scenario.required_tools);
354
+ const allowedTools = stringArray(scenario.allowed_tools);
354
355
  const forbiddenBehaviors = stringArray(scenario.forbidden_behaviors);
355
356
  const expectedPublicSafeEvidence = stringArray(scenario.expected_public_safe_evidence);
356
357
  const privateDataExclusions = stringArray(scenario.private_data_exclusions).length
@@ -393,7 +394,7 @@ function readRuntimeScenario(scenario: ScenarioJson, file: string, evidenceDir:
393
394
  surface: stringValue(scenario.surface) || "unknown",
394
395
  status: contractBlockers.length > 0 ? "invalid" : proof.blockers.length > 0 ? "runtime_proof_required" : "runtime_proof_ready",
395
396
  evidencePath: join(evidenceDir, `${id}.json`),
396
- allowedTools: requiredTools,
397
+ allowedTools: allowedTools.length > 0 ? allowedTools : requiredTools,
397
398
  forbiddenBehaviors,
398
399
  expectedPublicSafeEvidence,
399
400
  privateDataExclusions,
@@ -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.3",
5
+ "version": "1.1.4",
6
6
  "kind": "tool",
7
7
  "tools": {
8
8
  "prefix": "loo_"