lossless-openclaw-orchestrator 1.2.0-beta.0 → 1.2.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +56 -13
  2. package/VISION.md +14 -2
  3. package/assets/readme/hero.png +0 -0
  4. package/dist/packages/adapters/src/index.js +114 -13
  5. package/dist/packages/adapters/src/policy.js +1 -0
  6. package/dist/packages/cli/src/openclaw-tool-smoke.js +92 -7
  7. package/dist/packages/cli/src/scorecard-sweep.js +1 -0
  8. package/dist/packages/core/src/agent-provenance.js +346 -0
  9. package/dist/packages/core/src/index.js +596 -22
  10. package/dist/packages/core/src/model-compaction-canary.js +186 -0
  11. package/dist/packages/mcp-server/src/server.js +1 -1
  12. package/dist/packages/mcp-server/src/tools.js +326 -4
  13. package/dist/packages/openclaw-plugin/src/index.js +1 -0
  14. package/docs/CLAIM_AUDIT.md +12 -0
  15. package/docs/CODEX_NATIVE_COMPACTION_CAPTURE.md +110 -0
  16. package/docs/LOCAL_MODEL_COMPACTION_CANARY.md +27 -0
  17. package/docs/OPENCLAW_PLUGIN.md +34 -2
  18. package/docs/RELEASE_NOTES_1.2.0-beta.1.md +90 -0
  19. package/docs/RELEASE_NOTES_1.2.0-beta.2.md +93 -0
  20. package/docs/SETUP.md +136 -4
  21. package/docs/sprints/brief-lco-1.2-prepared-state-summary-leaves-2026-07-03.md +19 -2
  22. package/evals/scenarios/v1/codex-native-compaction-capture-proposal-v1.json +259 -0
  23. package/evals/scenarios/v1/local-model-compaction-canary-v1.json +60 -0
  24. package/evals/scenarios/v1/prepared-cards-inbox-v1.json +14 -4
  25. package/evals/scorecards/v1.0/README.md +6 -0
  26. package/evals/scorecards/v1.0/local-agent-usability-review.json +5 -4
  27. package/evals/scorecards/v1.0/safety-bypass-review.json +3 -3
  28. package/evals/scorecards/v1.0/tool-facade-usability-review.json +60 -0
  29. package/openclaw.plugin.json +280 -3
  30. package/package.json +2 -1
  31. package/packages/adapters/src/index.ts +126 -18
  32. package/packages/adapters/src/policy.ts +1 -0
  33. package/packages/cli/src/openclaw-tool-smoke.ts +92 -6
  34. package/packages/cli/src/scorecard-sweep.ts +1 -0
  35. package/packages/core/src/agent-provenance.ts +415 -0
  36. package/packages/core/src/index.ts +714 -23
  37. package/packages/core/src/model-compaction-canary.ts +314 -0
  38. package/packages/mcp-server/src/server.ts +1 -1
  39. package/packages/mcp-server/src/tools.ts +381 -4
  40. package/packages/openclaw-plugin/openclaw.plugin.json +280 -3
  41. package/packages/openclaw-plugin/src/index.ts +2 -0
  42. package/skills/lossless-openclaw-orchestrator/SKILL.md +47 -14
package/README.md CHANGED
@@ -3,9 +3,7 @@
3
3
  **LCO turns local Codex sessions into searchable, bounded, approval-aware
4
4
  work objects for OpenClaw.**
5
5
 
6
- Use it when an agent or user needs to answer: what sessions are active, what did
7
- they plan, what did they finish, what files did they touch, and what is the next
8
- safe action without rereading raw transcripts.
6
+ ![Enchanted open-claw conductor guiding bounded session cards and an abstract code beast in a dark technical workshop](assets/readme/hero.png)
9
7
 
