lossless-openclaw-orchestrator 1.2.0-beta.2 → 1.2.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packages/cli/src/openclaw-tool-smoke.js +3 -1
- package/dist/packages/core/src/index.js +228 -21
- package/dist/packages/mcp-server/src/tools.js +4 -4
- package/docs/RELEASE_NOTES_1.2.0-beta.3.md +95 -0
- package/evals/scenarios/v1/prepared-cards-inbox-v1.json +9 -5
- package/openclaw.plugin.json +11 -2
- package/package.json +1 -1
- package/packages/cli/src/openclaw-tool-smoke.ts +4 -1
- package/packages/core/src/index.ts +224 -22
- package/packages/mcp-server/src/tools.ts +4 -3
- package/packages/openclaw-plugin/openclaw.plugin.json +11 -2
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, isAbsolute } from "node:path";
|
|
5
|
+
import { PREPARED_CARD_STATES } from "../../core/src/index.js";
|
|
5
6
|
export const DEFAULT_REQUIRED_TOOL_CALLS = [
|
|
6
7
|
"loo_doctor",
|
|
7
8
|
"loo_search_sessions",
|
|
@@ -40,6 +41,7 @@ export const DEFAULT_REQUIRED_TOOL_CALLS = [
|
|
|
40
41
|
"loo_prepared_cards",
|
|
41
42
|
"loo_prepared_inbox"
|
|
42
43
|
];
|
|
44
|
+
const PREPARED_CARD_STATE_SET = new Set(PREPARED_CARD_STATES);
|
|
43
45
|
const AUTONOMY_TICK_SUMMARY_KEYS = [
|
|
44
46
|
"totalLanes",
|
|
45
47
|
"returnedSteps",
|
|
@@ -1214,7 +1216,7 @@ function summarizeInvocation(toolName, call, requestArgs = {}) {
|
|
|
1214
1216
|
const state = stringPath(card, ["state"]);
|
|
1215
1217
|
return !cardRef?.startsWith("prepared_card:")
|
|
1216
1218
|
|| !targetRef?.startsWith("codex_thread:")
|
|
1217
|
-
|| !
|
|
1219
|
+
|| !PREPARED_CARD_STATE_SET.has(state ?? "");
|
|
1218
1220
|
}))
|
|
1219
1221
|
blockers.push("prepared_cards_public_refs_invalid");
|
|
1220
1222
|
}
|
|
@@ -14,6 +14,21 @@ function getDatabaseSync() {
|
|
|
14
14
|
}
|
|
15
15
|
return cachedDatabaseSync;
|
|
16
16
|
}
|
|
17
|
+
export const PREPARED_CARD_STATES = [
|
|
18
|
+
"ready",
|
|
19
|
+
"stale",
|
|
20
|
+
"partial",
|
|
21
|
+
"unknown",
|
|
22
|
+
"completed",
|
|
23
|
+
"blocked_missing_info",
|
|
24
|
+
"waiting_approval",
|
|
25
|
+
"watching_external_check",
|
|
26
|
+
"needs_resume",
|
|
27
|
+
"dirty_worktree_handoff",
|
|
28
|
+
"stale_or_partial",
|
|
29
|
+
"ready_for_review",
|
|
30
|
+
"unknown_lifecycle"
|
|
31
|
+
];
|
|
17
32
|
const COLLABORATION_COCKPIT_INTERNAL_CARD_LIMIT = 5000;
|
|
18
33
|
const OPERATING_SOURCE_KINDS = [
|
|
19
34
|
"lco",
|
|
@@ -1581,6 +1596,7 @@ export function getPreparedCards(db, options = {}) {
|
|
|
1581
1596
|
let stale = 0;
|
|
1582
1597
|
let partial = 0;
|
|
1583
1598
|
let unknown = 0;
|
|
1599
|
+
let completed = 0;
|
|
1584
1600
|
let lowConfidence = 0;
|
|
1585
1601
|
for (const row of rows) {
|
|
1586
1602
|
const card = publicPreparedCardFromRow(row);
|
|
@@ -1588,12 +1604,14 @@ export function getPreparedCards(db, options = {}) {
|
|
|
1588
1604
|
filteredUnsafeRows += 1;
|
|
1589
1605
|
continue;
|
|
1590
1606
|
}
|
|
1591
|
-
if (card.stale)
|
|
1607
|
+
if (card.stale || card.state === "stale")
|
|
1592
1608
|
stale += 1;
|
|
1593
|
-
if (card.state
|
|
1609
|
+
if (preparedCardCountsAsPartialSummary(card.state))
|
|
1594
1610
|
partial += 1;
|
|
1595
|
-
if (card.state === "unknown")
|
|
1611
|
+
if (card.state === "unknown" || card.state === "unknown_lifecycle")
|
|
1596
1612
|
unknown += 1;
|
|
1613
|
+
if (card.state === "completed")
|
|
1614
|
+
completed += 1;
|
|
1597
1615
|
if (card.confidence < 0.5)
|
|
1598
1616
|
lowConfidence += 1;
|
|
1599
1617
|
if (validCards.length < limit)
|
|
@@ -1631,6 +1649,7 @@ export function getPreparedCards(db, options = {}) {
|
|
|
1631
1649
|
stale,
|
|
1632
1650
|
partial,
|
|
1633
1651
|
unknown,
|
|
1652
|
+
completed,
|
|
1634
1653
|
lowConfidence
|
|
1635
1654
|
},
|
|
1636
1655
|
cards: validCards,
|
|
@@ -1929,9 +1948,19 @@ function buildPreparedCardDraft(db, threadId, leaves, filteredUnsafeRows) {
|
|
|
1929
1948
|
const watcherObservationsStatus = watcherObservationCoverageForTarget(db, targetRef);
|
|
1930
1949
|
const freshnessAt = latestIso([...leaves.map((leaf) => leaf.freshnessAt), session?.updatedAt ?? null]);
|
|
1931
1950
|
const stale = leaves.some((leaf) => leaf.stale);
|
|
1951
|
+
const evidenceState = stale
|
|
1952
|
+
? "stale_or_partial"
|
|
1953
|
+
: summaryLeavesStatus === "unknown"
|
|
1954
|
+
? "unknown_lifecycle"
|
|
1955
|
+
: summaryLeavesStatus === "partial" || sessionMetadataStatus === "partial"
|
|
1956
|
+
? "stale_or_partial"
|
|
1957
|
+
: "ready";
|
|
1958
|
+
const lifecycle = preparedLifecycleFromMetadata(metadata, evidenceState);
|
|
1959
|
+
const state = lifecycle.state;
|
|
1932
1960
|
const reasonCodes = unique([
|
|
1933
1961
|
leaves.length > 0 ? "summary_leaves_ready" : "summary_leaves_missing",
|
|
1934
1962
|
"metadata_only",
|
|
1963
|
+
...lifecycle.reasonCodes,
|
|
1935
1964
|
watcherObservationsStatus === "not_configured" ? "watcher_not_configured" : "watcher_observations_available",
|
|
1936
1965
|
stale ? "stale_cache" : "",
|
|
1937
1966
|
summaryLeavesStatus === "partial" ? "authority_partial" : "",
|
|
@@ -1941,17 +1970,10 @@ function buildPreparedCardDraft(db, threadId, leaves, filteredUnsafeRows) {
|
|
|
1941
1970
|
watcherObservationsStatus === "unknown" ? "watcher_observations_unknown" : "",
|
|
1942
1971
|
filteredUnsafeRows > 0 ? "filtered_unsafe_rows" : ""
|
|
1943
1972
|
].filter(Boolean));
|
|
1944
|
-
const state = stale
|
|
1945
|
-
? "stale"
|
|
1946
|
-
: summaryLeavesStatus === "unknown"
|
|
1947
|
-
? "unknown"
|
|
1948
|
-
: summaryLeavesStatus === "partial" || sessionMetadataStatus === "partial"
|
|
1949
|
-
? "partial"
|
|
1950
|
-
: "ready";
|
|
1951
1973
|
const averageLeafConfidence = leaves.length
|
|
1952
1974
|
? leaves.reduce((sum, leaf) => sum + leaf.confidence, 0) / leaves.length
|
|
1953
1975
|
: 0.3;
|
|
1954
|
-
const confidence = preparedCardConfidence(averageLeafConfidence, state);
|
|
1976
|
+
const confidence = preparedCardConfidence(averageLeafConfidence, state, evidenceState);
|
|
1955
1977
|
const sourceRangeRefsFull = unique(leaves.flatMap((leaf) => leaf.sourceRangeRefs));
|
|
1956
1978
|
const sourceRangeRefs = sourceRangeRefsFull.slice(0, PREPARED_CARD_SOURCE_RANGE_REF_LIMIT);
|
|
1957
1979
|
const sourceRangeRefsOmitted = Math.max(0, sourceRangeRefsFull.length - sourceRangeRefs.length)
|
|
@@ -1965,7 +1987,7 @@ function buildPreparedCardDraft(db, threadId, leaves, filteredUnsafeRows) {
|
|
|
1965
1987
|
role: "title"
|
|
1966
1988
|
});
|
|
1967
1989
|
const title = looksSensitiveRefLike(cleanedTitle.text) ? publicSafeText(safeThreadId(threadId), 160) : cleanedTitle.text;
|
|
1968
|
-
const summaryText = publicSafeText(`Prepared state: ${leaves.length} summary leaf${leaves.length === 1 ? "" : "s"} across ${Math.max(0, leafRangeCount)} prepared source range${leafRangeCount === 1 ? "" : "s"}.`, 320);
|
|
1990
|
+
const summaryText = publicSafeText(`Prepared state: ${leaves.length} summary leaf${leaves.length === 1 ? "" : "s"} across ${Math.max(0, leafRangeCount)} prepared source range${leafRangeCount === 1 ? "" : "s"}. Lifecycle: ${preparedCardStateLabel(state)}.`, 320);
|
|
1969
1991
|
const nextAction = preparedCardNextAction(state);
|
|
1970
1992
|
const authorityCoverage = {
|
|
1971
1993
|
summaryLeaves: {
|
|
@@ -1985,6 +2007,10 @@ function buildPreparedCardDraft(db, threadId, leaves, filteredUnsafeRows) {
|
|
|
1985
2007
|
leafRefs: leaves.map((leaf) => leaf.leafRef),
|
|
1986
2008
|
inputHashes: leaves.map((leaf) => leaf.inputHash),
|
|
1987
2009
|
outputHashes: leaves.map((leaf) => leaf.outputHash),
|
|
2010
|
+
metadataSignalHash: lifecycle.metadataSignalHash,
|
|
2011
|
+
lifecycleState: state,
|
|
2012
|
+
lifecycleReasonCodes: lifecycle.reasonCodes,
|
|
2013
|
+
evidenceState,
|
|
1988
2014
|
freshnessAt,
|
|
1989
2015
|
stale,
|
|
1990
2016
|
summaryLeavesStatus,
|
|
@@ -2020,7 +2046,7 @@ function buildPreparedCardDraft(db, threadId, leaves, filteredUnsafeRows) {
|
|
|
2020
2046
|
function preparedInboxItemFromCard(card) {
|
|
2021
2047
|
const reasonCodes = unique([
|
|
2022
2048
|
...card.reasonCodes,
|
|
2023
|
-
card.state === "ready" ? "prepared_card_ready" : "needs_attention"
|
|
2049
|
+
card.state === "ready" ? "prepared_card_ready" : card.state === "completed" ? "prepared_card_completed" : "needs_attention"
|
|
2024
2050
|
]);
|
|
2025
2051
|
const urgencyScore = preparedInboxUrgencyScore(card, reasonCodes);
|
|
2026
2052
|
return {
|
|
@@ -2289,7 +2315,7 @@ function getPreparedTargetCoverage(db, threadId) {
|
|
|
2289
2315
|
preparedSourceEvents: preparedSourceEvents.count > 0 ? "ok" : "not_configured",
|
|
2290
2316
|
preparedSourceRanges: preparedSourceRanges.publicCount > 0 ? "ok" : preparedSourceRanges.rawCount > 0 ? "partial" : "not_configured",
|
|
2291
2317
|
summaryLeaves: summaryLeaves.publicCount > 0 ? "ok" : summaryLeaves.rawCount > 0 ? "partial" : "not_configured",
|
|
2292
|
-
preparedCards: publicCard ? publicCard
|
|
2318
|
+
preparedCards: publicCard ? preparedTargetCardCoverage(publicCard) : preparedCards > 0 ? "partial" : "not_configured",
|
|
2293
2319
|
preparedInboxItems: publicCard && preparedInboxItems.publicCount > 0 ? "ok" : preparedInboxItems.rawCount > 0 ? "partial" : "not_configured",
|
|
2294
2320
|
watcherObservations: watcherObservationCoverageForTarget(db, targetRef)
|
|
2295
2321
|
};
|
|
@@ -2314,8 +2340,7 @@ function getPreparedTargetCoverage(db, threadId) {
|
|
|
2314
2340
|
const partialDerivedCache = requiredCoverage.some((state) => state === "partial");
|
|
2315
2341
|
const allDerivedLayersMissing = requiredCoverage.every((state) => state === "not_configured");
|
|
2316
2342
|
const stale = Boolean(session && (missingDerivedCache
|
|
2317
|
-
|| publicCard
|
|
2318
|
-
|| publicCard?.state !== "ready"
|
|
2343
|
+
|| (publicCard ? preparedTargetCardCoverage(publicCard) !== "ok" : false)
|
|
2319
2344
|
|| summaryLeaves.staleCount > 0
|
|
2320
2345
|
|| (sourceUpdatedAt && preparedFreshnessAt && sourceUpdatedAt > preparedFreshnessAt)));
|
|
2321
2346
|
const anyDerivedRows = preparedSourceEvents.count + preparedSourceRanges.rawCount + summaryLeaves.rawCount + preparedCards + preparedInboxItems.rawCount > 0;
|
|
@@ -2353,6 +2378,18 @@ function getPreparedTargetCoverage(db, threadId) {
|
|
|
2353
2378
|
nextAction: preparedTargetNextAction(status)
|
|
2354
2379
|
};
|
|
2355
2380
|
}
|
|
2381
|
+
function preparedTargetCardCoverage(card) {
|
|
2382
|
+
if (card.stale)
|
|
2383
|
+
return "partial";
|
|
2384
|
+
return preparedCardStateHasFreshTargetCoverage(card.state) ? "ok" : "partial";
|
|
2385
|
+
}
|
|
2386
|
+
function preparedCardStateHasFreshTargetCoverage(state) {
|
|
2387
|
+
return state !== "stale"
|
|
2388
|
+
&& state !== "partial"
|
|
2389
|
+
&& state !== "stale_or_partial"
|
|
2390
|
+
&& state !== "unknown"
|
|
2391
|
+
&& state !== "unknown_lifecycle";
|
|
2392
|
+
}
|
|
2356
2393
|
function countPreparedTargetRows(db, sql, ...params) {
|
|
2357
2394
|
return Number(db.prepare(sql).get(...params)?.count ?? 0);
|
|
2358
2395
|
}
|
|
@@ -2545,7 +2582,7 @@ function preparedCoverageState(value) {
|
|
|
2545
2582
|
return value === "ok" || value === "partial" || value === "not_configured" || value === "unknown" ? value : "unknown";
|
|
2546
2583
|
}
|
|
2547
2584
|
function isPreparedCardState(value) {
|
|
2548
|
-
return value
|
|
2585
|
+
return PREPARED_CARD_STATES.includes(value);
|
|
2549
2586
|
}
|
|
2550
2587
|
function isPublicPreparedSourceRef(value) {
|
|
2551
2588
|
if (value.startsWith("codex_thread:"))
|
|
@@ -2569,7 +2606,125 @@ function isPublicCodexSubagentResultRef(value) {
|
|
|
2569
2606
|
}
|
|
2570
2607
|
return /^[A-Za-z0-9._:-]{1,160}$/.test(decodedId) && !looksSensitiveRefLike(decodedId);
|
|
2571
2608
|
}
|
|
2572
|
-
function
|
|
2609
|
+
function preparedLifecycleFromMetadata(metadata, evidenceState) {
|
|
2610
|
+
const signals = {
|
|
2611
|
+
status: normalizedMetadataValue(metadata.status),
|
|
2612
|
+
blocker: normalizedMetadataValue(metadata.blocker),
|
|
2613
|
+
nextAction: normalizedMetadataValue(metadata.nextAction),
|
|
2614
|
+
closeoutState: normalizedMetadataValue(metadata.closeoutState),
|
|
2615
|
+
planCompletionState: normalizedMetadataValue(metadata.planCompletionState)
|
|
2616
|
+
};
|
|
2617
|
+
const metadataSignalHash = stableId(JSON.stringify(signals));
|
|
2618
|
+
const nonBlockerText = [signals.status, signals.nextAction, signals.closeoutState, signals.planCompletionState].filter(Boolean).join(" ");
|
|
2619
|
+
const text = [nonBlockerText, signals.blocker].filter(Boolean).join(" ");
|
|
2620
|
+
const sourceReasonCodes = Object.entries(signals)
|
|
2621
|
+
.filter(([, value]) => value.length > 0)
|
|
2622
|
+
.map(([field]) => `lifecycle_signal:${field.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`)}`);
|
|
2623
|
+
const completedByStatus = lifecycleCompletionLike(signals.status);
|
|
2624
|
+
const completedByCloseoutAndPlan = lifecycleCompletionLike(signals.closeoutState)
|
|
2625
|
+
&& lifecycleCompletionLike(signals.planCompletionState);
|
|
2626
|
+
const completed = completedByStatus || completedByCloseoutAndPlan;
|
|
2627
|
+
const dirtyHandoff = /\b(?:dirty[-_ ]?worktree|uncommitted|worktree[-_ ]?handoff|dirty[-_ ]?handoff|handoff[-_ ]?dirty|cleanup[-_ ]?required)\b/.test(text);
|
|
2628
|
+
const waitingApproval = /\b(?:waiting[-_ ]?(?:for[-_ ]?)?approval|needs[-_ ]?approval|requires[-_ ]?approval|approval[-_ ]?(?:required|needed|pending)|pending[-_ ]?approval|approval[-_ ]?gate|do[-_ ]?not[-_ ]?execute[-_ ]?without[-_ ]?explicit[-_ ]?approval)\b/.test(text);
|
|
2629
|
+
const needsResume = /\b(?:needs[-_ ]?(?:to[-_ ]?)?resume|resume[-_ ]?(?:session|thread|work|run|lane|needed|required|requested|request)|continue[-_ ]?(?:session|thread|lane)|nudge[-_ ]?(?:session|thread|agent|lane)|rejoin[-_ ]?(?:session|thread|lane))\b/.test(text);
|
|
2630
|
+
const watchingExternalCheck = /\b(?:(?:watch|watching|monitor|monitoring)[-_ ]+(?:ci|checks?|codeql|coderabbit|deploy(?:[-_ ]?check)?|external[-_ ]?(?:check|review)|review[-_ ]?check)|(?:waiting[-_ ]?(?:on|for)|pending)[-_ ]+(?:ci|checks?|codeql|coderabbit|deploy|external[-_ ]?check|review[-_ ]?check)|(?:ci|checks?|codeql|coderabbit|deploy[-_ ]?check|review[-_ ]?check)[-_ ]+(?:pending|queued|running|failing|failed|blocked|red|not[-_ ]?green|in[-_ ]?progress))\b/.test(text);
|
|
2631
|
+
const readyForReview = /\b(?:ready[-_ ]?for[-_ ]?review|review[-_ ]?ready|ready[-_ ]?to[-_ ]?review|pr[-_ ]?ready|needs[-_ ]?review)\b/.test(text);
|
|
2632
|
+
const negatedBlocker = /\b(?:not[-_ ]?blocked|no[-_ ]?blocker|unblocked|without[-_ ]?blocker|blocker[-_ ]?(?:none|na|n[-_ ]?a))\b/.test(text);
|
|
2633
|
+
const missingInfoSignal = /\b(?:missing[-_ ]?(?:info|input|context)|needs[-_ ]?(?:info|input|context)|waiting[-_ ]?(?:on|for)[-_ ]?(?:user|operator|human|input|context)|cannot[-_ ]?proceed)\b/.test(nonBlockerText);
|
|
2634
|
+
const blockedSignal = !negatedBlocker && /\b(?:blocked|blocker)\b/.test(nonBlockerText);
|
|
2635
|
+
const blockedMissingInfo = !waitingApproval
|
|
2636
|
+
&& !dirtyHandoff
|
|
2637
|
+
&& !watchingExternalCheck
|
|
2638
|
+
&& !negatedBlocker
|
|
2639
|
+
&& (hasRealBlocker(metadata.blocker) || missingInfoSignal || blockedSignal);
|
|
2640
|
+
const matchedLifecycleStates = [
|
|
2641
|
+
completed ? "completed" : null,
|
|
2642
|
+
dirtyHandoff ? "dirty_worktree_handoff" : null,
|
|
2643
|
+
waitingApproval ? "waiting_approval" : null,
|
|
2644
|
+
blockedMissingInfo ? "blocked_missing_info" : null,
|
|
2645
|
+
needsResume ? "needs_resume" : null,
|
|
2646
|
+
watchingExternalCheck ? "watching_external_check" : null,
|
|
2647
|
+
readyForReview ? "ready_for_review" : null
|
|
2648
|
+
].filter((state) => Boolean(state));
|
|
2649
|
+
const evidenceReasonCodes = evidenceState !== "ready" ? [`lifecycle:${evidenceState}`] : [];
|
|
2650
|
+
const conflict = completed && matchedLifecycleStates.some((state) => state !== "completed");
|
|
2651
|
+
if (conflict) {
|
|
2652
|
+
return {
|
|
2653
|
+
state: "unknown_lifecycle",
|
|
2654
|
+
reasonCodes: unique([
|
|
2655
|
+
"semantic_lifecycle",
|
|
2656
|
+
"lifecycle:unknown_lifecycle",
|
|
2657
|
+
"lifecycle_conflict",
|
|
2658
|
+
...matchedLifecycleStates.map((state) => `lifecycle:${state}`),
|
|
2659
|
+
...evidenceReasonCodes,
|
|
2660
|
+
...sourceReasonCodes
|
|
2661
|
+
]),
|
|
2662
|
+
metadataSignalHash
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
const semanticState = dirtyHandoff
|
|
2666
|
+
? "dirty_worktree_handoff"
|
|
2667
|
+
: waitingApproval
|
|
2668
|
+
? "waiting_approval"
|
|
2669
|
+
: blockedMissingInfo
|
|
2670
|
+
? "blocked_missing_info"
|
|
2671
|
+
: needsResume
|
|
2672
|
+
? "needs_resume"
|
|
2673
|
+
: watchingExternalCheck
|
|
2674
|
+
? "watching_external_check"
|
|
2675
|
+
: readyForReview
|
|
2676
|
+
? "ready_for_review"
|
|
2677
|
+
: completed
|
|
2678
|
+
? "completed"
|
|
2679
|
+
: null;
|
|
2680
|
+
if (semanticState === "completed" && evidenceState !== "ready") {
|
|
2681
|
+
return {
|
|
2682
|
+
state: evidenceState,
|
|
2683
|
+
reasonCodes: unique(["semantic_lifecycle", "lifecycle:completed", `lifecycle:${evidenceState}`, ...sourceReasonCodes]),
|
|
2684
|
+
metadataSignalHash
|
|
2685
|
+
};
|
|
2686
|
+
}
|
|
2687
|
+
if (semanticState) {
|
|
2688
|
+
return {
|
|
2689
|
+
state: semanticState,
|
|
2690
|
+
reasonCodes: unique(["semantic_lifecycle", `lifecycle:${semanticState}`, ...evidenceReasonCodes, ...sourceReasonCodes]),
|
|
2691
|
+
metadataSignalHash
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
if (evidenceState !== "ready") {
|
|
2695
|
+
return {
|
|
2696
|
+
state: evidenceState,
|
|
2697
|
+
reasonCodes: unique(["semantic_lifecycle", `lifecycle:${evidenceState}`, "lifecycle_signal_missing", ...sourceReasonCodes]),
|
|
2698
|
+
metadataSignalHash
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
return {
|
|
2702
|
+
state: "ready",
|
|
2703
|
+
reasonCodes: unique(["semantic_lifecycle", "lifecycle:unknown_lifecycle", "lifecycle_signal_missing"]),
|
|
2704
|
+
metadataSignalHash
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2707
|
+
function lifecycleCompletionLike(value) {
|
|
2708
|
+
return ["complete", "completed", "done", "closed", "merged", "success", "successful", "succeeded", "passed"].includes(value);
|
|
2709
|
+
}
|
|
2710
|
+
function preparedCardStateLabel(state) {
|
|
2711
|
+
return {
|
|
2712
|
+
ready: "evidence ready, lifecycle unknown",
|
|
2713
|
+
stale: "stale evidence",
|
|
2714
|
+
partial: "partial evidence",
|
|
2715
|
+
unknown: "unknown evidence",
|
|
2716
|
+
completed: "completed",
|
|
2717
|
+
blocked_missing_info: "blocked missing info",
|
|
2718
|
+
waiting_approval: "waiting approval",
|
|
2719
|
+
watching_external_check: "watching external check",
|
|
2720
|
+
needs_resume: "needs resume",
|
|
2721
|
+
dirty_worktree_handoff: "dirty worktree handoff",
|
|
2722
|
+
stale_or_partial: "stale or partial",
|
|
2723
|
+
ready_for_review: "ready for review",
|
|
2724
|
+
unknown_lifecycle: "unknown lifecycle"
|
|
2725
|
+
}[state] ?? "unknown lifecycle";
|
|
2726
|
+
}
|
|
2727
|
+
function preparedCardConfidence(averageLeafConfidence, state, evidenceState = state) {
|
|
2573
2728
|
let confidence = Number.isFinite(averageLeafConfidence) ? averageLeafConfidence : 0.3;
|
|
2574
2729
|
if (state === "partial")
|
|
2575
2730
|
confidence = Math.min(confidence, 0.49);
|
|
@@ -2577,6 +2732,14 @@ function preparedCardConfidence(averageLeafConfidence, state) {
|
|
|
2577
2732
|
confidence = Math.min(confidence, 0.49);
|
|
2578
2733
|
if (state === "unknown")
|
|
2579
2734
|
confidence = Math.min(confidence, 0.44);
|
|
2735
|
+
if (state === "stale_or_partial")
|
|
2736
|
+
confidence = Math.min(confidence, 0.49);
|
|
2737
|
+
if (state === "unknown_lifecycle")
|
|
2738
|
+
confidence = Math.min(confidence, 0.44);
|
|
2739
|
+
if (evidenceState === "partial" || evidenceState === "stale" || evidenceState === "stale_or_partial")
|
|
2740
|
+
confidence = Math.min(confidence, 0.49);
|
|
2741
|
+
if (evidenceState === "unknown" || evidenceState === "unknown_lifecycle")
|
|
2742
|
+
confidence = Math.min(confidence, 0.44);
|
|
2580
2743
|
return Math.max(0.2, Math.min(0.99, Number(confidence.toFixed(2))));
|
|
2581
2744
|
}
|
|
2582
2745
|
function preparedCardNextAction(state) {
|
|
@@ -2586,14 +2749,41 @@ function preparedCardNextAction(state) {
|
|
|
2586
2749
|
return "Refresh the local prepared-state cache before treating this card as current.";
|
|
2587
2750
|
if (state === "partial")
|
|
2588
2751
|
return "Inspect source coverage and summary leaves before relying on this prepared card.";
|
|
2752
|
+
if (state === "completed")
|
|
2753
|
+
return "No action is needed unless bounded evidence contradicts the completed lifecycle state.";
|
|
2754
|
+
if (state === "blocked_missing_info")
|
|
2755
|
+
return "Inspect bounded evidence and resolve the missing input or blocker before resuming.";
|
|
2756
|
+
if (state === "waiting_approval")
|
|
2757
|
+
return "Inspect the bounded approval context; do not execute without explicit approval.";
|
|
2758
|
+
if (state === "watching_external_check")
|
|
2759
|
+
return "Refresh external check or watcher evidence before deciding the next action.";
|
|
2760
|
+
if (state === "needs_resume")
|
|
2761
|
+
return "Inspect bounded evidence and resume only through the approval-gated control path.";
|
|
2762
|
+
if (state === "dirty_worktree_handoff")
|
|
2763
|
+
return "Inspect handoff evidence and clean or transfer the dirty worktree deliberately.";
|
|
2764
|
+
if (state === "stale_or_partial")
|
|
2765
|
+
return "Refresh or inspect partial prepared-state evidence before relying on this lifecycle card.";
|
|
2766
|
+
if (state === "ready_for_review")
|
|
2767
|
+
return "Review bounded evidence and source refs before marking the lane complete.";
|
|
2768
|
+
if (state === "unknown_lifecycle")
|
|
2769
|
+
return "Inspect summary leaves and session metadata; lifecycle signals are missing or conflicting.";
|
|
2589
2770
|
return "Inspect summary leaves and source coverage; missing or conflicting authority prevents a ready claim.";
|
|
2590
2771
|
}
|
|
2591
2772
|
function preparedInboxUrgencyScore(card, reasonCodes) {
|
|
2592
2773
|
const stateScore = {
|
|
2593
2774
|
unknown: 88,
|
|
2775
|
+
unknown_lifecycle: 60,
|
|
2594
2776
|
stale: 82,
|
|
2777
|
+
stale_or_partial: 74,
|
|
2595
2778
|
partial: 74,
|
|
2596
|
-
|
|
2779
|
+
blocked_missing_info: 94,
|
|
2780
|
+
waiting_approval: 92,
|
|
2781
|
+
dirty_worktree_handoff: 89,
|
|
2782
|
+
watching_external_check: 78,
|
|
2783
|
+
needs_resume: 76,
|
|
2784
|
+
ready_for_review: 68,
|
|
2785
|
+
ready: 42,
|
|
2786
|
+
completed: 18
|
|
2597
2787
|
};
|
|
2598
2788
|
const codeScore = reasonCodes.reduce((score, code) => score + ({
|
|
2599
2789
|
authority_unknown: 8,
|
|
@@ -2601,9 +2791,26 @@ function preparedInboxUrgencyScore(card, reasonCodes) {
|
|
|
2601
2791
|
stale_cache: 8,
|
|
2602
2792
|
low_confidence: 6,
|
|
2603
2793
|
filtered_unsafe_rows: 5,
|
|
2604
|
-
needs_attention: 4
|
|
2794
|
+
needs_attention: 4,
|
|
2795
|
+
"lifecycle_conflict": 10,
|
|
2796
|
+
"lifecycle:blocked_missing_info": 8,
|
|
2797
|
+
"lifecycle:waiting_approval": 7,
|
|
2798
|
+
"lifecycle:dirty_worktree_handoff": 7,
|
|
2799
|
+
"lifecycle:watching_external_check": 5,
|
|
2800
|
+
"lifecycle:needs_resume": 5
|
|
2605
2801
|
}[code] ?? 0), 0);
|
|
2606
|
-
|
|
2802
|
+
const baseStateScore = stateScore[card.state] ?? 50;
|
|
2803
|
+
return Math.max(0, Math.min(100, Number((baseStateScore + codeScore + Math.round((1 - card.confidence) * 10)).toFixed(2))));
|
|
2804
|
+
}
|
|
2805
|
+
function preparedCardCountsAsPartialSummary(state) {
|
|
2806
|
+
return state === "partial"
|
|
2807
|
+
|| state === "stale_or_partial"
|
|
2808
|
+
|| state === "blocked_missing_info"
|
|
2809
|
+
|| state === "waiting_approval"
|
|
2810
|
+
|| state === "watching_external_check"
|
|
2811
|
+
|| state === "needs_resume"
|
|
2812
|
+
|| state === "dirty_worktree_handoff"
|
|
2813
|
+
|| state === "ready_for_review";
|
|
2607
2814
|
}
|
|
2608
2815
|
function preparedCardReadActions() {
|
|
2609
2816
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
|
-
import { configuredLcmPeerDbPaths, createCloseoutEnvelopeReport, createAttentionInbox, createBusinessPulse, createCodexActiveThreadState, createCodexAutonomyTick, createCodexCollaborationNextSteps, createCodexCollaborationCockpit, createCodexRuntimeDesktopVisibilityStatus, describeSession, describeRecallRef, defaultCodexRoots, createIndexedSessionSanitizerReport, createIndexedSessionSanitizerRepairPlan, expandSession, expandQuery, expandSummaryLeaves, getPreparedCards, getPreparedInbox, getPreparedStateStatus, getWatcherEvents, createPlanStatePinsReport, createGithubOperatingItemsReport, createProjectDigest, createResumeRequestPacket, createWatcherStatusReport, createCodexDesktopCoherenceReport, createVisibleCodexSessionMap, getCockpitInbox, getCodexFinalMessages, getCodexPlans, getCodexSessionManagementMap, getCodexThreadMap, getCodexTouchedFiles, getCodexToolCalls, getRecentSessions, getSummaryLeaves, grepRecall, indexCodexSessions, probeLcmPeerDbs, probeCodexSqliteStores, searchSessions } from "../../core/src/index.js";
|
|
3
|
+
import { configuredLcmPeerDbPaths, createCloseoutEnvelopeReport, createAttentionInbox, createBusinessPulse, createCodexActiveThreadState, createCodexAutonomyTick, createCodexCollaborationNextSteps, createCodexCollaborationCockpit, createCodexRuntimeDesktopVisibilityStatus, describeSession, describeRecallRef, defaultCodexRoots, createIndexedSessionSanitizerReport, createIndexedSessionSanitizerRepairPlan, expandSession, expandQuery, expandSummaryLeaves, getPreparedCards, getPreparedInbox, getPreparedStateStatus, PREPARED_CARD_STATES, getWatcherEvents, createPlanStatePinsReport, createGithubOperatingItemsReport, createProjectDigest, createResumeRequestPacket, createWatcherStatusReport, createCodexDesktopCoherenceReport, createVisibleCodexSessionMap, getCockpitInbox, getCodexFinalMessages, getCodexPlans, getCodexSessionManagementMap, getCodexThreadMap, getCodexTouchedFiles, getCodexToolCalls, getRecentSessions, getSummaryLeaves, grepRecall, indexCodexSessions, probeLcmPeerDbs, probeCodexSqliteStores, searchSessions } from "../../core/src/index.js";
|
|
4
4
|
import { LOO_COMMAND_POLICY, createCodexAppServerStatusReport, createCodexAppServerThreadsReport, codexTransportStatus, createCodexControl, createCodexDesktopCollaborationProof, createCodexDesktopFallbackReport, createDesktopGuiProofReport, createDesktopLiveProofHarness, createDesktopProofAction, desktopActDryRun, desktopFallbackDiagnostics, desktopSee, isDesktopBackend, redactValue } from "../../adapters/src/index.js";
|
|
5
5
|
export const LOO_TOOL_TIERS = ["public_facade", "workflow_detail", "proof_debug", "internal_low_level"];
|
|
6
6
|
export const LOO_TOOL_SURFACE = {
|
|
@@ -274,7 +274,7 @@ export function createLooTools(options) {
|
|
|
274
274
|
})),
|
|
275
275
|
tool("loo_prepared_cards", "List public-safe prepared Codex state cards over summary leaves.", {
|
|
276
276
|
thread_id: { type: "string" },
|
|
277
|
-
state: { type: "string", enum: [
|
|
277
|
+
state: { type: "string", enum: [...PREPARED_CARD_STATES] },
|
|
278
278
|
limit: { type: "integer", minimum: 1, maximum: 500 }
|
|
279
279
|
}, (input) => getPreparedCards(options.db, {
|
|
280
280
|
threadId: optionalString(input.thread_id),
|
|
@@ -1366,9 +1366,9 @@ function optionalSummaryLeafKind(value) {
|
|
|
1366
1366
|
function optionalPreparedCardState(value) {
|
|
1367
1367
|
if (value === undefined)
|
|
1368
1368
|
return undefined;
|
|
1369
|
-
if (
|
|
1369
|
+
if (typeof value === "string" && PREPARED_CARD_STATES.includes(value))
|
|
1370
1370
|
return value;
|
|
1371
|
-
throw new Error(
|
|
1371
|
+
throw new Error(`state must be one of ${PREPARED_CARD_STATES.join(", ")}`);
|
|
1372
1372
|
}
|
|
1373
1373
|
function optionalRecentScope(value) {
|
|
1374
1374
|
if (value === undefined)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Release Notes 1.2.0-beta.3
|
|
2
|
+
|
|
3
|
+
`1.2.0-beta.3` is a scoped prerelease checkpoint for Codex-first local orchestration
|
|
4
|
+
and the LCO 1.2 prepared-state sprint. It publishes semantic
|
|
5
|
+
prepared-card lifecycle routing and the completed-card target coverage follow-up
|
|
6
|
+
merged after `1.2.0-beta.2`.
|
|
7
|
+
|
|
8
|
+
This release is intentionally on the npm `beta` channel. It does not promote
|
|
9
|
+
`latest` and it does not claim 1.2 GA.
|
|
10
|
+
|
|
11
|
+
## What Changed
|
|
12
|
+
|
|
13
|
+
- Added semantic lifecycle states for prepared cards and prepared inbox routing:
|
|
14
|
+
`completed`, `blocked_missing_info`, `waiting_approval`,
|
|
15
|
+
`watching_external_check`, `needs_resume`, `dirty_worktree_handoff`,
|
|
16
|
+
`ready_for_review`, `stale_or_partial`, and `unknown_lifecycle`.
|
|
17
|
+
- Exposed the lifecycle state enum through the shared core registry, MCP tool
|
|
18
|
+
schema, OpenClaw plugin manifests, and OpenClaw gateway smoke validation.
|
|
19
|
+
- Added deterministic lifecycle reason codes, lifecycle-aware next actions, and
|
|
20
|
+
urgency ranking for advisory prepared cards.
|
|
21
|
+
- Added a completed-card summary counter so finished lanes remain visible in
|
|
22
|
+
prepared-card summaries instead of disappearing from stale/partial/unknown
|
|
23
|
+
counters.
|
|
24
|
+
- Tightened lifecycle classification so generic words such as `resume`,
|
|
25
|
+
`monitor`, and `ci` do not become lifecycle states without operator-action
|
|
26
|
+
context.
|
|
27
|
+
- Fixed completed prepared-card target coverage so a fresh public completed card
|
|
28
|
+
counts as target coverage `ok` and does not make `loo_prepared_state_status`
|
|
29
|
+
report stale/partial coverage for a fully materialized completed lane.
|
|
30
|
+
- Preserved stale, partial, unknown, unsafe-row, and stale-freshness downgrades
|
|
31
|
+
for incomplete or unsafe prepared-state evidence.
|
|
32
|
+
- Carries forward the #160 desktop proof-action release boundary:
|
|
33
|
+
`loo_desktop_proof_action` / `loo desktop proof-action` validates the CUA Driver TextEdit scratch proof gate and does not prove generic GUI mutation.
|
|
34
|
+
The proof action still requires exact backend, target app, target window, action hash, approval ref, permission state, scratch file path, and `execute: true` before the backend can run.
|
|
35
|
+
Generic gateway invocation without exact proof args fails closed. The desktop proof action keeps the same proof boundary as beta.35.
|
|
36
|
+
- Carries forward strict OpenClaw gateway result handling: plugin output with
|
|
37
|
+
`output.details.ok: false` is reported as
|
|
38
|
+
`openclaw_tool_result_not_ok:<tool>` rather than successful tool proof.
|
|
39
|
+
|
|
40
|
+
## Current Claim Scope
|
|
41
|
+
|
|
42
|
+
Allowed prerelease claim:
|
|
43
|
+
|
|
44
|
+
> Local prepared Codex state and summary-leaf recall for OpenClaw/Eva, including
|
|
45
|
+
> semantic prepared-card lifecycle routing, prepared inbox prioritization,
|
|
46
|
+
> bounded summary expansion, visible Codex sidebar inventory, and
|
|
47
|
+
> approval-gated start-thread proof packets without raw transcript reads.
|
|
48
|
+
|
|
49
|
+
Prepared state remains an advisory local derived cache, not source authority
|
|
50
|
+
for PR, CI, release, runtime, customer, or business truth.
|
|
51
|
+
|
|
52
|
+
This release remains focused on local Codex sessions.
|
|
53
|
+
|
|
54
|
+
## Release Gate Notes
|
|
55
|
+
|
|
56
|
+
- Parent 1.2 tracker: #405.
|
|
57
|
+
- Cockpit tracker: #448.
|
|
58
|
+
- Release checkpoint issue: #473.
|
|
59
|
+
- Included implementation PRs: #452 and #472.
|
|
60
|
+
- Baseline stable release: `v1.1.4`.
|
|
61
|
+
- Prior beta release: `v1.2.0-beta.2`.
|
|
62
|
+
- Candidate package: `lossless-openclaw-orchestrator@1.2.0-beta.3`.
|
|
63
|
+
- Expected npm dist-tag: `beta`.
|
|
64
|
+
- Expected git tag: `v1.2.0-beta.3`.
|
|
65
|
+
- Expected GitHub Release:
|
|
66
|
+
`https://github.com/100yenadmin/Lossless-Codex-Orchestrator-LCO/releases/tag/v1.2.0-beta.3`.
|
|
67
|
+
- Required scoped prerelease gates: focused prepared-card/OpenClaw tool-smoke
|
|
68
|
+
tests, build/typecheck, `npm pack --dry-run`, release bundle/status checks,
|
|
69
|
+
GitHub CI, CodeQL, current-head review threads clear, npm publish to `beta`,
|
|
70
|
+
GitHub prerelease, and post-publish finalization status.
|
|
71
|
+
- Bundle/status/finalization checks do not publish to npm and do not create a GitHub Release.
|
|
72
|
+
- Working-app status example:
|
|
73
|
+
`loo release status --claim-scope codex-working-app-proof --runtime-proof-dir <path> --approved-live-control-evidence <path> --npm-publish-approval-evidence <path> --github-release-approval-evidence <path> --candidate-sha <sha> --github-ci-evidence <path> --codeql-evidence <path> --evidence-dir <path> --strict`
|
|
74
|
+
- Reduced-scope status example:
|
|
75
|
+
`loo release status --claim-scope codex-read-search-expand-dry-run --npm-publish-approval-evidence <path> --github-release-approval-evidence <path> --candidate-sha <sha> --github-ci-evidence <path> --codeql-evidence <path> --evidence-dir <path> --strict`
|
|
76
|
+
- `approved_live_control_smoke_missing` remains the expected blocker when a
|
|
77
|
+
working-app or live-control claim is attempted without approved live-control
|
|
78
|
+
smoke evidence for the exact candidate SHA.
|
|
79
|
+
|
|
80
|
+
## Explicit Non-Claims
|
|
81
|
+
|
|
82
|
+
No new live Codex control smoke is run by this release.
|
|
83
|
+
It does not run generic GUI mutation and does not run Codex GUI mutation.
|
|
84
|
+
No automatic gateway authorization.
|
|
85
|
+
No broad gateway scope approval. No prompt typing. No clicking. No arbitrary app control.
|
|
86
|
+
No screenshots or videos are part of the public release evidence.
|
|
87
|
+
Claude Code remains an adapter stub, not an adapter-equivalence claim.
|
|
88
|
+
No true Codex compaction-summary capture.
|
|
89
|
+
No model compaction proof.
|
|
90
|
+
No raw transcript upload and no OpenClaw LCM merge.
|
|
91
|
+
No Notion, support-control, Stripe, or Company Brain P1 adapter proof.
|
|
92
|
+
No cloud sync.
|
|
93
|
+
No unattended desktop takeover.
|
|
94
|
+
No npm `latest` promotion.
|
|
95
|
+
No release-grade enterprise security or customer-ready security claim.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"scenario_version": "1.0",
|
|
3
3
|
"id": "prepared-cards-inbox-v1",
|
|
4
|
-
"title": "Prepared-state OpenClaw gateway dogfood",
|
|
4
|
+
"title": "Prepared-state lifecycle OpenClaw gateway dogfood",
|
|
5
5
|
"claim_scope": "codex-read-search-expand-dry-run",
|
|
6
|
-
"user_task": "As Eva, inspect prepared Codex session cards, choose the highest-priority attention item, and expand only bounded summary-leaf evidence before deciding the next safe action.",
|
|
6
|
+
"user_task": "As Eva, inspect prepared Codex session cards, distinguish evidence readiness from semantic lifecycle state, choose the highest-priority attention item, and expand only bounded summary-leaf evidence before deciding the next safe action.",
|
|
7
7
|
"surface": "openclaw_gateway",
|
|
8
8
|
"allowed_tools": [
|
|
9
9
|
"loo_prepared_state_status",
|
|
@@ -39,7 +39,9 @@
|
|
|
39
39
|
"source coverage",
|
|
40
40
|
"authority coverage",
|
|
41
41
|
"freshness and confidence",
|
|
42
|
-
"
|
|
42
|
+
"semantic lifecycle state and lifecycle reason codes",
|
|
43
|
+
"completed blocked_missing_info waiting_approval watching_external_check needs_resume dirty_worktree_handoff ready_for_review stale_or_partial and unknown_lifecycle classification",
|
|
44
|
+
"stale partial unknown and low-confidence evidence-readiness reason codes",
|
|
43
45
|
"execute=false attention queue actions",
|
|
44
46
|
"bounded summary expansion references",
|
|
45
47
|
"OpenClaw gateway tool-smoke",
|
|
@@ -69,6 +71,8 @@
|
|
|
69
71
|
"requires_execute_false_actions": true,
|
|
70
72
|
"requires_openclaw_gateway_tool_smoke": true,
|
|
71
73
|
"requires_agent_recommendation": true,
|
|
74
|
+
"requires_semantic_lifecycle_reason_codes": true,
|
|
75
|
+
"requires_lifecycle_urgency_ranking": true,
|
|
72
76
|
"requires_stale_partial_unknown_reason_codes": true,
|
|
73
77
|
"max_raw_transcript_spans": 0,
|
|
74
78
|
"max_raw_tool_payloads": 0,
|
|
@@ -76,6 +80,6 @@
|
|
|
76
80
|
"max_gui_actions": 0,
|
|
77
81
|
"max_screenshots_in_public_evidence": 0
|
|
78
82
|
},
|
|
79
|
-
"proof_boundary": "Dry-run fixture and OpenClaw gateway dogfood scenario for deterministic prepared-card and attention-inbox materialization over public-safe summary leaves. It proves advisory local prepared Codex
|
|
80
|
-
"next_action": "Attach issue-specific
|
|
83
|
+
"proof_boundary": "Dry-run fixture and OpenClaw gateway dogfood scenario for deterministic prepared-card and attention-inbox materialization over public-safe summary leaves plus session metadata lifecycle signals. It proves advisory local prepared Codex lifecycle routing and targeted prepared-state coverage for OpenClaw/Eva only when paired with focused lifecycle fixtures, a public-safe `loo openclaw tool-smoke` report that invokes prepared-state tools for a requested thread id, emits an agent recommendation, and records `rawTranscriptRead=false`. It does not prove model compaction, true Codex compaction-summary capture, live control, GUI mutation, unattended autonomy, release readiness, or 1.2 GA readiness.",
|
|
84
|
+
"next_action": "Attach issue-specific semantic lifecycle evidence for #446, then continue remaining #405/#448 children behind their explicit canary/protocol boundaries."
|
|
81
85
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "lossless-openclaw-orchestrator",
|
|
3
3
|
"name": "Lossless OpenClaw Orchestrator",
|
|
4
4
|
"description": "Control and collaborate with local Codex sessions through OpenClaw using local indexing, bounded recall, and approval-gated controls.",
|
|
5
|
-
"version": "1.2.0-beta.
|
|
5
|
+
"version": "1.2.0-beta.3",
|
|
6
6
|
"kind": "tool",
|
|
7
7
|
"tools": {
|
|
8
8
|
"prefix": "loo_"
|
|
@@ -477,7 +477,16 @@
|
|
|
477
477
|
"ready",
|
|
478
478
|
"stale",
|
|
479
479
|
"partial",
|
|
480
|
-
"unknown"
|
|
480
|
+
"unknown",
|
|
481
|
+
"completed",
|
|
482
|
+
"blocked_missing_info",
|
|
483
|
+
"waiting_approval",
|
|
484
|
+
"watching_external_check",
|
|
485
|
+
"needs_resume",
|
|
486
|
+
"dirty_worktree_handoff",
|
|
487
|
+
"stale_or_partial",
|
|
488
|
+
"ready_for_review",
|
|
489
|
+
"unknown_lifecycle"
|
|
481
490
|
]
|
|
482
491
|
},
|
|
483
492
|
"limit": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lossless-openclaw-orchestrator",
|
|
3
|
-
"version": "1.2.0-beta.
|
|
3
|
+
"version": "1.2.0-beta.3",
|
|
4
4
|
"description": "Index, search, and control local Codex sessions through OpenClaw with approval-gated safety; Claude Code remains an experimental adapter stub.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, isAbsolute } from "node:path";
|
|
5
|
+
import { PREPARED_CARD_STATES } from "../../core/src/index.js";
|
|
5
6
|
|
|
6
7
|
export const DEFAULT_REQUIRED_TOOL_CALLS = [
|
|
7
8
|
"loo_doctor",
|
|
@@ -42,6 +43,8 @@ export const DEFAULT_REQUIRED_TOOL_CALLS = [
|
|
|
42
43
|
"loo_prepared_inbox"
|
|
43
44
|
];
|
|
44
45
|
|
|
46
|
+
const PREPARED_CARD_STATE_SET = new Set<string>(PREPARED_CARD_STATES);
|
|
47
|
+
|
|
45
48
|
const AUTONOMY_TICK_SUMMARY_KEYS = [
|
|
46
49
|
"totalLanes",
|
|
47
50
|
"returnedSteps",
|
|
@@ -1332,7 +1335,7 @@ function summarizeInvocation(
|
|
|
1332
1335
|
const state = stringPath(card, ["state"]);
|
|
1333
1336
|
return !cardRef?.startsWith("prepared_card:")
|
|
1334
1337
|
|| !targetRef?.startsWith("codex_thread:")
|
|
1335
|
-
|| !
|
|
1338
|
+
|| !PREPARED_CARD_STATE_SET.has(state ?? "");
|
|
1336
1339
|
})) blockers.push("prepared_cards_public_refs_invalid");
|
|
1337
1340
|
}
|
|
1338
1341
|
if (toolName === "loo_prepared_inbox") {
|
|
@@ -262,7 +262,22 @@ export type SummaryLeafMaterializationOptions = {
|
|
|
262
262
|
limit?: number;
|
|
263
263
|
};
|
|
264
264
|
|
|
265
|
-
export
|
|
265
|
+
export const PREPARED_CARD_STATES = [
|
|
266
|
+
"ready",
|
|
267
|
+
"stale",
|
|
268
|
+
"partial",
|
|
269
|
+
"unknown",
|
|
270
|
+
"completed",
|
|
271
|
+
"blocked_missing_info",
|
|
272
|
+
"waiting_approval",
|
|
273
|
+
"watching_external_check",
|
|
274
|
+
"needs_resume",
|
|
275
|
+
"dirty_worktree_handoff",
|
|
276
|
+
"stale_or_partial",
|
|
277
|
+
"ready_for_review",
|
|
278
|
+
"unknown_lifecycle"
|
|
279
|
+
] as const;
|
|
280
|
+
export type PreparedCardState = (typeof PREPARED_CARD_STATES)[number];
|
|
266
281
|
export type PreparedCardKind = "codex_session";
|
|
267
282
|
export type PreparedStateCoverage = "ok" | "partial" | "not_configured" | "unknown";
|
|
268
283
|
|
|
@@ -393,6 +408,7 @@ export type PreparedCardsReport = {
|
|
|
393
408
|
stale: number;
|
|
394
409
|
partial: number;
|
|
395
410
|
unknown: number;
|
|
411
|
+
completed: number;
|
|
396
412
|
lowConfidence: number;
|
|
397
413
|
};
|
|
398
414
|
cards: PreparedCard[];
|
|
@@ -3891,6 +3907,7 @@ export function getPreparedCards(db: LooDatabase, options: PreparedCardsOptions
|
|
|
3891
3907
|
let stale = 0;
|
|
3892
3908
|
let partial = 0;
|
|
3893
3909
|
let unknown = 0;
|
|
3910
|
+
let completed = 0;
|
|
3894
3911
|
let lowConfidence = 0;
|
|
3895
3912
|
for (const row of rows) {
|
|
3896
3913
|
const card = publicPreparedCardFromRow(row);
|
|
@@ -3898,9 +3915,10 @@ export function getPreparedCards(db: LooDatabase, options: PreparedCardsOptions
|
|
|
3898
3915
|
filteredUnsafeRows += 1;
|
|
3899
3916
|
continue;
|
|
3900
3917
|
}
|
|
3901
|
-
if (card.stale) stale += 1;
|
|
3902
|
-
if (card.state
|
|
3903
|
-
if (card.state === "unknown") unknown += 1;
|
|
3918
|
+
if (card.stale || card.state === "stale") stale += 1;
|
|
3919
|
+
if (preparedCardCountsAsPartialSummary(card.state)) partial += 1;
|
|
3920
|
+
if (card.state === "unknown" || card.state === "unknown_lifecycle") unknown += 1;
|
|
3921
|
+
if (card.state === "completed") completed += 1;
|
|
3904
3922
|
if (card.confidence < 0.5) lowConfidence += 1;
|
|
3905
3923
|
if (validCards.length < limit) validCards.push(card);
|
|
3906
3924
|
}
|
|
@@ -3936,6 +3954,7 @@ export function getPreparedCards(db: LooDatabase, options: PreparedCardsOptions
|
|
|
3936
3954
|
stale,
|
|
3937
3955
|
partial,
|
|
3938
3956
|
unknown,
|
|
3957
|
+
completed,
|
|
3939
3958
|
lowConfidence
|
|
3940
3959
|
},
|
|
3941
3960
|
cards: validCards,
|
|
@@ -4268,9 +4287,19 @@ function buildPreparedCardDraft(db: LooDatabase, threadId: string, leaves: Summa
|
|
|
4268
4287
|
const watcherObservationsStatus = watcherObservationCoverageForTarget(db, targetRef);
|
|
4269
4288
|
const freshnessAt = latestIso([...leaves.map((leaf) => leaf.freshnessAt), session?.updatedAt ?? null]);
|
|
4270
4289
|
const stale = leaves.some((leaf) => leaf.stale);
|
|
4290
|
+
const evidenceState: PreparedCardState = stale
|
|
4291
|
+
? "stale_or_partial"
|
|
4292
|
+
: summaryLeavesStatus === "unknown"
|
|
4293
|
+
? "unknown_lifecycle"
|
|
4294
|
+
: summaryLeavesStatus === "partial" || sessionMetadataStatus === "partial"
|
|
4295
|
+
? "stale_or_partial"
|
|
4296
|
+
: "ready";
|
|
4297
|
+
const lifecycle = preparedLifecycleFromMetadata(metadata, evidenceState);
|
|
4298
|
+
const state = lifecycle.state;
|
|
4271
4299
|
const reasonCodes = unique([
|
|
4272
4300
|
leaves.length > 0 ? "summary_leaves_ready" : "summary_leaves_missing",
|
|
4273
4301
|
"metadata_only",
|
|
4302
|
+
...lifecycle.reasonCodes,
|
|
4274
4303
|
watcherObservationsStatus === "not_configured" ? "watcher_not_configured" : "watcher_observations_available",
|
|
4275
4304
|
stale ? "stale_cache" : "",
|
|
4276
4305
|
summaryLeavesStatus === "partial" ? "authority_partial" : "",
|
|
@@ -4280,17 +4309,10 @@ function buildPreparedCardDraft(db: LooDatabase, threadId: string, leaves: Summa
|
|
|
4280
4309
|
watcherObservationsStatus === "unknown" ? "watcher_observations_unknown" : "",
|
|
4281
4310
|
filteredUnsafeRows > 0 ? "filtered_unsafe_rows" : ""
|
|
4282
4311
|
].filter(Boolean));
|
|
4283
|
-
const state: PreparedCardState = stale
|
|
4284
|
-
? "stale"
|
|
4285
|
-
: summaryLeavesStatus === "unknown"
|
|
4286
|
-
? "unknown"
|
|
4287
|
-
: summaryLeavesStatus === "partial" || sessionMetadataStatus === "partial"
|
|
4288
|
-
? "partial"
|
|
4289
|
-
: "ready";
|
|
4290
4312
|
const averageLeafConfidence = leaves.length
|
|
4291
4313
|
? leaves.reduce((sum, leaf) => sum + leaf.confidence, 0) / leaves.length
|
|
4292
4314
|
: 0.3;
|
|
4293
|
-
const confidence = preparedCardConfidence(averageLeafConfidence, state);
|
|
4315
|
+
const confidence = preparedCardConfidence(averageLeafConfidence, state, evidenceState);
|
|
4294
4316
|
const sourceRangeRefsFull = unique(leaves.flatMap((leaf) => leaf.sourceRangeRefs));
|
|
4295
4317
|
const sourceRangeRefs = sourceRangeRefsFull.slice(0, PREPARED_CARD_SOURCE_RANGE_REF_LIMIT);
|
|
4296
4318
|
const sourceRangeRefsOmitted = Math.max(0, sourceRangeRefsFull.length - sourceRangeRefs.length)
|
|
@@ -4305,7 +4327,7 @@ function buildPreparedCardDraft(db: LooDatabase, threadId: string, leaves: Summa
|
|
|
4305
4327
|
});
|
|
4306
4328
|
const title = looksSensitiveRefLike(cleanedTitle.text) ? publicSafeText(safeThreadId(threadId), 160) : cleanedTitle.text;
|
|
4307
4329
|
const summaryText = publicSafeText(
|
|
4308
|
-
`Prepared state: ${leaves.length} summary leaf${leaves.length === 1 ? "" : "s"} across ${Math.max(0, leafRangeCount)} prepared source range${leafRangeCount === 1 ? "" : "s"}.`,
|
|
4330
|
+
`Prepared state: ${leaves.length} summary leaf${leaves.length === 1 ? "" : "s"} across ${Math.max(0, leafRangeCount)} prepared source range${leafRangeCount === 1 ? "" : "s"}. Lifecycle: ${preparedCardStateLabel(state)}.`,
|
|
4309
4331
|
320
|
|
4310
4332
|
);
|
|
4311
4333
|
const nextAction = preparedCardNextAction(state);
|
|
@@ -4327,6 +4349,10 @@ function buildPreparedCardDraft(db: LooDatabase, threadId: string, leaves: Summa
|
|
|
4327
4349
|
leafRefs: leaves.map((leaf) => leaf.leafRef),
|
|
4328
4350
|
inputHashes: leaves.map((leaf) => leaf.inputHash),
|
|
4329
4351
|
outputHashes: leaves.map((leaf) => leaf.outputHash),
|
|
4352
|
+
metadataSignalHash: lifecycle.metadataSignalHash,
|
|
4353
|
+
lifecycleState: state,
|
|
4354
|
+
lifecycleReasonCodes: lifecycle.reasonCodes,
|
|
4355
|
+
evidenceState,
|
|
4330
4356
|
freshnessAt,
|
|
4331
4357
|
stale,
|
|
4332
4358
|
summaryLeavesStatus,
|
|
@@ -4363,7 +4389,7 @@ function buildPreparedCardDraft(db: LooDatabase, threadId: string, leaves: Summa
|
|
|
4363
4389
|
function preparedInboxItemFromCard(card: PreparedCard): PreparedInboxItem {
|
|
4364
4390
|
const reasonCodes = unique([
|
|
4365
4391
|
...card.reasonCodes,
|
|
4366
|
-
card.state === "ready" ? "prepared_card_ready" : "needs_attention"
|
|
4392
|
+
card.state === "ready" ? "prepared_card_ready" : card.state === "completed" ? "prepared_card_completed" : "needs_attention"
|
|
4367
4393
|
]);
|
|
4368
4394
|
const urgencyScore = preparedInboxUrgencyScore(card, reasonCodes);
|
|
4369
4395
|
return {
|
|
@@ -4642,7 +4668,7 @@ function getPreparedTargetCoverage(db: LooDatabase, threadId?: string): Prepared
|
|
|
4642
4668
|
preparedSourceEvents: preparedSourceEvents.count > 0 ? "ok" : "not_configured",
|
|
4643
4669
|
preparedSourceRanges: preparedSourceRanges.publicCount > 0 ? "ok" : preparedSourceRanges.rawCount > 0 ? "partial" : "not_configured",
|
|
4644
4670
|
summaryLeaves: summaryLeaves.publicCount > 0 ? "ok" : summaryLeaves.rawCount > 0 ? "partial" : "not_configured",
|
|
4645
|
-
preparedCards: publicCard ? publicCard
|
|
4671
|
+
preparedCards: publicCard ? preparedTargetCardCoverage(publicCard) : preparedCards > 0 ? "partial" : "not_configured",
|
|
4646
4672
|
preparedInboxItems: publicCard && preparedInboxItems.publicCount > 0 ? "ok" : preparedInboxItems.rawCount > 0 ? "partial" : "not_configured",
|
|
4647
4673
|
watcherObservations: watcherObservationCoverageForTarget(db, targetRef)
|
|
4648
4674
|
} satisfies PreparedTargetCoverage["sourceCoverage"];
|
|
@@ -4668,8 +4694,7 @@ function getPreparedTargetCoverage(db: LooDatabase, threadId?: string): Prepared
|
|
|
4668
4694
|
const allDerivedLayersMissing = requiredCoverage.every((state) => state === "not_configured");
|
|
4669
4695
|
const stale = Boolean(session && (
|
|
4670
4696
|
missingDerivedCache
|
|
4671
|
-
|| publicCard
|
|
4672
|
-
|| publicCard?.state !== "ready"
|
|
4697
|
+
|| (publicCard ? preparedTargetCardCoverage(publicCard) !== "ok" : false)
|
|
4673
4698
|
|| summaryLeaves.staleCount > 0
|
|
4674
4699
|
|| (sourceUpdatedAt && preparedFreshnessAt && sourceUpdatedAt > preparedFreshnessAt)
|
|
4675
4700
|
));
|
|
@@ -4709,6 +4734,19 @@ function getPreparedTargetCoverage(db: LooDatabase, threadId?: string): Prepared
|
|
|
4709
4734
|
};
|
|
4710
4735
|
}
|
|
4711
4736
|
|
|
4737
|
+
function preparedTargetCardCoverage(card: PreparedCard): PreparedStateCoverage {
|
|
4738
|
+
if (card.stale) return "partial";
|
|
4739
|
+
return preparedCardStateHasFreshTargetCoverage(card.state) ? "ok" : "partial";
|
|
4740
|
+
}
|
|
4741
|
+
|
|
4742
|
+
function preparedCardStateHasFreshTargetCoverage(state: PreparedCardState): boolean {
|
|
4743
|
+
return state !== "stale"
|
|
4744
|
+
&& state !== "partial"
|
|
4745
|
+
&& state !== "stale_or_partial"
|
|
4746
|
+
&& state !== "unknown"
|
|
4747
|
+
&& state !== "unknown_lifecycle";
|
|
4748
|
+
}
|
|
4749
|
+
|
|
4712
4750
|
function countPreparedTargetRows(db: LooDatabase, sql: string, ...params: Array<string | number>): number {
|
|
4713
4751
|
return Number((db.prepare(sql).get(...params) as { count: number } | undefined)?.count ?? 0);
|
|
4714
4752
|
}
|
|
@@ -4911,7 +4949,7 @@ function preparedCoverageState(value: unknown): PreparedStateCoverage {
|
|
|
4911
4949
|
}
|
|
4912
4950
|
|
|
4913
4951
|
function isPreparedCardState(value: string): value is PreparedCardState {
|
|
4914
|
-
return
|
|
4952
|
+
return (PREPARED_CARD_STATES as readonly string[]).includes(value);
|
|
4915
4953
|
}
|
|
4916
4954
|
|
|
4917
4955
|
function isPublicPreparedSourceRef(value: string): boolean {
|
|
@@ -4933,11 +4971,139 @@ function isPublicCodexSubagentResultRef(value: string): boolean {
|
|
|
4933
4971
|
return /^[A-Za-z0-9._:-]{1,160}$/.test(decodedId) && !looksSensitiveRefLike(decodedId);
|
|
4934
4972
|
}
|
|
4935
4973
|
|
|
4936
|
-
function
|
|
4974
|
+
function preparedLifecycleFromMetadata(
|
|
4975
|
+
metadata: SessionMetadata,
|
|
4976
|
+
evidenceState: PreparedCardState
|
|
4977
|
+
): { state: PreparedCardState; reasonCodes: string[]; metadataSignalHash: string } {
|
|
4978
|
+
const signals = {
|
|
4979
|
+
status: normalizedMetadataValue(metadata.status),
|
|
4980
|
+
blocker: normalizedMetadataValue(metadata.blocker),
|
|
4981
|
+
nextAction: normalizedMetadataValue(metadata.nextAction),
|
|
4982
|
+
closeoutState: normalizedMetadataValue(metadata.closeoutState),
|
|
4983
|
+
planCompletionState: normalizedMetadataValue(metadata.planCompletionState)
|
|
4984
|
+
};
|
|
4985
|
+
const metadataSignalHash = stableId(JSON.stringify(signals));
|
|
4986
|
+
const nonBlockerText = [signals.status, signals.nextAction, signals.closeoutState, signals.planCompletionState].filter(Boolean).join(" ");
|
|
4987
|
+
const text = [nonBlockerText, signals.blocker].filter(Boolean).join(" ");
|
|
4988
|
+
const sourceReasonCodes = Object.entries(signals)
|
|
4989
|
+
.filter(([, value]) => value.length > 0)
|
|
4990
|
+
.map(([field]) => `lifecycle_signal:${field.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`)}`);
|
|
4991
|
+
const completedByStatus = lifecycleCompletionLike(signals.status);
|
|
4992
|
+
const completedByCloseoutAndPlan = lifecycleCompletionLike(signals.closeoutState)
|
|
4993
|
+
&& lifecycleCompletionLike(signals.planCompletionState);
|
|
4994
|
+
const completed = completedByStatus || completedByCloseoutAndPlan;
|
|
4995
|
+
const dirtyHandoff = /\b(?:dirty[-_ ]?worktree|uncommitted|worktree[-_ ]?handoff|dirty[-_ ]?handoff|handoff[-_ ]?dirty|cleanup[-_ ]?required)\b/.test(text);
|
|
4996
|
+
const waitingApproval = /\b(?:waiting[-_ ]?(?:for[-_ ]?)?approval|needs[-_ ]?approval|requires[-_ ]?approval|approval[-_ ]?(?:required|needed|pending)|pending[-_ ]?approval|approval[-_ ]?gate|do[-_ ]?not[-_ ]?execute[-_ ]?without[-_ ]?explicit[-_ ]?approval)\b/.test(text);
|
|
4997
|
+
const needsResume = /\b(?:needs[-_ ]?(?:to[-_ ]?)?resume|resume[-_ ]?(?:session|thread|work|run|lane|needed|required|requested|request)|continue[-_ ]?(?:session|thread|lane)|nudge[-_ ]?(?:session|thread|agent|lane)|rejoin[-_ ]?(?:session|thread|lane))\b/.test(text);
|
|
4998
|
+
const watchingExternalCheck = /\b(?:(?:watch|watching|monitor|monitoring)[-_ ]+(?:ci|checks?|codeql|coderabbit|deploy(?:[-_ ]?check)?|external[-_ ]?(?:check|review)|review[-_ ]?check)|(?:waiting[-_ ]?(?:on|for)|pending)[-_ ]+(?:ci|checks?|codeql|coderabbit|deploy|external[-_ ]?check|review[-_ ]?check)|(?:ci|checks?|codeql|coderabbit|deploy[-_ ]?check|review[-_ ]?check)[-_ ]+(?:pending|queued|running|failing|failed|blocked|red|not[-_ ]?green|in[-_ ]?progress))\b/.test(text);
|
|
4999
|
+
const readyForReview = /\b(?:ready[-_ ]?for[-_ ]?review|review[-_ ]?ready|ready[-_ ]?to[-_ ]?review|pr[-_ ]?ready|needs[-_ ]?review)\b/.test(text);
|
|
5000
|
+
const negatedBlocker = /\b(?:not[-_ ]?blocked|no[-_ ]?blocker|unblocked|without[-_ ]?blocker|blocker[-_ ]?(?:none|na|n[-_ ]?a))\b/.test(text);
|
|
5001
|
+
const missingInfoSignal = /\b(?:missing[-_ ]?(?:info|input|context)|needs[-_ ]?(?:info|input|context)|waiting[-_ ]?(?:on|for)[-_ ]?(?:user|operator|human|input|context)|cannot[-_ ]?proceed)\b/.test(nonBlockerText);
|
|
5002
|
+
const blockedSignal = !negatedBlocker && /\b(?:blocked|blocker)\b/.test(nonBlockerText);
|
|
5003
|
+
const blockedMissingInfo = !waitingApproval
|
|
5004
|
+
&& !dirtyHandoff
|
|
5005
|
+
&& !watchingExternalCheck
|
|
5006
|
+
&& !negatedBlocker
|
|
5007
|
+
&& (hasRealBlocker(metadata.blocker) || missingInfoSignal || blockedSignal);
|
|
5008
|
+
const matchedLifecycleStates: PreparedCardState[] = [
|
|
5009
|
+
completed ? "completed" : null,
|
|
5010
|
+
dirtyHandoff ? "dirty_worktree_handoff" : null,
|
|
5011
|
+
waitingApproval ? "waiting_approval" : null,
|
|
5012
|
+
blockedMissingInfo ? "blocked_missing_info" : null,
|
|
5013
|
+
needsResume ? "needs_resume" : null,
|
|
5014
|
+
watchingExternalCheck ? "watching_external_check" : null,
|
|
5015
|
+
readyForReview ? "ready_for_review" : null
|
|
5016
|
+
].filter((state): state is PreparedCardState => Boolean(state));
|
|
5017
|
+
const evidenceReasonCodes = evidenceState !== "ready" ? [`lifecycle:${evidenceState}`] : [];
|
|
5018
|
+
const conflict = completed && matchedLifecycleStates.some((state) => state !== "completed");
|
|
5019
|
+
if (conflict) {
|
|
5020
|
+
return {
|
|
5021
|
+
state: "unknown_lifecycle",
|
|
5022
|
+
reasonCodes: unique([
|
|
5023
|
+
"semantic_lifecycle",
|
|
5024
|
+
"lifecycle:unknown_lifecycle",
|
|
5025
|
+
"lifecycle_conflict",
|
|
5026
|
+
...matchedLifecycleStates.map((state) => `lifecycle:${state}`),
|
|
5027
|
+
...evidenceReasonCodes,
|
|
5028
|
+
...sourceReasonCodes
|
|
5029
|
+
]),
|
|
5030
|
+
metadataSignalHash
|
|
5031
|
+
};
|
|
5032
|
+
}
|
|
5033
|
+
const semanticState: PreparedCardState | null = dirtyHandoff
|
|
5034
|
+
? "dirty_worktree_handoff"
|
|
5035
|
+
: waitingApproval
|
|
5036
|
+
? "waiting_approval"
|
|
5037
|
+
: blockedMissingInfo
|
|
5038
|
+
? "blocked_missing_info"
|
|
5039
|
+
: needsResume
|
|
5040
|
+
? "needs_resume"
|
|
5041
|
+
: watchingExternalCheck
|
|
5042
|
+
? "watching_external_check"
|
|
5043
|
+
: readyForReview
|
|
5044
|
+
? "ready_for_review"
|
|
5045
|
+
: completed
|
|
5046
|
+
? "completed"
|
|
5047
|
+
: null;
|
|
5048
|
+
if (semanticState === "completed" && evidenceState !== "ready") {
|
|
5049
|
+
return {
|
|
5050
|
+
state: evidenceState,
|
|
5051
|
+
reasonCodes: unique(["semantic_lifecycle", "lifecycle:completed", `lifecycle:${evidenceState}`, ...sourceReasonCodes]),
|
|
5052
|
+
metadataSignalHash
|
|
5053
|
+
};
|
|
5054
|
+
}
|
|
5055
|
+
if (semanticState) {
|
|
5056
|
+
return {
|
|
5057
|
+
state: semanticState,
|
|
5058
|
+
reasonCodes: unique(["semantic_lifecycle", `lifecycle:${semanticState}`, ...evidenceReasonCodes, ...sourceReasonCodes]),
|
|
5059
|
+
metadataSignalHash
|
|
5060
|
+
};
|
|
5061
|
+
}
|
|
5062
|
+
if (evidenceState !== "ready") {
|
|
5063
|
+
return {
|
|
5064
|
+
state: evidenceState,
|
|
5065
|
+
reasonCodes: unique(["semantic_lifecycle", `lifecycle:${evidenceState}`, "lifecycle_signal_missing", ...sourceReasonCodes]),
|
|
5066
|
+
metadataSignalHash
|
|
5067
|
+
};
|
|
5068
|
+
}
|
|
5069
|
+
return {
|
|
5070
|
+
state: "ready",
|
|
5071
|
+
reasonCodes: unique(["semantic_lifecycle", "lifecycle:unknown_lifecycle", "lifecycle_signal_missing"]),
|
|
5072
|
+
metadataSignalHash
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
|
|
5076
|
+
function lifecycleCompletionLike(value: string): boolean {
|
|
5077
|
+
return ["complete", "completed", "done", "closed", "merged", "success", "successful", "succeeded", "passed"].includes(value);
|
|
5078
|
+
}
|
|
5079
|
+
|
|
5080
|
+
function preparedCardStateLabel(state: PreparedCardState): string {
|
|
5081
|
+
return ({
|
|
5082
|
+
ready: "evidence ready, lifecycle unknown",
|
|
5083
|
+
stale: "stale evidence",
|
|
5084
|
+
partial: "partial evidence",
|
|
5085
|
+
unknown: "unknown evidence",
|
|
5086
|
+
completed: "completed",
|
|
5087
|
+
blocked_missing_info: "blocked missing info",
|
|
5088
|
+
waiting_approval: "waiting approval",
|
|
5089
|
+
watching_external_check: "watching external check",
|
|
5090
|
+
needs_resume: "needs resume",
|
|
5091
|
+
dirty_worktree_handoff: "dirty worktree handoff",
|
|
5092
|
+
stale_or_partial: "stale or partial",
|
|
5093
|
+
ready_for_review: "ready for review",
|
|
5094
|
+
unknown_lifecycle: "unknown lifecycle"
|
|
5095
|
+
} as Record<PreparedCardState, string>)[state] ?? "unknown lifecycle";
|
|
5096
|
+
}
|
|
5097
|
+
|
|
5098
|
+
function preparedCardConfidence(averageLeafConfidence: number, state: PreparedCardState, evidenceState: PreparedCardState = state): number {
|
|
4937
5099
|
let confidence = Number.isFinite(averageLeafConfidence) ? averageLeafConfidence : 0.3;
|
|
4938
5100
|
if (state === "partial") confidence = Math.min(confidence, 0.49);
|
|
4939
5101
|
if (state === "stale") confidence = Math.min(confidence, 0.49);
|
|
4940
5102
|
if (state === "unknown") confidence = Math.min(confidence, 0.44);
|
|
5103
|
+
if (state === "stale_or_partial") confidence = Math.min(confidence, 0.49);
|
|
5104
|
+
if (state === "unknown_lifecycle") confidence = Math.min(confidence, 0.44);
|
|
5105
|
+
if (evidenceState === "partial" || evidenceState === "stale" || evidenceState === "stale_or_partial") confidence = Math.min(confidence, 0.49);
|
|
5106
|
+
if (evidenceState === "unknown" || evidenceState === "unknown_lifecycle") confidence = Math.min(confidence, 0.44);
|
|
4941
5107
|
return Math.max(0.2, Math.min(0.99, Number(confidence.toFixed(2))));
|
|
4942
5108
|
}
|
|
4943
5109
|
|
|
@@ -4945,15 +5111,33 @@ function preparedCardNextAction(state: PreparedCardState): string {
|
|
|
4945
5111
|
if (state === "ready") return "Use loo_summary_expand for bounded evidence before deciding whether broader session expansion is needed.";
|
|
4946
5112
|
if (state === "stale") return "Refresh the local prepared-state cache before treating this card as current.";
|
|
4947
5113
|
if (state === "partial") return "Inspect source coverage and summary leaves before relying on this prepared card.";
|
|
5114
|
+
if (state === "completed") return "No action is needed unless bounded evidence contradicts the completed lifecycle state.";
|
|
5115
|
+
if (state === "blocked_missing_info") return "Inspect bounded evidence and resolve the missing input or blocker before resuming.";
|
|
5116
|
+
if (state === "waiting_approval") return "Inspect the bounded approval context; do not execute without explicit approval.";
|
|
5117
|
+
if (state === "watching_external_check") return "Refresh external check or watcher evidence before deciding the next action.";
|
|
5118
|
+
if (state === "needs_resume") return "Inspect bounded evidence and resume only through the approval-gated control path.";
|
|
5119
|
+
if (state === "dirty_worktree_handoff") return "Inspect handoff evidence and clean or transfer the dirty worktree deliberately.";
|
|
5120
|
+
if (state === "stale_or_partial") return "Refresh or inspect partial prepared-state evidence before relying on this lifecycle card.";
|
|
5121
|
+
if (state === "ready_for_review") return "Review bounded evidence and source refs before marking the lane complete.";
|
|
5122
|
+
if (state === "unknown_lifecycle") return "Inspect summary leaves and session metadata; lifecycle signals are missing or conflicting.";
|
|
4948
5123
|
return "Inspect summary leaves and source coverage; missing or conflicting authority prevents a ready claim.";
|
|
4949
5124
|
}
|
|
4950
5125
|
|
|
4951
5126
|
function preparedInboxUrgencyScore(card: PreparedCard, reasonCodes: string[]): number {
|
|
4952
5127
|
const stateScore = {
|
|
4953
5128
|
unknown: 88,
|
|
5129
|
+
unknown_lifecycle: 60,
|
|
4954
5130
|
stale: 82,
|
|
5131
|
+
stale_or_partial: 74,
|
|
4955
5132
|
partial: 74,
|
|
4956
|
-
|
|
5133
|
+
blocked_missing_info: 94,
|
|
5134
|
+
waiting_approval: 92,
|
|
5135
|
+
dirty_worktree_handoff: 89,
|
|
5136
|
+
watching_external_check: 78,
|
|
5137
|
+
needs_resume: 76,
|
|
5138
|
+
ready_for_review: 68,
|
|
5139
|
+
ready: 42,
|
|
5140
|
+
completed: 18
|
|
4957
5141
|
} as const;
|
|
4958
5142
|
const codeScore = reasonCodes.reduce((score, code) => score + ({
|
|
4959
5143
|
authority_unknown: 8,
|
|
@@ -4961,9 +5145,27 @@ function preparedInboxUrgencyScore(card: PreparedCard, reasonCodes: string[]): n
|
|
|
4961
5145
|
stale_cache: 8,
|
|
4962
5146
|
low_confidence: 6,
|
|
4963
5147
|
filtered_unsafe_rows: 5,
|
|
4964
|
-
needs_attention: 4
|
|
5148
|
+
needs_attention: 4,
|
|
5149
|
+
"lifecycle_conflict": 10,
|
|
5150
|
+
"lifecycle:blocked_missing_info": 8,
|
|
5151
|
+
"lifecycle:waiting_approval": 7,
|
|
5152
|
+
"lifecycle:dirty_worktree_handoff": 7,
|
|
5153
|
+
"lifecycle:watching_external_check": 5,
|
|
5154
|
+
"lifecycle:needs_resume": 5
|
|
4965
5155
|
}[code] ?? 0), 0);
|
|
4966
|
-
|
|
5156
|
+
const baseStateScore = stateScore[card.state] ?? 50;
|
|
5157
|
+
return Math.max(0, Math.min(100, Number((baseStateScore + codeScore + Math.round((1 - card.confidence) * 10)).toFixed(2))));
|
|
5158
|
+
}
|
|
5159
|
+
|
|
5160
|
+
function preparedCardCountsAsPartialSummary(state: PreparedCardState): boolean {
|
|
5161
|
+
return state === "partial"
|
|
5162
|
+
|| state === "stale_or_partial"
|
|
5163
|
+
|| state === "blocked_missing_info"
|
|
5164
|
+
|| state === "waiting_approval"
|
|
5165
|
+
|| state === "watching_external_check"
|
|
5166
|
+
|| state === "needs_resume"
|
|
5167
|
+
|| state === "dirty_worktree_handoff"
|
|
5168
|
+
|| state === "ready_for_review";
|
|
4967
5169
|
}
|
|
4968
5170
|
|
|
4969
5171
|
function preparedCardReadActions(): PreparedCardsReport["actionsPerformed"] {
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
getPreparedCards,
|
|
23
23
|
getPreparedInbox,
|
|
24
24
|
getPreparedStateStatus,
|
|
25
|
+
PREPARED_CARD_STATES,
|
|
25
26
|
getWatcherEvents,
|
|
26
27
|
createPlanStatePinsReport,
|
|
27
28
|
createGithubOperatingItemsReport,
|
|
@@ -406,7 +407,7 @@ export function createLooTools(options: { db: LooDatabase; audit: AuditStore; co
|
|
|
406
407
|
})),
|
|
407
408
|
tool("loo_prepared_cards", "List public-safe prepared Codex state cards over summary leaves.", {
|
|
408
409
|
thread_id: { type: "string" },
|
|
409
|
-
state: { type: "string", enum: [
|
|
410
|
+
state: { type: "string", enum: [...PREPARED_CARD_STATES] },
|
|
410
411
|
limit: { type: "integer", minimum: 1, maximum: 500 }
|
|
411
412
|
}, (input) => getPreparedCards(options.db, {
|
|
412
413
|
threadId: optionalString(input.thread_id),
|
|
@@ -1544,8 +1545,8 @@ function optionalSummaryLeafKind(value: unknown): SummaryLeafKind | undefined {
|
|
|
1544
1545
|
|
|
1545
1546
|
function optionalPreparedCardState(value: unknown): PreparedCardState | undefined {
|
|
1546
1547
|
if (value === undefined) return undefined;
|
|
1547
|
-
if (
|
|
1548
|
-
throw new Error(
|
|
1548
|
+
if (typeof value === "string" && (PREPARED_CARD_STATES as readonly string[]).includes(value)) return value as PreparedCardState;
|
|
1549
|
+
throw new Error(`state must be one of ${PREPARED_CARD_STATES.join(", ")}`);
|
|
1549
1550
|
}
|
|
1550
1551
|
|
|
1551
1552
|
function optionalRecentScope(value: unknown): "active" | "recent" | "all" | undefined {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "lossless-openclaw-orchestrator",
|
|
3
3
|
"name": "Lossless OpenClaw Orchestrator",
|
|
4
4
|
"description": "Control and collaborate with local Codex sessions through OpenClaw using local indexing, bounded recall, and approval-gated controls.",
|
|
5
|
-
"version": "1.2.0-beta.
|
|
5
|
+
"version": "1.2.0-beta.3",
|
|
6
6
|
"kind": "tool",
|
|
7
7
|
"tools": {
|
|
8
8
|
"prefix": "loo_"
|
|
@@ -477,7 +477,16 @@
|
|
|
477
477
|
"ready",
|
|
478
478
|
"stale",
|
|
479
479
|
"partial",
|
|
480
|
-
"unknown"
|
|
480
|
+
"unknown",
|
|
481
|
+
"completed",
|
|
482
|
+
"blocked_missing_info",
|
|
483
|
+
"waiting_approval",
|
|
484
|
+
"watching_external_check",
|
|
485
|
+
"needs_resume",
|
|
486
|
+
"dirty_worktree_handoff",
|
|
487
|
+
"stale_or_partial",
|
|
488
|
+
"ready_for_review",
|
|
489
|
+
"unknown_lifecycle"
|
|
481
490
|
]
|
|
482
491
|
},
|
|
483
492
|
"limit": {
|