lossless-openclaw-orchestrator 1.2.3 → 1.2.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
@@ -23,7 +23,7 @@ safe action without rereading raw transcripts.
23
23
  | Approval-gated boundaries | Dry-run Codex actions and verify matching audit ids before any live control. |
24
24
  | OpenClaw/MCP tools | Use the same local-first recall and approval-bounded surfaces from agent workflows. |
25
25
 
26
- [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.2.3.md) · [1.2.2 Notes](docs/RELEASE_NOTES_1.2.2.md) · [1.2.1 Notes](docs/RELEASE_NOTES_1.2.1.md) · [1.2.0 Notes](docs/RELEASE_NOTES_1.2.0.md) · [1.1.4 Notes](docs/RELEASE_NOTES_1.1.4.md) · [1.0 Notes](docs/RELEASE_NOTES_1.0.0.md) · [License](LICENSE)
26
+ [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.2.4.md) · [1.2.3 Notes](docs/RELEASE_NOTES_1.2.3.md) · [1.2.2 Notes](docs/RELEASE_NOTES_1.2.2.md) · [1.2.1 Notes](docs/RELEASE_NOTES_1.2.1.md) · [1.2.0 Notes](docs/RELEASE_NOTES_1.2.0.md) · [1.1.4 Notes](docs/RELEASE_NOTES_1.1.4.md) · [1.0 Notes](docs/RELEASE_NOTES_1.0.0.md) · [License](LICENSE)
27
27
 
28
28
  ## Why It Matters
29
29
 
@@ -325,7 +325,7 @@ The stable public product is Codex-first local orchestration: index, search,
325
325
  describe, expand, prepared-state recall, OpenClaw/MCP tools, and
326
326
  approval-gated dry-run/control boundaries.
327
327
 
328
- The 1.2 prepared-state and summary-leaves lane is shipped in stable `1.2.3`.
328
+ The 1.2 prepared-state and summary-leaves lane is shipped in stable `1.2.4`.
329
329
  Current launch work is the M11 GA assurance sprint, tracked in GitHub and
330
330
  summarized in [VISION.md](VISION.md). Keep sprint and agent-operator details
331
331
  there, in [AGENTS.md](AGENTS.md), and in the packaged
package/VISION.md CHANGED
@@ -12,11 +12,11 @@ The stable product should feel like a local orchestration cockpit: OpenClaw can
12
12
 