10
8
  [![npm latest](https://img.shields.io/npm/v/lossless-openclaw-orchestrator/latest?label=npm%20latest)](https://www.npmjs.com/package/lossless-openclaw-orchestrator)
11
9
  [![npm beta](https://img.shields.io/npm/v/lossless-openclaw-orchestrator/beta?label=npm%20beta)](https://www.npmjs.com/package/lossless-openclaw-orchestrator)
@@ -14,6 +12,17 @@ safe action without rereading raw transcripts.
14
12
  [![License](https://img.shields.io/badge/license-PolyForm%20Noncommercial-blue)](LICENSE)
15
13
  [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](CONTRIBUTING.md)
16
14
 
15
+ Use it when an agent or user needs to answer: what sessions are active, what did
16
+ they plan, what did they finish, what files did they touch, and what is the next
17
+ safe action without rereading raw transcripts.
18
+
19
+ | What LCO gives agents | Why it matters |
20
+ | --- | --- |
21
+ | Searchable local session memory | Find plans, finals, touched files, and refs without raw transcript rereads. |
22
+ | Bounded evidence expansion | Read compact public-safe briefs before opening larger source material. |
23
+ | Approval-gated control | Dry-run Codex actions and verify matching audit ids before live control. |
24
+ | OpenClaw/MCP tools | Use the same local-first recall and control surfaces from agent workflows. |
25
+
17
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.0-beta.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)
18
27
 
19
28
  ## Why It Matters
@@ -113,6 +122,10 @@ loo doctor
113
122
  loo onboard status --strict
114
123
  ```
115
124
 
125
+ If agents will author PRs, issue comments, or closeouts in the repo, also copy
126
+ the provenance snippets from [docs/SETUP.md](docs/SETUP.md#agent-provenance-setup)
127
+ into the repo's `AGENTS.md` and `CLAUDE.md` files.
128
+
116
129
  ## First Workflow
117
130
 
118
131
  Search for a session:
@@ -139,16 +152,25 @@ Expand from a query when you do not know the ref yet:
139
152
  loo expand-query --profile brief --token-budget 1000 "billing bridge"
140
153
  ```
141
154
 
142
- For normal agent workflows, use the MCP/OpenClaw tools:
143
-
144
- - `loo_search_sessions`
145
- - `loo_describe_session`
146
- - `loo_expand_session`
147
- - `loo_codex_plans`
148
- - `loo_codex_final_messages`
149
- - `loo_codex_touched_files`
150
- - `loo_codex_control_dry_run`
151
- - `loo_codex_start_thread`
155
+ For normal agent workflows, start with the compact public/operator facade
156
+ instead of treating every `loo_*` tool as a peer:
157
+
158
+ | Step | Tool | Purpose |
159
+ | --- | --- | --- |
160
+ | 1 | `loo_prepared_inbox` | Start from the compact prepared-state operating picture. |
161
+ | 2 | `loo_describe_ref` | Look up a specific session or source ref from the inbox. |
162
+ | 3 | `loo_expand_query` | Expand one bounded evidence brief when the ref is not known. |
163
+ | 4 | `loo_recent_sessions` | Refresh recent or active cards after reads or approved actions. |
164
+ | 5 | `loo_attention_inbox` | Review the compact attention queue before choosing a next action. |
165
+ | 6 | `loo_project_digest` | Produce a bounded provenance and handoff digest. |
166
+ | 7 | `loo_codex_control_dry_run` | Create the exact dry-run action packet and approval hashes. |
167
+ | 8 | `loo_codex_resume_thread` | Run an approved resume only after the matching audit id. |
168
+
169
+ Other declared tools remain available as `workflow_detail`, `proof_debug`, or
170
+ `internal_low_level` surfaces for setup, diagnosis, proof, and recovery. Their
171
+ existence is deliberate: normal agents should start from the facade, then drop
172
+ to the lower tiers only when the compact path returns a specific next step or
173
+ blocker.
152
174
 
153
175
  `loo_codex_control_dry_run` returns the audit id and hashes an agent should show
154
176
  before any live start/resume/send/steer/interrupt call. Live control requires
@@ -163,6 +185,13 @@ before claiming the turn or thread completed, persisted, or is safe to build on.
163
185
  The packaged agent playbook is
164
186
  [skills/lossless-openclaw-orchestrator/SKILL.md](skills/lossless-openclaw-orchestrator/SKILL.md).
165
187
 
188
+ Naming policy: `LCO` is the public product abbreviation and `lco_*` is the
189
+ forward public alias target for new user-facing tool names. The currently
190
+ callable OpenClaw/MCP tools still use the historical `loo_*` runtime prefix, so
191
+ examples that must run today continue to show `loo_*` until #434 lands a tested
192
+ alias layer. Do not delete or silently rename the `loo_*` tools; keep them as
193
+ backward-compatible aliases when `lco_*` aliases are added.
194
+
166
195
  ## OpenClaw And MCP
167
196
 
168
197
  Run the MCP server directly:
@@ -214,6 +243,9 @@ Default behavior:
214
243
  - bounded expansion profiles
215
244
  - read-only OpenClaw LCM peer DB access
216
245
  - direct Codex protocol before desktop fallback
246
+ - CUA Driver as the preferred/default desktop fallback backend when desktop
247
+ fallback is needed; CUA is externally installed, not bundled by LCO, and
248
+ Peekaboo remains a secondary visible fallback
217
249
  - dry-run plus matching `approval_audit_id` before live Codex control,
218
250
  including new-thread creation
219
251
  - explicit mutation classes: pure reads use empty `mutationClasses`, and
@@ -236,6 +268,13 @@ storage and control paths are proven. Desktop fallback surfaces report
236
268
  readiness, blockers, and proof states; they do not authorize prompt typing,
237
269
  clicking, refresh/restart automation, or arbitrary app control.
238
270
 
271
+ Desktop fallback readiness is optional for normal read/search/describe
272
+ workflows. Operators who need fallback control should install CUA Driver
273
+ separately, verify the launch entrypoint with `cua-driver mcp --help`, then use
274
+ `loo doctor --json` or `loo desktop see cua-driver` only for LCO readiness and
275
+ blocker reporting. Treat missing CUA as a desktop-fallback readiness blocker
276
+ rather than a package install failure.
277
+
239
278
  Claude adapter proof boundaries live in
240
279
  [docs/CLAUDE_ADAPTER_BOUNDARY.md](docs/CLAUDE_ADAPTER_BOUNDARY.md). Public
241
280
  claim details live in [docs/CLAIM_AUDIT.md](docs/CLAIM_AUDIT.md).
@@ -301,6 +340,10 @@ OpenClaw/Eva agent can start from compact prepared state rather than rereading
301
340
  huge Codex transcripts. Summary leaves are advisory
302
341
  routing/evidence cards over prepared ranges; they are not authority, hidden
303
342
  autonomy, GUI mutation, Claude parity, or true Codex compaction-summary capture.
343
+ When a specific Codex thread id is requested, prepared-state status reports
344
+ thread-level `targetCoverage` with opaque source refs, freshness, coverage, and
345
+ reason codes such as `source_present_not_indexed` instead of hiding a miss
346
+ behind healthy global cache counts.
304
347
  The hook sidecar CLI lives under `loo hook closeout-capture`,
305
348
  `loo hook state-prep`, and `loo hook compaction-capture --mode marker`; those
306
349
  commands write only LCO-owned derived cache and treat transcript paths as
package/VISION.md CHANGED
@@ -24,7 +24,7 @@ The current target is:
24
24
  - Keep prepared state as advisory cache: cards, inbox items, and summary leaves route an agent to source refs; they do not become authority for PR/CI/release/runtime/customer truth.
25
25
  - Classify mutations explicitly: pure reads use empty `mutationClasses`, LCO-owned indexing/audit/prepared-state writes use `mode: "local_cache_write"` with `derived_cache`, and source-store, external-system, live-control, GUI, release, and npm mutations stay non-default.
26
26
  - Add source ranges and summary leaves as an additive DB layer behind the existing search/describe/expand tools instead of rewriting the current session-level DB in one migration. Source ranges are the first 1.2 proof slice; summary leaves sit on top as metadata-only routing/evidence cards with bounded DAG expansion.
27
- - Treat Codex compaction hooks outside Codex as marker capture only. True compaction-summary capture waits for Codex-native sanitized summary support.
27
+ - Treat Codex compaction hooks outside Codex as marker capture only. True compaction-summary capture waits for Codex-native sanitized summary support. The proposal is [docs/CODEX_NATIVE_COMPACTION_CAPTURE.md](docs/CODEX_NATIVE_COMPACTION_CAPTURE.md) and its dry-run claim-audit contract is `codex-native-compaction-capture-proposal-v1`.
28
28
  - Keep model compaction opt-in and behind a later spike. It must not receive raw transcripts or current `safe_text` by default.
29
29
  - Keep README, VISION, release notes, and scorecards current so completed release gates are recorded as completed proof, not active work.
30
30
  - 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.
@@ -50,6 +50,12 @@ What 1.2 should let a local OpenClaw agent do next:
50
50
  What a local OpenClaw agent can do today:
51
51
 
52
52
  - Discover the installed LCO plugin and declared `loo_*` tools.
53
+ - Start from the compact public facade (`loo_prepared_inbox`,
54
+ `loo_describe_ref`, `loo_expand_query`, `loo_recent_sessions`,
55
+ `loo_attention_inbox`, `loo_project_digest`, `loo_codex_control_dry_run`,
56
+ and approval-gated `loo_codex_resume_thread`) instead of treating every
57
+ declared tool as an equal first step. Expert/debug tools remain explicit for
58
+ detail, proof, and recovery.
53
59
  - Index and search local Codex sessions without reading raw transcripts.
54
60
  - Describe a session using metadata, source refs, status fields, plans, finals, touched files, and safe summaries.
55
61
  - Expand a selected session or query with bounded metadata, brief, or evidence profiles.
@@ -100,7 +106,10 @@ The 1.2 prepared-state layer is the next leverage step for this mode. It should
100
106
  store local, deterministic, public-safe prepared cards and summary leaves so an
101
107
  agent can operate from compact source-ref-backed state rather than repeatedly
102
108
  searching and expanding the same large threads. Summary leaves route the agent
103
- to the right source range; they do not replace authoritative sources.
109
+ to the right source range; they do not replace authoritative sources. Targeted
110
+ prepared-state reads must distinguish global cache health from a requested
111
+ thread's own coverage, including `source_present_not_indexed` style gaps when
112
+ the indexed source exists but prepared rows need refresh.
104
113
 
105
114
  Features should be prioritized by the `orchestrator-leverage-prioritization.json` scorecard when they change roadmap order. High-priority work gives the agent more session-management leverage per token: thread metadata, closeout hooks, project/status/priority tagging, archive and fork workflows, cited bounded expansion, and hybrid search when it improves top-k retrieval quality. Lower-priority work can still matter, but should wait when it makes the product more visually complete without reducing the orchestrator's rereading burden.
106
115
 
@@ -173,6 +182,7 @@ Versioned scorecards live under `evals/scorecards/v1.0/`. Use them as the shared
173
182
  - `orchestrator-leverage-prioritization.json`
174
183
  - `packaging-install-review.json`
175
184
  - `public-claim-review.json`
185
+ - `tool-facade-usability-review.json`
176
186
  - `local-agent-usability-review.json`
177
187
  - `local-mac-search-ui-review.json`
178
188
  - `working-app-runtime-proof-review.json`
@@ -208,6 +218,7 @@ For implementation issues, copy `evals/scorecards/v1.0/issue-scorecard-update-te
208
218
  | Public claims | README/docs/release notes stay inside allowed beta wording | claim audit result |
209
219
  | Privacy | Evidence contains no raw session files, SQLite DBs, screenshots, tokens, or secrets | artifact scan result |
210
220
  | Source authority | Operating-picture tools distinguish source availability from source ownership | `authorityCoverage`, degraded unavailable-source cards, source-authority profile |
221
+ | Tool facade usability | Agent starts from the 8-tool public facade, then drops to workflow/proof/debug tiers only when the facade returns a reason | `tool-facade-usability-review.json` score movement |
211
222
  | Cockpit card presentation | Agent-facing cards separate clean presentation text from source evidence and downgrade unclean extraction | `presentation_cleaned`, `presentation_low_confidence`, public-safe canary tests |
212
223
 
213
224
  ## Eval Scenarios
@@ -253,6 +264,7 @@ Core eval scenarios:
253
264
  - Prove the Milestone 7 working-app runtime path from `evals/scenarios/v1.1`: installed gateway, approved live Codex action, post-action refresh, and safe agent reasoning.
254
265
  - Prove `runtime-desktop-visibility-status-v1-1` when a release claim needs one compact lane-level Desktop visibility status report, while keeping actual GUI mutation and unattended collaboration out of scope.
255
266
  - Keep Claude Code behind the inventory in [docs/CLAUDE_ADAPTER_BOUNDARY.md](docs/CLAUDE_ADAPTER_BOUNDARY.md): the first adapter proof step is read-only session inventory, not control or parity.
267
+ - Run `codex-native-compaction-capture-proposal-v1` when auditing the future Codex-native compaction packet: outside-Codex markers stay on `compaction observed`, while sanitized `CompactionCaptured` or enriched `PostCompact` packets may only create advisory summary leaves with refs and omissions.
256
268
 
257
269
  ## Adversarial Milestone Sweeps
258
270
 
Binary file
@@ -272,15 +272,31 @@ function liveProofState(input) {
272
272
  threadId: proofThreadId,
273
273
  ...(turnId ? { turnId } : {}),
274
274
  ...(responseStatus ? { responseStatus } : {}),
275
- ...(acceptedByTransport && !persisted && proofThreadId !== "new_thread" ? { nextProof: {
276
- tool: "loo_codex_app_server_threads",
277
- execute: false,
278
- args: { read_thread_id: proofThreadId, limit: 20 },
279
- reason: "Bounded read-only follow-up proof is required before treating transport acceptance as durable Codex execution or persistence.",
280
- stopConditions: ["execute_false_only", "raw_transcript_not_read", "do_not_claim_completed_or_persisted_until_durable_read"]
281
- } } : {}),
275
+ ...(acceptedByTransport && !persisted && proofThreadId !== "new_thread" ? {
276
+ nextProof: input.method === "thread/start"
277
+ ? {
278
+ tool: "loo_codex_start_thread_post_create_proof",
279
+ execute: false,
280
+ args: {
281
+ created_thread_id: proofThreadId,
282
+ created_thread_ref: `codex_thread:${proofThreadId}`,
283
+ limit: 20
284
+ },
285
+ reason: "Run the bounded read-only post-create proof before treating transport acceptance as durable Codex thread creation or persistence.",
286
+ stopConditions: ["execute_false_only", "raw_transcript_not_read", "do_not_claim_created_or_persisted_until_post_create_proof"]
287
+ }
288
+ : {
289
+ tool: "loo_codex_app_server_threads",
290
+ execute: false,
291
+ args: { read_thread_id: proofThreadId, limit: 20 },
292
+ reason: "Bounded read-only follow-up proof is required before treating transport acceptance as durable Codex execution or persistence.",
293
+ stopConditions: ["execute_false_only", "raw_transcript_not_read", "do_not_claim_completed_or_persisted_until_durable_read"]
294
+ }
295
+ } : {}),
282
296
  callerInstruction: acceptedByTransport
283
- ? "Transport acceptance is not durable execution. Run the bounded follow-up proof before claiming the turn or thread completed, persisted, or is safe to build on."
297
+ ? input.method === "thread/start"
298
+ ? "Transport acceptance is not durable thread creation. Run the bounded post-create proof before claiming the new thread completed, persisted, or is safe to build on."
299
+ : "Transport acceptance is not durable execution. Run the bounded follow-up proof before claiming the turn or thread completed, persisted, or is safe to build on."
284
300
  : "Codex transport did not accept the control request. Do not retry live control without a fresh dry-run and approval.",
285
301
  proofBoundary: "This proof state is public-safe transport/output classification only. It does not read raw transcripts, prove durable local-session persistence, mutate the GUI, or claim completed orchestration without follow-up evidence."
286
302
  };
@@ -401,8 +417,9 @@ export async function createCodexAppServerThreadsReport(options) {
401
417
  let readProbe;
402
418
  let readProbeOk;
403
419
  if (options.readThreadId) {
404
- const threadId = capTextValue(options.readThreadId, 160);
405
- const read = await codexReadRequest(options.client, "thread/read", { threadId, includeTurns: false });
420
+ const requestedThreadId = options.readThreadId;
421
+ const threadId = capTextValue(requestedThreadId, 160);
422
+ const read = await codexReadRequest(options.client, "thread/read", { threadId: requestedThreadId, includeTurns: false });
406
423
  readProbeOk = read.ok;
407
424
  if (!read.ok) {
408
425
  if (read.error)
@@ -1594,6 +1611,7 @@ const threadStatusLabels = [
1594
1611
  "Failed",
1595
1612
  "Error"
1596
1613
  ];
1614
+ const threadStatusLabelSet = new Set(threadStatusLabels.map((label) => label.toLowerCase()));
1597
1615
  const threadSectionLabels = new Set(["pinned", "projects", "chats", "recent", "show more"]);
1598
1616
  const threadControlLabels = new Set(["archive chat", "automation folders", "automations", "unarchive chat", "pin chat", "unpin chat", "continue", "copy", "copy message", "new chat", "new thread", "search", "settings", "send", "plugins"]);
1599
1617
  const threadControlPrefixLabels = ["archive chat", "automation folders", "automations", "pin chat", "unarchive chat", "unpin chat"];
@@ -1643,11 +1661,17 @@ function visibleWindowsFromSnapshot(snapshot) {
1643
1661
  function visibleThreadMapFromSnapshot(snapshot) {
1644
1662
  const threads = [];
1645
1663
  const seen = new Set();
1664
+ const consumedChildElementIds = new Set();
1665
+ const consumedChildFingerprints = new Set();
1666
+ const acceptedElementFingerprints = new Set();
1646
1667
  let currentProject;
1647
1668
  let inProjects = false;
1648
1669
  for (const element of snapshot.elements) {
1649
1670
  if (threads.length >= snapshot.maxNodes)
1650
1671
  break;
1672
+ const elementFingerprint = sidebarChildFingerprint(element);
1673
+ if (consumedChildElementIds.has(element.elementId) || (elementFingerprint && consumedChildFingerprints.has(elementFingerprint)))
1674
+ continue;
1651
1675
  const rawLabel = element.label?.trim();
1652
1676
  if (!rawLabel || !isThreadCandidateRole(element.role))
1653
1677
  continue;
@@ -1660,23 +1684,36 @@ function visibleThreadMapFromSnapshot(snapshot) {
1660
1684
  currentProject = capTextValue(rawLabel, 160);
1661
1685
  continue;
1662
1686
  }
1663
- if (isThreadControlLabel(lowered))
1687
+ const childCandidate = isThreadControlLabel(lowered)
1688
+ ? sidebarThreadCandidateFromChildText(snapshot.elements, element)
1689
+ : null;
1690
+ if (isThreadControlLabel(lowered) && !childCandidate)
1691
+ continue;
1692
+ if (childCandidate?.titleElementFingerprint && acceptedElementFingerprints.has(childCandidate.titleElementFingerprint))
1664
1693
  continue;
1665
- const split = splitThreadTitleStatus(rawLabel);
1694
+ const split = childCandidate?.split ?? splitThreadTitleStatus(rawLabel);
1666
1695
  if (!split.title || isThreadControlLabel(split.title.toLowerCase()))
1667
1696
  continue;
1697
+ if (!isVisibleSidebarCandidateTitle({ title: split.title, rawLabel, element, childCandidate: Boolean(childCandidate), split }))
1698
+ continue;
1668
1699
  if (split.title.length < 3 || ["codex", "vantage"].includes(split.title.toLowerCase()))
1669
1700
  continue;
1670
1701
  const visibleId = visibleThreadId({ index: threads.length, title: split.title, sourceElementId: element.elementId });
1671
1702
  if (seen.has(visibleId))
1672
1703
  continue;
1673
1704
  seen.add(visibleId);
1705
+ for (const childId of childCandidate?.consumedElementIds ?? [])
1706
+ consumedChildElementIds.add(childId);
1707
+ for (const childFingerprint of childCandidate?.consumedElementFingerprints ?? [])
1708
+ consumedChildFingerprints.add(childFingerprint);
1709
+ if (elementFingerprint)
1710
+ acceptedElementFingerprints.add(elementFingerprint);
1674
1711
  const center = centerFromBounds(element.bounds);
1675
1712
  threads.push({
1676
1713
  visibleId,
1677
1714
  index: threads.length,
1678
1715
  title: split.title,
1679
- rawTitle: capTextValue(rawLabel, 200),
1716
+ rawTitle: capTextValue(childCandidate?.rawLabel ?? rawLabel, 200),
1680
1717
  project: currentProject,
1681
1718
  status: split.status,
1682
1719
  updatedLabel: split.updatedLabel,
@@ -1698,6 +1735,62 @@ function visibleThreadMapFromSnapshot(snapshot) {
1698
1735
  warnings: snapshot.truncated ? ["Snapshot was truncated before thread-map extraction; rerun with a larger bounded max_nodes value if more visible rows are needed."] : []
1699
1736
  };
1700
1737
  }
1738
+ function sidebarThreadCandidateFromChildText(elements, row) {
1739
+ if (!row.bounds)
1740
+ return null;
1741
+ const children = elements
1742
+ .filter((element) => element.elementId !== row.elementId && element.label && isStaticThreadRole(element.role) && rectContains(row.bounds, element.bounds))
1743
+ .sort((left, right) => (left.bounds?.y ?? 0) - (right.bounds?.y ?? 0) || (left.bounds?.x ?? 0) - (right.bounds?.x ?? 0));
1744
+ if (!children.length)
1745
+ return null;
1746
+ const updatedLabel = children.map((child) => child.label?.trim()).find((label) => Boolean(label && threadTimePattern.test(label)))
1747
+ ?? splitThreadTitleStatus(row.label ?? "").updatedLabel;
1748
+ const titleChild = children.find((child) => looksLikeSidebarThreadTitle(child.label ?? ""));
1749
+ const title = titleChild?.label?.trim();
1750
+ if (!title)
1751
+ return null;
1752
+ const rawLabel = [title, updatedLabel].filter(Boolean).join(" ");
1753
+ return {
1754
+ split: splitThreadTitleStatus(rawLabel),
1755
+ rawLabel,
1756
+ consumedElementIds: children.map((child) => stableElementId(child)).filter((id) => Boolean(id)),
1757
+ consumedElementFingerprints: children.map(sidebarChildFingerprint).filter((fingerprint) => Boolean(fingerprint)),
1758
+ titleElementFingerprint: sidebarChildFingerprint(titleChild)
1759
+ };
1760
+ }
1761
+ function looksLikeSidebarThreadTitle(label) {
1762
+ const trimmed = label.trim();
1763
+ if (!trimmed || trimmed.length < 3)
1764
+ return false;
1765
+ const lowered = trimmed.toLowerCase();
1766
+ if (threadSectionLabels.has(lowered) || isThreadControlLabel(lowered) || threadTimePattern.test(lowered) || threadStatusLabelSet.has(lowered))
1767
+ return false;
1768
+ return !/[\\/]/.test(trimmed) && !trimmed.includes("<redacted-");
1769
+ }
1770
+ function isVisibleSidebarCandidateTitle({ title, rawLabel, element, childCandidate, split }) {
1771
+ if (looksLikeSidebarThreadTitle(title))
1772
+ return true;
1773
+ if (childCandidate || isStaticThreadRole(element.role))
1774
+ return false;
1775
+ if (!title.includes("<redacted-"))
1776
+ return false;
1777
+ const rawSplit = splitThreadTitleStatus(rawLabel);
1778
+ return Boolean(split.status || split.updatedLabel || rawSplit.status || rawSplit.updatedLabel);
1779
+ }
1780
+ function stableElementId(element) {
1781
+ const id = element.elementId.trim();
1782
+ return id && id !== "unknown" ? id : undefined;
1783
+ }
1784
+ function sidebarChildFingerprint(element) {
1785
+ if (!element?.bounds || !element.label)
1786
+ return undefined;
1787
+ const label = element.label.trim();
1788
+ if (!label)
1789
+ return undefined;
1790
+ const role = (element.role || "").trim().toLowerCase();
1791
+ const { x, y, width, height } = element.bounds;
1792
+ return `${role}:${label}:${x}:${y}:${width}:${height}`;
1793
+ }
1701
1794
  function isCodexSnapshot(snapshot) {
1702
1795
  const app = (snapshot.frontmostApp || "").trim().toLowerCase();
1703
1796
  return app === "codex" || app === "codex desktop";
@@ -1756,6 +1849,14 @@ function centerFromBounds(bounds) {
1756
1849
  return undefined;
1757
1850
  return { x: Math.trunc(bounds.x + bounds.width / 2), y: Math.trunc(bounds.y + bounds.height / 2) };
1758
1851
  }
1852
+ function rectContains(parent, child) {
1853
+ if (!parent || !child)
1854
+ return false;
1855
+ return child.x >= parent.x
1856
+ && child.y >= parent.y
1857
+ && child.x + child.width <= parent.x + parent.width
1858
+ && child.y + child.height <= parent.y + parent.height;
1859
+ }
1759
1860
  function threadConfidence(input) {
1760
1861
  let score = 0;
1761
1862
  if (input.center)
@@ -128,6 +128,7 @@ export const LOO_COMMAND_POLICY = {
128
128
  loo_resume_request_packet: readOnly("structured_operating_inputs"),
129
129
  loo_codex_app_server_status: readOnly("codex_direct"),
130
130
  loo_codex_app_server_threads: readOnly("codex_direct"),
131
+ loo_codex_start_thread_post_create_proof: readOnly("codex_direct"),
131
132
  loo_visible_codex_map: readOnly("structured_operating_inputs"),
132
133
  loo_codex_desktop_coherence: readOnly("structured_operating_inputs"),
133
134
  loo_codex_desktop_fallback_status: readOnly("desktop_fallback"),
@@ -118,7 +118,7 @@ export function runOpenClawToolSmoke(options = {}) {
118
118
  confirm: false,
119
119
  idempotencyKey: `loo-tool-smoke-${runId}-${toolName}`
120
120
  }, gatewayCallOptions);
121
- const summary = summarizeInvocation(toolName, call);
121
+ const summary = summarizeInvocation(toolName, call, args ?? {});
122
122
  annotateRequestedExpansionProfile(summary, args);
123
123
  invocations.push(summary);
124
124
  blockers.push(...summary.blockers);
@@ -176,10 +176,18 @@ function buildAgentReasoning(invocations, blockers) {
176
176
  const byTool = new Map(invocations.map((invocation) => [invocation.toolName, invocation]));
177
177
  const search = byTool.get("loo_search_sessions");
178
178
  const describe = byTool.get("loo_describe_session");
179
- const expand = byTool.get("loo_expand_query") ?? byTool.get("loo_expand_session");
179
+ const preparedCards = byTool.get("loo_prepared_cards");
180
+ const preparedInbox = byTool.get("loo_prepared_inbox");
181
+ const summaryExpand = byTool.get("loo_summary_expand");
182
+ const expand = summaryExpand ?? byTool.get("loo_expand_query") ?? byTool.get("loo_expand_session");
180
183
  const dryRun = byTool.get("loo_codex_control_dry_run");
181
- const sourceRefs = [...new Set(invocations.flatMap((invocation) => invocation.summary.sourceRefs ?? []))].slice(0, 5);
182
- const selectedThreadId = describe?.summary.threadId || search?.summary.threadId || dryRun?.summary.threadId;
184
+ const sourceRefs = prioritizedReasoningSourceRefs(invocations, byTool).slice(0, 5);
185
+ const selectedThreadId = preparedInbox?.summary.threadId
186
+ || preparedCards?.summary.threadId
187
+ || summaryExpand?.summary.threadId
188
+ || describe?.summary.threadId
189
+ || search?.summary.threadId
190
+ || dryRun?.summary.threadId;
183
191
  const workflowEvidence = [
184
192
  ...(byTool.get("loo_doctor")?.ok ? ["doctor_ready"] : []),
185
193
  ...(sourceRefs.length ? ["search_source_ref"] : []),
@@ -188,6 +196,11 @@ function buildAgentReasoning(invocations, blockers) {
188
196
  ...(byTool.get("loo_codex_plans")?.ok ? ["plan_lookup"] : []),
189
197
  ...(byTool.get("loo_codex_final_messages")?.ok ? ["final_message_lookup"] : []),
190
198
  ...(byTool.get("loo_codex_touched_files")?.ok ? ["touched_files_lookup"] : []),
199
+ ...(byTool.get("loo_prepared_state_status")?.ok ? ["prepared_state_status"] : []),
200
+ ...(byTool.get("loo_prepared_cards")?.ok ? ["prepared_cards"] : []),
201
+ ...(byTool.get("loo_prepared_inbox")?.ok ? ["prepared_inbox"] : []),
202
+ ...(byTool.get("loo_summary_leaves")?.ok ? ["summary_leaf_lookup"] : []),
203
+ ...(byTool.get("loo_summary_expand")?.ok ? ["summary_expand"] : []),
191
204
  ...(dryRun?.summary.approvalAuditId && dryRun.summary.live === false ? ["dry_run_audit"] : [])
192
205
  ];
193
206
  if (!selectedThreadId || sourceRefs.length === 0 || !workflowEvidence.includes("bounded_expand"))
@@ -206,6 +219,24 @@ function buildAgentReasoning(invocations, blockers) {
206
219
  rawTranscriptRead: false
207
220
  };
208
221
  }
222
+ function prioritizedReasoningSourceRefs(invocations, byTool) {
223
+ const refs = [];
224
+ const seen = new Set();
225
+ const addRefs = (invocation) => {
226
+ for (const ref of invocation?.summary.sourceRefs ?? []) {
227
+ if (seen.has(ref))
228
+ continue;
229
+ seen.add(ref);
230
+ refs.push(ref);
231
+ }
232
+ };
233
+ for (const toolName of ["loo_prepared_inbox", "loo_prepared_cards", "loo_summary_expand", "loo_summary_leaves"]) {
234
+ addRefs(byTool.get(toolName));
235
+ }
236
+ for (const invocation of invocations)
237
+ addRefs(invocation);
238
+ return refs;
239
+ }
209
240
  function annotateRequestedExpansionProfile(summary, args) {
210
241
  if (summary.toolName !== "loo_expand_query" && summary.toolName !== "loo_expand_session")
211
242
  return;
@@ -509,8 +540,11 @@ function buildToolArgs(params) {
509
540
  token_budget: Math.min(params.tokenBudget, 1000)
510
541
  };
511
542
  }
512
- if (params.toolName === "loo_prepared_state_status")
513
- return {};
543
+ if (params.toolName === "loo_prepared_state_status") {
544
+ return {
545
+ ...(params.threadId ? { thread_id: params.threadId } : {})
546
+ };
547
+ }
514
548
  if (params.toolName === "loo_prepared_cards" || params.toolName === "loo_prepared_inbox") {
515
549
  return {
516
550
  ...(params.threadId ? { thread_id: params.threadId } : {}),
@@ -725,7 +759,7 @@ function smokeCodexDesktopCollaborationProofReport(threadId) {
725
759
  }
726
760
  };
727
761
  }
728
- function summarizeInvocation(toolName, call) {
762
+ function summarizeInvocation(toolName, call, requestArgs = {}) {
729
763
  const payload = call.parsed ? unwrapGatewayPayload(call.parsed) : undefined;
730
764
  const blockers = [
731
765
  ...gatewayFailureBlockers(call, `openclaw_tool_invoke_failed:${toolName}`, toolName),
@@ -1108,6 +1142,57 @@ function summarizeInvocation(toolName, call) {
1108
1142
  }
1109
1143
  if (!isRecord(statusOutput) || !isRecord(statusOutput.sourceCoverage))
1110
1144
  blockers.push("prepared_state_status_coverage_missing");
1145
+ const targetedThreadId = stringPath(requestArgs, ["thread_id"]);
1146
+ if (targetedThreadId && (!isRecord(statusOutput) || statusOutput.targetCoverage === undefined)) {
1147
+ blockers.push("prepared_state_status_target_coverage_missing");
1148
+ }
1149
+ if (isRecord(statusOutput) && statusOutput.targetCoverage !== undefined) {
1150
+ const targetCoverage = statusOutput.targetCoverage;
1151
+ if (!isRecord(targetCoverage))
1152
+ blockers.push("prepared_state_status_target_coverage_invalid");
1153
+ else {
1154
+ if (stringPath(targetCoverage, ["schema"]) !== "lco.prepared.targetCoverage.v1")
1155
+ blockers.push("prepared_state_status_target_coverage_schema_invalid");
1156
+ const targetSourceCoverage = isRecord(targetCoverage.sourceCoverage) ? targetCoverage.sourceCoverage : null;
1157
+ const targetCounts = isRecord(targetCoverage.counts) ? targetCoverage.counts : null;
1158
+ const targetFreshness = isRecord(targetCoverage.freshness) ? targetCoverage.freshness : null;
1159
+ const reasonCodes = arrayPath(targetCoverage, ["reasonCodes"]);
1160
+ const validLayerStatuses = new Set(["ok", "partial", "not_configured", "unknown"]);
1161
+ const requiredCoverageKeys = ["indexedSession", "sourceFile", "preparedSourceEvents", "preparedSourceRanges", "summaryLeaves", "preparedCards", "preparedInboxItems", "watcherObservations"];
1162
+ const requiredCountKeys = ["preparedSourceEvents", "preparedSourceRanges", "summaryLeaves", "preparedCards", "preparedInboxItems"];
1163
+ if (!targetSourceCoverage)
1164
+ blockers.push("prepared_state_status_target_source_coverage_missing");
1165
+ else if (requiredCoverageKeys.some((key) => !validLayerStatuses.has(String(targetSourceCoverage[key] ?? "")))) {
1166
+ blockers.push("prepared_state_status_target_source_coverage_invalid");
1167
+ }
1168
+ if (!targetCounts || requiredCountKeys.some((key) => {
1169
+ const value = targetCounts[key];
1170
+ return typeof value !== "number" || !Number.isFinite(value) || value < 0;
1171
+ }))
1172
+ blockers.push("prepared_state_status_target_coverage_details_missing");
1173
+ if (!targetFreshness || booleanPath(targetCoverage, ["freshness", "stale"]) === undefined) {
1174
+ blockers.push("prepared_state_status_target_coverage_details_missing");
1175
+ }
1176
+ if (!reasonCodes.length || reasonCodes.some((code) => typeof code !== "string" || !code)) {
1177
+ blockers.push("prepared_state_status_target_coverage_details_missing");
1178
+ }
1179
+ if (!stringPath(targetCoverage, ["nextAction"]))
1180
+ blockers.push("prepared_state_status_target_coverage_details_missing");
1181
+ if (!arrayPath(targetCoverage, ["sourceRefs"]).length)
1182
+ blockers.push("prepared_state_status_target_coverage_details_missing");
1183
+ if (targetedThreadId) {
1184
+ const targetThreadId = stringPath(targetCoverage, ["threadId"]) || stringPath(targetCoverage, ["thread_id"]);
1185
+ const targetRef = stringPath(targetCoverage, ["targetRef"]) || stringPath(targetCoverage, ["target_ref"]);
1186
+ if (targetThreadId !== targetedThreadId || targetRef !== `codex_thread:${targetedThreadId}`) {
1187
+ blockers.push("prepared_state_status_target_coverage_mismatch");
1188
+ }
1189
+ }
1190
+ const targetStatus = stringPath(targetCoverage, ["status"]);
1191
+ if (!["ready", "source_present_not_indexed", "not_found", "partial", "unknown"].includes(targetStatus ?? "")) {
1192
+ blockers.push("prepared_state_status_target_status_invalid");
1193
+ }
1194
+ }
1195
+ }
1111
1196
  if (!hasPreparedReadOnlyActionMarkers(statusOutput))
1112
1197
  blockers.push("prepared_state_status_action_markers_invalid");
1113
1198
  }
@@ -13,6 +13,7 @@ const REQUIRED_SCORECARD_NAMES = [
13
13
  "public-claim-review",
14
14
  "retrieval-quality-review",
15
15
  "safety-bypass-review",
16
+ "tool-facade-usability-review",
16
17
  "working-app-runtime-proof-review"
17
18
  ];
18
19
  const REQUIRED_SCORECARD_FIELDS = [