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.
- package/README.md +56 -13
- package/VISION.md +14 -2
- package/assets/readme/hero.png +0 -0
- package/dist/packages/adapters/src/index.js +114 -13
- package/dist/packages/adapters/src/policy.js +1 -0
- package/dist/packages/cli/src/openclaw-tool-smoke.js +92 -7
- package/dist/packages/cli/src/scorecard-sweep.js +1 -0
- package/dist/packages/core/src/agent-provenance.js +346 -0
- package/dist/packages/core/src/index.js +596 -22
- package/dist/packages/core/src/model-compaction-canary.js +186 -0
- package/dist/packages/mcp-server/src/server.js +1 -1
- package/dist/packages/mcp-server/src/tools.js +326 -4
- package/dist/packages/openclaw-plugin/src/index.js +1 -0
- package/docs/CLAIM_AUDIT.md +12 -0
- package/docs/CODEX_NATIVE_COMPACTION_CAPTURE.md +110 -0
- package/docs/LOCAL_MODEL_COMPACTION_CANARY.md +27 -0
- package/docs/OPENCLAW_PLUGIN.md +34 -2
- package/docs/RELEASE_NOTES_1.2.0-beta.1.md +90 -0
- package/docs/RELEASE_NOTES_1.2.0-beta.2.md +93 -0
- package/docs/SETUP.md +136 -4
- package/docs/sprints/brief-lco-1.2-prepared-state-summary-leaves-2026-07-03.md +19 -2
- package/evals/scenarios/v1/codex-native-compaction-capture-proposal-v1.json +259 -0
- package/evals/scenarios/v1/local-model-compaction-canary-v1.json +60 -0
- package/evals/scenarios/v1/prepared-cards-inbox-v1.json +14 -4
- package/evals/scorecards/v1.0/README.md +6 -0
- package/evals/scorecards/v1.0/local-agent-usability-review.json +5 -4
- package/evals/scorecards/v1.0/safety-bypass-review.json +3 -3
- package/evals/scorecards/v1.0/tool-facade-usability-review.json +60 -0
- package/openclaw.plugin.json +280 -3
- package/package.json +2 -1
- package/packages/adapters/src/index.ts +126 -18
- package/packages/adapters/src/policy.ts +1 -0
- package/packages/cli/src/openclaw-tool-smoke.ts +92 -6
- package/packages/cli/src/scorecard-sweep.ts +1 -0
- package/packages/core/src/agent-provenance.ts +415 -0
- package/packages/core/src/index.ts +714 -23
- package/packages/core/src/model-compaction-canary.ts +314 -0
- package/packages/mcp-server/src/server.ts +1 -1
- package/packages/mcp-server/src/tools.ts +381 -4
- package/packages/openclaw-plugin/openclaw.plugin.json +280 -3
- package/packages/openclaw-plugin/src/index.ts +2 -0
- 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
|
-
|
|
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
|
+

|
|
9
7
|
|
|
10
8
|
[](https://www.npmjs.com/package/lossless-openclaw-orchestrator)
|
|
11
9
|
[](https://www.npmjs.com/package/lossless-openclaw-orchestrator)
|
|
@@ -14,6 +12,17 @@ safe action without rereading raw transcripts.
|
|
|
14
12
|
[](LICENSE)
|
|
15
13
|
[](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,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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" ? {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
?
|
|
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
|
|
405
|
-
const
|
|
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
|
-
|
|
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
|
|
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 =
|
|
182
|
-
const selectedThreadId =
|
|
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
|
}
|