13
13
  The current release-readiness lane is Milestone 11: LCO 1.2.0 GA Assurance And Global Launch Readiness. GitHub tracker [#478](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/478) and children [#479](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/479)-[#493](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/493) own implementation truth; this file owns the product and eval boundary.
14
14
 
15
- The stable 1.0.0, 1.1.0, 1.1.1, 1.1.2, 1.1.3, 1.1.4, 1.2.0, 1.2.1, 1.2.2, and 1.2.3 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 1.1 collaboration cockpit [#309](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/309) is completed proof for read-only collaboration summaries and execute-false next steps, not the current active child-work list.
15
+ The stable 1.0.0, 1.1.0, 1.1.1, 1.1.2, 1.1.3, 1.1.4, 1.2.0, 1.2.1, 1.2.2, and 1.2.3 packages have shipped on npm `latest` and their GitHub Releases are published. The 1.2.4 package is the current stable patch candidate; npm `latest` and GitHub Release finalization must be proven by release evidence before any global-ready claim treats it as 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 1.1 collaboration cockpit [#309](https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/issues/309) is completed proof for read-only collaboration summaries and execute-false next steps, not the current active child-work list.
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
 
19
- The core Codex recall, M9 handoff paths, Codex Autonomy Cockpit, Eva Operating Picture P0 paths, 1.1 Desktop collaboration cockpit paths, 1.2 prepared-state and summary-leaf paths, release metadata, package/gateway setup proof, docs truth, release gates, public-safe scorecards, post-publish fresh npm `@latest` install proof, and stable publication lanes are no longer the main product gap. The current gap is no-bug-left-behind launch assurance: prove the published `1.2.3` package is installable, discoverable, public-safe, and honest for global users before broad rollout.
19
+ The core Codex recall, M9 handoff paths, Codex Autonomy Cockpit, Eva Operating Picture P0 paths, 1.1 Desktop collaboration cockpit paths, 1.2 prepared-state and summary-leaf paths, release metadata, package/gateway setup proof, docs truth, release gates, and public-safe scorecards are no longer the main product gap. The current gap is no-bug-left-behind launch assurance: prove the `1.2.4` stable candidate is published, installable from npm `@latest`, discoverable, public-safe, and honest for global users before broad rollout.
20
20
 
21
21
  The current target is:
22
22
 
@@ -38,7 +38,7 @@ The current target is:
38
38
  - Keep P1 source adapters, including Notion, support-control, Company Brain, Stripe, dashboard/export, and model summarization, behind separate adapters and proof gates; P0 tools report those sources as `not_configured` instead of fabricating summaries.
39
39
  - Use the [source authority profile](docs/SOURCE_AUTHORITY_PROFILE.md) to distinguish "this source returned data" from "this source owns the current truth"; unavailable or cache-only sources must degrade claims to `unknown` or low confidence.
40
40
 
41
- The sprint remains Codex-first, local-first, read-only-first, and public-safe by default. It does not claim full business truth, customer readiness, Claude Code parity, remote sync, generic GUI mutation, unattended desktop control, permission bypass, or enterprise/customer-ready security. npm `latest` promotion and GitHub Release creation are complete for 1.2.3. Desktop-visible classification and fallback readiness/status are proven by #307/#308; actual Codex GUI mutation remains excluded until a future action-bound proof gate records the exact backend, target, action hash, approval, and observation.
41
+ The sprint remains Codex-first, local-first, read-only-first, and public-safe by default. It does not claim full business truth, customer readiness, Claude Code parity, remote sync, generic GUI mutation, unattended desktop control, permission bypass, or enterprise/customer-ready security. npm `latest` promotion and GitHub Release creation for 1.2.4 are release gates, not assumptions. Desktop-visible classification and fallback readiness/status are proven by #307/#308; actual Codex GUI mutation remains excluded until a future action-bound proof gate records the exact backend, target, action hash, approval, and observation.
42
42
 
43
43
  What 1.2 should let a local OpenClaw agent do next:
44
44
 
@@ -294,7 +294,7 @@ Release candidates follow [docs/BETA_RELEASE_RUNBOOK.md](docs/BETA_RELEASE_RUNBO
294
294
 
295
295
  ## Proof Boundary
296
296
 
297
- Allowed stable 1.2.3 claim:
297
+ Allowed stable 1.2.4 claim:
298
298
 
299
299
  > Collaborate with local Codex sessions through OpenClaw using local indexing, prepared-state recall, bounded expansion, and approval-gated dry-run/control boundaries.
300
300
 
@@ -4004,13 +4004,13 @@ function formatClaudeSessionInventoryMetadata(description) {
4004
4004
  return [
4005
4005
  `Claude session ID: ${description.sessionId}`,
4006
4006
  `Ref: ${description.sourceRef}`,
4007
- description.title ? `Title: ${description.title}` : null,
4008
- description.project ? `Project: ${description.project}` : null,
4009
- description.workspaceHint ? `Workspace: ${description.workspaceHint}` : null,
4007
+ description.title ? `Title: ${publicSafeText(description.title, 500)}` : null,
4008
+ description.project ? `Project: ${publicSafeText(description.project, 500)}` : null,
4009
+ description.workspaceHint ? `Workspace: ${publicSafeText(description.workspaceHint, 500)}` : null,
4010
4010
  description.status ? `Status: ${description.status}` : null,
4011
4011
  description.updatedAt ? `Updated: ${description.updatedAt}` : null,
4012
- `Source path: ${description.sourcePath}`,
4013
- description.sourceRefs.length ? `Source refs: ${description.sourceRefs.join(", ")}` : null,
4012
+ `Source ref: ${publicSourcePathRef(description.sourcePath)}`,
4013
+ description.sourceRefs.length ? `Source refs: ${description.sourceRefs.map((ref) => publicSafeText(ref, 180)).join(", ")}` : null,
4014
4014
  "Proof boundary: read-only Claude metadata fixture inventory only; no private transcript content, live control, GUI mutation, parity, or cloud sync proof."
4015
4015
  ].filter(Boolean).join("\n");
4016
4016
  }
@@ -8623,15 +8623,15 @@ export function expandSession(db, options) {
8623
8623
  `Thread: ${description.title ?? description.threadId}`,
8624
8624
  `Ref: ${description.sourceRef}`,
8625
8625
  `ID: ${description.threadId}`,
8626
- description.cwd ? `CWD: ${description.cwd}` : null,
8626
+ description.cwd ? `CWD: ${publicSafeText(description.cwd, 500)}` : null,
8627
8627
  description.branch ? `Branch: ${description.branch}` : null,
8628
8628
  description.gitSha ? `Git SHA: ${description.gitSha}` : null,
8629
- description.summary ? `Summary: ${description.summary}` : null,
8629
+ description.summary ? `Summary: ${publicSafeText(description.summary, 2000)}` : null,
8630
8630
  formatSessionMetadata(description.metadata),
8631
8631
  `Plans: ${description.planCount}`,
8632
8632
  `Touched files: ${description.touchedFiles.length}`,
8633
8633
  `Tool calls: ${description.toolCallCount}`,
8634
- `Source path: ${description.sourcePath}`
8634
+ `Source ref: ${publicSourcePathRef(description.sourcePath)}`
8635
8635
  ].filter(Boolean).join("\n");
8636
8636
  return {
8637
8637
  sourceKind: "codex_thread",
@@ -8645,13 +8645,13 @@ export function expandSession(db, options) {
8645
8645
  const text = [
8646
8646
  `Thread: ${description.title ?? description.threadId}`,
8647
8647
  `ID: ${description.threadId}`,
8648
- description.cwd ? `CWD: ${description.cwd}` : null,
8648
+ description.cwd ? `CWD: ${publicSafeText(description.cwd, 500)}` : null,
8649
8649
  description.branch ? `Branch: ${description.branch}` : null,
8650
8650
  description.gitSha ? `Git SHA: ${description.gitSha}` : null,
8651
- description.summary ? `Summary: ${description.summary}` : null,
8652
- description.finalMessage ? `Final message: ${truncate(description.finalMessage, profile.name === "evidence" ? 3200 : 900)}` : null,
8651
+ description.summary ? `Summary: ${publicSafeText(description.summary, profile.name === "evidence" ? 3200 : 1600)}` : null,
8652
+ description.finalMessage ? `Final message: ${publicSafeText(description.finalMessage, profile.name === "evidence" ? 3200 : 900)}` : null,
8653
8653
  description.touchedFiles.length ? `Touched files:\n${formatTouchedFiles(description.touchedFiles, profile.name === "evidence" ? 50 : 12, profile.name === "evidence" ? 3200 : 900)}` : null,
8654
- plans.length ? `Plans:\n${plans.map((plan) => truncate(plan, profile.name === "evidence" ? 3200 : 1200)).join("\n\n")}` : null
8654
+ plans.length ? `Plans:\n${plans.map((plan) => publicSafeText(plan, profile.name === "evidence" ? 3200 : 1200)).join("\n\n")}` : null
8655
8655
  ].filter(Boolean).join("\n\n");
8656
8656
  return {
8657
8657
  sourceKind: "codex_thread",
@@ -8666,7 +8666,7 @@ function formatTouchedFiles(files, limit, maxChars) {
8666
8666
  const perPathLimit = maxChars > 1000 ? 180 : 120;
8667
8667
  const visible = [];
8668
8668
  for (const file of files.slice(0, limit)) {
8669
- const next = `- ${truncate(file, perPathLimit)}`;
8669
+ const next = `- ${publicSafeText(file, perPathLimit)}`;
8670
8670
  const hiddenIfAccepted = files.length - (visible.length + 1);
8671
8671
  const markerIfAccepted = hiddenIfAccepted > 0 ? `- ... ${hiddenIfAccepted} more touched files omitted` : null;
8672
8672
  const visibleMaxChars = markerIfAccepted ? Math.max(0, maxChars - markerIfAccepted.length - 1) : maxChars;
@@ -8760,7 +8760,7 @@ export function expandRecallRef(db, options) {
8760
8760
  const metadata = formatClaudeSessionInventoryMetadata(description);
8761
8761
  const text = profile.name === "metadata"
8762
8762
  ? metadata
8763
- : truncateByApproxTokens(`${metadata}\n\nSafe summary:\n${description.summary ?? ""}`, profile.tokenBudget);
8763
+ : truncateByApproxTokens(`${metadata}\n\nSafe summary:\n${publicSafeText(description.summary ?? "", profile.tokenBudget * 6)}`, profile.tokenBudget);
8764
8764
  return {
8765
8765
  sourceKind: "claude_session",
8766
8766
  sourceRef: description.sourceRef,
@@ -8777,18 +8777,18 @@ export function expandRecallRef(db, options) {
8777
8777
  const metadata = [
8778
8778
  `Summary ID: ${summary.summaryId}`,
8779
8779
  `Ref: ${lcmSummaryRef(summary.sourcePath, summary.summaryId)}`,
8780
- `Conversation: ${summary.conversationTitle ?? summary.conversationId}`,
8780
+ `Conversation: ${publicSafeText(summary.conversationTitle ?? String(summary.conversationId), 500)}`,
8781
8781
  `Conversation ID: ${summary.conversationId}`,
8782
8782
  summary.kind ? `Kind: ${summary.kind}` : null,
8783
8783
  summary.depth !== null ? `Depth: ${summary.depth}` : null,
8784
8784
  summary.tokenCount !== null ? `Token count: ${summary.tokenCount}` : null,
8785
- summary.model ? `Model: ${summary.model}` : null,
8785
+ summary.model ? `Model: ${publicSafeText(summary.model, 120)}` : null,
8786
8786
  summary.updatedAt ? `Updated: ${summary.updatedAt}` : null,
8787
- `Source path: ${summary.sourcePath}`
8787
+ `Source ref: ${publicSourcePathRef(summary.sourcePath)}`
8788
8788
  ].filter(Boolean).join("\n");
8789
8789
  const text = profile.name === "metadata"
8790
8790
  ? metadata
8791
- : truncateByApproxTokens(`${metadata}\n\nContent:\n${summary.content}`, profile.tokenBudget);
8791
+ : truncateByApproxTokens(`${metadata}\n\nContent:\n${publicSafeText(summary.content, profile.tokenBudget * 6)}`, profile.tokenBudget);
8792
8792
  return {
8793
8793
  sourceKind: "lcm_summary",
8794
8794
  sourceRef: lcmSummaryRef(summary.sourcePath, summary.summaryId),
@@ -9055,7 +9055,7 @@ function lcmSummaryRecord(path, row) {
9055
9055
  conversationTitle: nullableString(row.conversationTitle),
9056
9056
  kind: nullableString(row.kind),
9057
9057
  depth: row.depth === null || row.depth === undefined ? null : Number(row.depth),
9058
- content: redactSafeString(String(row.content ?? "")),
9058
+ content: redactPublicSafeString(String(row.content ?? "")),
9059
9059
  tokenCount: row.tokenCount === null || row.tokenCount === undefined ? null : Number(row.tokenCount),
9060
9060
  createdAt: nullableString(row.createdAt),
9061
9061
  updatedAt: nullableString(row.updatedAt),
@@ -9160,13 +9160,17 @@ function publicSourcePathRef(sourcePath) {
9160
9160
  return `codex_source:${stableId(sourcePath).slice(0, 16)}`;
9161
9161
  }
9162
9162
  function publicSafeText(value, maxChars = 500) {
9163
- const redacted = redactSafeString(value)
9164
- .replace(/\/Volumes\/[^\s"'`)]+/g, "<redacted-path>")
9165
- .replace(/\/(?:Users|home)\/[^/\s"'`)]+\/\.codex\/[^\s"'`)]+/g, "<redacted-path>")
9166
- .replace(/\/root\/\.codex\/[^\s"'`)]+/g, "<redacted-path>")
9167
- .replace(/\/(?:private\/)?(?:tmp|var)\/[^\s"'`)]+/g, "<redacted-path>")
9168
- .replace(/~\/\.codex\/[^\s"'`)]+/g, "<redacted-path>");
9169
- return truncate(redacted, maxChars);
9163
+ return truncate(redactPublicSafeString(value), maxChars);
9164
+ }
9165
+ function redactPublicSafeString(value) {
9166
+ const localPathRootPattern = "(?:\\/Volumes\\/|\\/(?:Users|home|root)\\/|\\/(?:private\\/)?(?:tmp|var)\\/|~\\/|(?<![A-Za-z])[A-Za-z]:[\\\\/])";
9167
+ const structuredLabelPattern = "[A-Za-z][A-Za-z0-9 _-]{0,32}:";
9168
+ const relativePathStartPattern = "(?:\\.{1,2}\\/|[A-Za-z0-9_.-]+\\/)";
9169
+ const omissionMarkerPattern = "\\+\\d+\\s+more\\b";
9170
+ const localPathTerminatorPattern = `(?=$|[\\r\\n"'\\\`)\\]}]|\\s+(?:${localPathRootPattern}|${relativePathStartPattern}|${omissionMarkerPattern}|${structuredLabelPattern}))`;
9171
+ const localPathPattern = new RegExp(`${localPathRootPattern}(?:(?!${localPathTerminatorPattern}).)+`, "g");
9172
+ const pathRedacted = value.replace(localPathPattern, "<redacted-path>");
9173
+ return redactSafeString(pathRedacted).replace(localPathPattern, "<redacted-path>");
9170
9174
  }
9171
9175
  function publicSafeSearchText(value, maxChars = 500) {
9172
9176
  return publicSafeText(value, maxChars);
@@ -384,10 +384,10 @@ Record `npm dist-tag ls lossless-openclaw-orchestrator` in the release evidence
384
384
  after every npm publication. Stable releases publish with
385
385
  `npm publish --tag latest`; public betas publish with `npm publish --tag beta`;
386
386
  release candidates publish with `npm publish --tag next`. The stable channel
387
- currently points at `1.2.3`; future stable releases move `latest` only after the
388
- separate stable-promotion gate proves the exact candidate. Keep beta and other
389
- prereleases on prerelease tags. Do not publish a fake stable package just to
390
- move a dist-tag. Release candidates must publish with `npm publish --tag next`;
387
+ target for this package version is `1.2.4`; npm `latest` must move only after
388
+ the separate stable-promotion gate proves the exact candidate. Keep beta and
389
+ other prereleases on prerelease tags. Do not publish a fake stable package just
390
+ to move a dist-tag. Release candidates must publish with `npm publish --tag next`;
391
391
  RC branches also carry `package.json` `publishConfig.tag` set to `next` as a
392
392
  fail-closed guard against accidental untagged publication. Stable branches carry
393
393
  `package.json` `publishConfig.tag` set to `latest`. Do not run untagged
@@ -1,6 +1,6 @@
1
1
  # Public Claim Audit
2
2
 
3
- ## Allowed Stable 1.2.3 Claim
3
+ ## Allowed Stable 1.2.4 Claim
4
4
 
5
5
  Collaborate with local Codex sessions through OpenClaw using local indexing, prepared-state recall, bounded expansion, and approval-gated dry-run/control boundaries.
6
6
 
@@ -75,10 +75,10 @@ blockers instead of allowing a working-app claim.
75
75
 
76
76
  Install stable releases through the `latest` dist-tag, public betas through the
77
77
  `beta` dist-tag, and release candidates through `next`. The stable channel
78
- currently points at `1.2.3`; future stable releases move `latest` only after the
79
- separate stable-promotion gate proves the exact candidate. Keep beta and other
80
- prereleases on prerelease tags. Do not publish a fake stable package just to
81
- move a dist-tag.
78
+ target for this package version is `1.2.4`; npm `latest` must move only after
79
+ the separate stable-promotion gate proves the exact candidate. Keep beta and
80
+ other prereleases on prerelease tags. Do not publish a fake stable package just
81
+ to move a dist-tag.
82
82
 
83
83
  ## Release Checklist
84
84
 
@@ -0,0 +1,92 @@
1
+ # Release Notes 1.2.4
2
+
3
+ `1.2.4` is a GA-assurance privacy hygiene patch for the LCO 1.2 stable train.
4
+ It keeps the 1.2 claim boundary unchanged and fixes a public-safe rendering gap
5
+ found during the M11 launch packet pass.
6
+
7
+ ## What Changed
8
+
9
+ - Redacts local absolute paths from `loo expand-query`, `loo expand-ref`, and
10
+ `expandSession` human-readable expansion text.
11
+ - Keeps relative project paths visible where they are already public-safe, while
12
+ replacing local `/Volumes/...`, `/Users/...`, `.codex`, and tmp-style paths
13
+ with `<redacted-path>`.
14
+ - Keeps exact touched-file metadata in the local index for matching and recall;
15
+ only the rendered expansion text is sanitized.
16
+ - Updates bounded expansion snapshots and regression tests so future releases
17
+ fail if expansion evidence leaks local absolute paths.
18
+ - Carries forward the 1.2.3 recursive evidence scanner hardening for nested raw
19
+ artifacts, SQLite sidecars, symlinked evidence entries, and deep evidence
20
+ trees.
21
+ - Clarifies launch-truth wording from `1.2.0`/`1.2.3` to the current `1.2.4`
22
+ stable patch line.
23
+
24
+ ## Current Claim Scope
25
+
26
+ Claim scope: `codex-read-search-expand-dry-run`.
27
+
28
+ Allowed stable claim:
29
+
30
+ > Collaborate with local Codex sessions through OpenClaw using local indexing,
31
+ > prepared-state recall, bounded expansion, and approval-gated dry-run/control
32
+ > boundaries.
33
+
34
+ When finalization evidence proves this candidate on the stable channel, the
35
+ scoped prepared-state and cockpit-management surface is ready as a public
36
+ local-Codex release. It does not broaden the control, GUI, Claude, customer, or
37
+ enterprise-security proof boundary.
38
+
39
+ This remains a Codex-first local orchestration release.
40
+
41
+ ## Carried-Forward Desktop Proof Boundary
42
+
43
+ The Desktop proof-action hardening from #160 is still included. The public tool
44
+ surface includes `loo_desktop_proof_action` and the CLI command
45
+ `loo desktop proof-action`, but they remain bounded to the same proof boundary as beta.35: a CUA Driver TextEdit scratch proof only. The exact tuple is:
46
+ exact backend, target app, target window, action hash, approval ref, permission state, scratch file path, and `execute: true`.
47
+
48
+ A generic gateway invocation without exact proof args fails closed. The expected
49
+ OpenClaw failure shape is `openclaw_tool_result_not_ok:<tool>` with
50
+ `output.details.ok: false`; it is not proof of generic GUI mutation.
51
+
52
+ ## Release Gate Notes
53
+
54
+ - Parent 1.2 tracker: #405.
55
+ - GA assurance tracker: #478.
56
+ - Path-redaction issue: #508.
57
+ - Hygiene patch issue: #506.
58
+ - Baseline stable release: `v1.2.3`.
59
+ - Candidate package: `lossless-openclaw-orchestrator@1.2.4`.
60
+ - Expected npm dist-tag: `latest`.
61
+ - Expected git tag: `v1.2.4`.
62
+ - Example reduced-scope release preflight:
63
+ `loo release preflight --claim-scope codex-read-search-expand-dry-run --evidence-dir <public-safe-evidence> --strict`.
64
+ - Example release status with all required non-GUI approvals:
65
+ `loo release status --candidate-sha <sha> --github-ci-evidence <ci.json> --codeql-evidence <codeql.json> --approved-live-control-evidence <live-control.json> --npm-publish-approval-evidence <npm-approval.json> --github-release-approval-evidence <github-release-approval.json> --strict`.
66
+ - Reduced-scope release status may keep `approved_live_control_smoke_missing`
67
+ only when `--claim-scope codex-read-search-expand-dry-run` is explicit and
68
+ live Codex control is recorded as excluded.
69
+ - Required stable gates: focused path-redaction tests, full `npm run check`,
70
+ release preflight/bundle/status, `npm pack --dry-run`, GitHub CI, CodeQL,
71
+ review threads clear, npm publish to `latest`, GitHub Release, post-publish
72
+ finalization status, fresh npm install, OpenClaw dogfood/tool-smoke,
73
+ scenario/scorecard sweeps, and privacy scan.
74
+ - `loo release bundle` prepares public-safe local artifacts; it does not publish to npm and does not create a GitHub Release.
75
+
76
+ ## Explicit Non-Claims
77
+
78
+ No new live Codex control smoke is run by this release.
79
+ It does not run generic GUI mutation and does not run Codex GUI mutation.
80
+ No automatic gateway authorization.
81
+ No broad gateway scope approval. No prompt typing. No clicking. No arbitrary app control.
82
+ No screenshots or videos are part of the public release evidence.
83
+ Claude Code remains an adapter stub, not an adapter-equivalence claim.
84
+ No true Codex compaction-summary capture.
85
+ No raw model compaction by default and no default model access to raw transcript
86
+ or current `safe_text`.
87
+ No raw transcript upload and no OpenClaw LCM merge.
88
+ No source-store mutation.
89
+ No Notion, support-control, Stripe, or Company Brain P1 adapter proof.
90
+ No cloud sync.
91
+ No unattended desktop takeover.
92
+ No release-grade enterprise security or customer-ready security claim.
@@ -2,7 +2,7 @@
2
2
  "id": "lossless-openclaw-orchestrator",
3
3
  "name": "Lossless OpenClaw Orchestrator",
4
4
  "description": "Collaborate with local Codex sessions through OpenClaw using local indexing, prepared-state recall, bounded expansion, and approval-gated dry-run/control boundaries.",
5
- "version": "1.2.3",
5
+ "version": "1.2.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.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "Index, search, and prepare local Codex sessions for OpenClaw with approval-gated dry-run/control boundaries.",
5
5
  "type": "module",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -6531,13 +6531,13 @@ function formatClaudeSessionInventoryMetadata(description: ClaudeSessionInventor
6531
6531
  return [
6532
6532
  `Claude session ID: ${description.sessionId}`,
6533
6533
  `Ref: ${description.sourceRef}`,
6534
- description.title ? `Title: ${description.title}` : null,
6535
- description.project ? `Project: ${description.project}` : null,
6536
- description.workspaceHint ? `Workspace: ${description.workspaceHint}` : null,
6534
+ description.title ? `Title: ${publicSafeText(description.title, 500)}` : null,
6535
+ description.project ? `Project: ${publicSafeText(description.project, 500)}` : null,
6536
+ description.workspaceHint ? `Workspace: ${publicSafeText(description.workspaceHint, 500)}` : null,
6537
6537
  description.status ? `Status: ${description.status}` : null,
6538
6538
  description.updatedAt ? `Updated: ${description.updatedAt}` : null,
6539
- `Source path: ${description.sourcePath}`,
6540
- description.sourceRefs.length ? `Source refs: ${description.sourceRefs.join(", ")}` : null,
6539
+ `Source ref: ${publicSourcePathRef(description.sourcePath)}`,
6540
+ description.sourceRefs.length ? `Source refs: ${description.sourceRefs.map((ref) => publicSafeText(ref, 180)).join(", ")}` : null,
6541
6541
  "Proof boundary: read-only Claude metadata fixture inventory only; no private transcript content, live control, GUI mutation, parity, or cloud sync proof."
6542
6542
  ].filter(Boolean).join("\n");
6543
6543
  }
@@ -11405,15 +11405,15 @@ export function expandSession(db: LooDatabase, options: ExpandSessionOptions): E
11405
11405
  `Thread: ${description.title ?? description.threadId}`,
11406
11406
  `Ref: ${description.sourceRef}`,
11407
11407
  `ID: ${description.threadId}`,
11408
- description.cwd ? `CWD: ${description.cwd}` : null,
11408
+ description.cwd ? `CWD: ${publicSafeText(description.cwd, 500)}` : null,
11409
11409
  description.branch ? `Branch: ${description.branch}` : null,
11410
11410
  description.gitSha ? `Git SHA: ${description.gitSha}` : null,
11411
- description.summary ? `Summary: ${description.summary}` : null,
11411
+ description.summary ? `Summary: ${publicSafeText(description.summary, 2000)}` : null,
11412
11412
  formatSessionMetadata(description.metadata),
11413
11413
  `Plans: ${description.planCount}`,
11414
11414
  `Touched files: ${description.touchedFiles.length}`,
11415
11415
  `Tool calls: ${description.toolCallCount}`,
11416
- `Source path: ${description.sourcePath}`
11416
+ `Source ref: ${publicSourcePathRef(description.sourcePath)}`
11417
11417
  ].filter(Boolean).join("\n");
11418
11418
  return {
11419
11419
  sourceKind: "codex_thread",
@@ -11427,13 +11427,13 @@ export function expandSession(db: LooDatabase, options: ExpandSessionOptions): E
11427
11427
  const text = [
11428
11428
  `Thread: ${description.title ?? description.threadId}`,
11429
11429
  `ID: ${description.threadId}`,
11430
- description.cwd ? `CWD: ${description.cwd}` : null,
11430
+ description.cwd ? `CWD: ${publicSafeText(description.cwd, 500)}` : null,
11431
11431
  description.branch ? `Branch: ${description.branch}` : null,
11432
11432
  description.gitSha ? `Git SHA: ${description.gitSha}` : null,
11433
- description.summary ? `Summary: ${description.summary}` : null,
11434
- description.finalMessage ? `Final message: ${truncate(description.finalMessage, profile.name === "evidence" ? 3200 : 900)}` : null,
11433
+ description.summary ? `Summary: ${publicSafeText(description.summary, profile.name === "evidence" ? 3200 : 1600)}` : null,
11434
+ description.finalMessage ? `Final message: ${publicSafeText(description.finalMessage, profile.name === "evidence" ? 3200 : 900)}` : null,
11435
11435
  description.touchedFiles.length ? `Touched files:\n${formatTouchedFiles(description.touchedFiles, profile.name === "evidence" ? 50 : 12, profile.name === "evidence" ? 3200 : 900)}` : null,
11436
- plans.length ? `Plans:\n${plans.map((plan) => truncate(plan, profile.name === "evidence" ? 3200 : 1200)).join("\n\n")}` : null
11436
+ plans.length ? `Plans:\n${plans.map((plan) => publicSafeText(plan, profile.name === "evidence" ? 3200 : 1200)).join("\n\n")}` : null
11437
11437
  ].filter(Boolean).join("\n\n");
11438
11438
  return {
11439
11439
  sourceKind: "codex_thread",
@@ -11449,7 +11449,7 @@ function formatTouchedFiles(files: string[], limit: number, maxChars: number): s
11449
11449
  const perPathLimit = maxChars > 1000 ? 180 : 120;
11450
11450
  const visible: string[] = [];
11451
11451
  for (const file of files.slice(0, limit)) {
11452
- const next = `- ${truncate(file, perPathLimit)}`;
11452
+ const next = `- ${publicSafeText(file, perPathLimit)}`;
11453
11453
  const hiddenIfAccepted = files.length - (visible.length + 1);
11454
11454
  const markerIfAccepted = hiddenIfAccepted > 0 ? `- ... ${hiddenIfAccepted} more touched files omitted` : null;
11455
11455
  const visibleMaxChars = markerIfAccepted ? Math.max(0, maxChars - markerIfAccepted.length - 1) : maxChars;
@@ -11552,7 +11552,7 @@ export function expandRecallRef(db: LooDatabase, options: {
11552
11552
  const metadata = formatClaudeSessionInventoryMetadata(description);
11553
11553
  const text = profile.name === "metadata"
11554
11554
  ? metadata
11555
- : truncateByApproxTokens(`${metadata}\n\nSafe summary:\n${description.summary ?? ""}`, profile.tokenBudget);
11555
+ : truncateByApproxTokens(`${metadata}\n\nSafe summary:\n${publicSafeText(description.summary ?? "", profile.tokenBudget * 6)}`, profile.tokenBudget);
11556
11556
  return {
11557
11557
  sourceKind: "claude_session",
11558
11558
  sourceRef: description.sourceRef,
@@ -11568,18 +11568,18 @@ export function expandRecallRef(db: LooDatabase, options: {
11568
11568
  const metadata = [
11569
11569
  `Summary ID: ${summary.summaryId}`,
11570
11570
  `Ref: ${lcmSummaryRef(summary.sourcePath, summary.summaryId)}`,
11571
- `Conversation: ${summary.conversationTitle ?? summary.conversationId}`,
11571
+ `Conversation: ${publicSafeText(summary.conversationTitle ?? String(summary.conversationId), 500)}`,
11572
11572
  `Conversation ID: ${summary.conversationId}`,
11573
11573
  summary.kind ? `Kind: ${summary.kind}` : null,
11574
11574
  summary.depth !== null ? `Depth: ${summary.depth}` : null,
11575
11575
  summary.tokenCount !== null ? `Token count: ${summary.tokenCount}` : null,
11576
- summary.model ? `Model: ${summary.model}` : null,
11576
+ summary.model ? `Model: ${publicSafeText(summary.model, 120)}` : null,
11577
11577
  summary.updatedAt ? `Updated: ${summary.updatedAt}` : null,
11578
- `Source path: ${summary.sourcePath}`
11578
+ `Source ref: ${publicSourcePathRef(summary.sourcePath)}`
11579
11579
  ].filter(Boolean).join("\n");
11580
11580
  const text = profile.name === "metadata"
11581
11581
  ? metadata
11582
- : truncateByApproxTokens(`${metadata}\n\nContent:\n${summary.content}`, profile.tokenBudget);
11582
+ : truncateByApproxTokens(`${metadata}\n\nContent:\n${publicSafeText(summary.content, profile.tokenBudget * 6)}`, profile.tokenBudget);
11583
11583
  return {
11584
11584
  sourceKind: "lcm_summary",
11585
11585
  sourceRef: lcmSummaryRef(summary.sourcePath, summary.summaryId),
@@ -11855,7 +11855,7 @@ function lcmSummaryRecord(path: string, row: Record<string, unknown>): LcmSummar
11855
11855
  conversationTitle: nullableString(row.conversationTitle),
11856
11856
  kind: nullableString(row.kind),
11857
11857
  depth: row.depth === null || row.depth === undefined ? null : Number(row.depth),
11858
- content: redactSafeString(String(row.content ?? "")),
11858
+ content: redactPublicSafeString(String(row.content ?? "")),
11859
11859
  tokenCount: row.tokenCount === null || row.tokenCount === undefined ? null : Number(row.tokenCount),
11860
11860
  createdAt: nullableString(row.createdAt),
11861
11861
  updatedAt: nullableString(row.updatedAt),
@@ -11969,13 +11969,20 @@ function publicSourcePathRef(sourcePath: string): string {
11969
11969
  }
11970
11970
 
11971
11971
  function publicSafeText(value: string, maxChars = 500): string {
11972
- const redacted = redactSafeString(value)
11973
- .replace(/\/Volumes\/[^\s"'`)]+/g, "<redacted-path>")
11974
- .replace(/\/(?:Users|home)\/[^/\s"'`)]+\/\.codex\/[^\s"'`)]+/g, "<redacted-path>")
11975
- .replace(/\/root\/\.codex\/[^\s"'`)]+/g, "<redacted-path>")
11976
- .replace(/\/(?:private\/)?(?:tmp|var)\/[^\s"'`)]+/g, "<redacted-path>")
11977
- .replace(/~\/\.codex\/[^\s"'`)]+/g, "<redacted-path>");
11978
- return truncate(redacted, maxChars);
11972
+ return truncate(redactPublicSafeString(value), maxChars);
11973
+ }
11974
+
11975
+ function redactPublicSafeString(value: string): string {
11976
+ const localPathRootPattern =
11977
+ "(?:\\/Volumes\\/|\\/(?:Users|home|root)\\/|\\/(?:private\\/)?(?:tmp|var)\\/|~\\/|(?<![A-Za-z])[A-Za-z]:[\\\\/])";
11978
+ const structuredLabelPattern = "[A-Za-z][A-Za-z0-9 _-]{0,32}:";
11979
+ const relativePathStartPattern = "(?:\\.{1,2}\\/|[A-Za-z0-9_.-]+\\/)";
11980
+ const omissionMarkerPattern = "\\+\\d+\\s+more\\b";
11981
+ const localPathTerminatorPattern =
11982
+ `(?=$|[\\r\\n"'\\\`)\\]}]|\\s+(?:${localPathRootPattern}|${relativePathStartPattern}|${omissionMarkerPattern}|${structuredLabelPattern}))`;
11983
+ const localPathPattern = new RegExp(`${localPathRootPattern}(?:(?!${localPathTerminatorPattern}).)+`, "g");
11984
+ const pathRedacted = value.replace(localPathPattern, "<redacted-path>");
11985
+ return redactSafeString(pathRedacted).replace(localPathPattern, "<redacted-path>");
11979
11986
  }
11980
11987
 
11981
11988
  function publicSafeSearchText(value: string, maxChars = 500): string {
@@ -2,7 +2,7 @@
2
2
  "id": "lossless-openclaw-orchestrator",
3
3
  "name": "Lossless OpenClaw Orchestrator",
4
4
  "description": "Collaborate with local Codex sessions through OpenClaw using local indexing, prepared-state recall, bounded expansion, and approval-gated dry-run/control boundaries.",
5
- "version": "1.2.3",
5
+ "version": "1.2.4",
6
6
  "kind": "tool",
7
7
  "tools": {
8
8
  "prefix": "loo_"