Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
163 commits
Select commit Hold shift + click to select a range
33e3323
docs(rfc): qualify merged discovery and select managed composition ev…
cocolord Nov 6, 2026
aee08c0
docs(rfc): characterize managed small-kernel composition and recovery
cocolord Nov 6, 2026
55e3ef4
perf(cli): load the version owner for selected version requests
cocolord Nov 6, 2026
60bb3b4
docs(rfc): qualify selected version loading and matched costs
cocolord Nov 6, 2026
8a79d86
fix(cli): retain version facade registrar and handler patch points
cocolord Nov 6, 2026
44ac548
docs(rfc): reconcile corrected version facade and cost qualification
cocolord Nov 6, 2026
41cfb7e
Merge latest target main for selected version integration
cocolord Nov 6, 2026
e3857f6
refactor(turn): consume one complete source through execution and rec…
loopx-agent Nov 6, 2026
5b4f6bb
test(turn): qualify complete source and versioned recovery boundaries
loopx-agent Nov 6, 2026
bff3b29
docs(turn): define the staged lifecycle extraction boundary
loopx-agent Nov 6, 2026
eb6f3eb
refactor(cli): load history reads on demand
cocolord Nov 6, 2026
e32d4db
refactor(runtime): defer process transport at core callers
cocolord Nov 6, 2026
70d0242
test(native): verify policy reads through CLI fixtures
cocolord Nov 7, 2026
0836bd9
fix: preserve producer-bound heartbeat Turn identities
loopx-agent Nov 7, 2026
71bce23
Merge current main into the complete Turn lifecycle slice
loopx-agent Nov 7, 2026
d54cdef
refactor(cli): demand-load registry inspection commands
cocolord Nov 7, 2026
e489b6b
fix(turn): qualify native execution and preserve Host obligations
loopx-agent Nov 7, 2026
f653078
test(turn): preserve quota and complete-source consumer boundaries
loopx-agent Nov 7, 2026
f016cb1
docs(turn): preserve consumer and packaged qualification boundaries
loopx-agent Nov 7, 2026
79665b9
fix(chat): review retained project results without replaying work (#8…
loopx-agent Nov 7, 2026
4df990c
refactor(cli): demand-load heartbeat prequota transport
cocolord Nov 7, 2026
97cc1e0
perf(lark): avoid decoding idle App feedback history (#8241)
loopx-agent Nov 7, 2026
8231ee2
fix(chat): recover admitted project continuation before result review…
loopx-agent Nov 7, 2026
e6d23c4
refactor(turn): defer DSH builders and process helpers to selected pa…
cocolord Nov 7, 2026
d39fac2
refactor(work-items): read the execution handoff modes from the vocab…
Jiaqiiii523 Nov 7, 2026
ebb39f4
test(control-plane): keep hot-path capture out of inline JSON smokes …
Jiaqiiii523 Nov 7, 2026
8e86f17
fix(chat): inspect completed results through the original live thread…
loopx-agent Nov 7, 2026
fed8158
docs: characterize governed CLI composition costs
cocolord Nov 7, 2026
f74b1d4
fix(test): read native policy in first-connect smoke (#8222)
cocolord Nov 7, 2026
b9faf8d
fix(explore): retain paired candidate measurements in one finding (#8…
huangruiteng Nov 7, 2026
fb43dd2
feat(benchmark): support explicit terminal makeup tolerance
loopx-agent Nov 7, 2026
d67599e
docs: clarify duration tolerance qualification boundaries
loopx-agent Nov 7, 2026
978b067
fix(benchmark): expose native execution evidence without declaring Go…
loopx-agent Nov 7, 2026
714dab2
feat(research): register native usage versions without changing selec…
loopx-agent Nov 7, 2026
b5cb6fb
fix(benchmark): isolate native inspection from the trial profile (#8176)
loopx-agent Nov 7, 2026
65b0295
fix(lark): redact credentials from shared Lark sinks (#8181)
JasonBuildAI Nov 7, 2026
9892d5e
refactor(issue-fix): read the queue receipt version from its domain p…
Jiaqiiii523 Nov 7, 2026
34a2076
fix(planning): retain Agent-owned overall requirements from the first…
loopx-agent Nov 7, 2026
37e83f3
test(cli): isolate source-entrypoint machine transport and telemetry …
loopx-agent Nov 7, 2026
9399879
Merge main into registry inspection branch
loopx-agent Nov 7, 2026
c47d8e9
fix(benchmark): retain research outside native submission trees (#8198)
huangruiteng Nov 7, 2026
1175fc0
Merge pull request #8233 from loopx-project/codex/small-kernel-regist…
loopx-agent Nov 7, 2026
00d67fd
Persist corrected success criteria before dependent work (#8199)
huangruiteng Nov 7, 2026
2061c00
Merge main into heartbeat prequota branch
loopx-agent Nov 7, 2026
fa266a2
feat(research): carry scoped yardstick decisions across work turns (#…
huangruiteng Nov 7, 2026
ccea1cd
fix(chat): recover replies completed between queue observations (#8204)
loopx-agent Nov 7, 2026
3e4cf70
Merge pull request #8240 from loopx-project/codex/small-kernel-heartb…
loopx-agent Nov 7, 2026
0ab25ce
fix(benchmark): cap restored phase at audited remaining allowance (#8…
loopx-agent Nov 7, 2026
4a33eb4
Merge pull request #8228 from loopx-project/codex/small-kernel-native…
loopx-agent Nov 7, 2026
e0e88cc
docs: explain what to evaluate with LoopX alongside native Goal (#8209)
loopx-agent Nov 7, 2026
495ab31
Merge main into small-kernel qualification branch
loopx-agent Nov 7, 2026
05a33c2
Merge pull request #8180 from loopx-project/codex/small-kernel-main-q…
loopx-agent Nov 7, 2026
ba71f51
Merge main into selected version loading branch
loopx-agent Nov 7, 2026
4437696
Merge pull request #8203 from loopx-project/codex/small-kernel-json-v…
loopx-agent Nov 7, 2026
b9b2501
fix(benchmark): resume verified interrupted hosts without rewinding s…
huangruiteng Nov 7, 2026
abf1442
feat(goals): verify Goal commitments without gating local Todos (#8161)
huangruiteng Nov 7, 2026
c649fe5
C1: unify work sources and validate recovery (#7430)
loopx-agent Nov 7, 2026
2659e5f
Revert "C1: unify work sources and validate recovery (#7430)" (#8265)
loopx-agent Nov 7, 2026
7f9e235
Merge main into history loading branch
loopx-agent Nov 7, 2026
0744b34
Merge pull request #8212 from loopx-project/codex/small-kernel-histor…
loopx-agent Nov 7, 2026
038de6a
Merge main into caller process loading branch
loopx-agent Nov 7, 2026
87e2482
fix(todo): reconcile explicit CAS after expired execution recovery (#…
loopx-agent Nov 7, 2026
00a8393
fix(workspace): preserve bookmarked research during incomplete reads …
loopx-agent Nov 7, 2026
f565a6b
Merge pull request #8218 from loopx-project/codex/small-kernel-doctor…
loopx-agent Nov 7, 2026
6310d53
docs(research): link retained benchmark trajectories (#8235)
loopx-agent Nov 7, 2026
4bcea96
Merge main into bound heartbeat identity branch
loopx-agent Nov 7, 2026
92500fe
fix(canary): forward premerge base to CLI budget checks (#8242)
loopx-agent Nov 7, 2026
9c6fe70
perf(quota): share settlement readback with reward memory (#8243)
loopx-agent Nov 7, 2026
8a72593
perf(collaboration): size Inbox receipt buffers to each record (#8244)
loopx-agent Nov 7, 2026
864409e
fix(quota): require full write-scope coverage
Jim-jimu Nov 7, 2026
81ad11a
perf(quota): narrow the initial spend settlement readback (#8248)
loopx-agent Nov 7, 2026
f2d150c
perf(chat): batch original delegation lifecycle checks (#8253)
loopx-agent Nov 7, 2026
ce17fb4
perf(turn): defer semantic preference recall during preview (#8254)
loopx-agent Nov 7, 2026
7e239ae
Merge pull request #8232 from loopx-project/codex/bound-heartbeat-tur…
loopx-agent Nov 7, 2026
abb4ee7
docs(governance): record cocolord Maintain access (#8260)
loopx-agent Nov 7, 2026
6cb25b5
fix(benchmark): narrow duration policy report projection
loopx-agent Nov 7, 2026
49c6885
Merge main into benchmark duration tolerance branch
loopx-agent Nov 7, 2026
860a3e4
feat(benchmark): opt in to sampled local work probes from SForge (#8263)
loopx-agent Nov 7, 2026
17e6142
Merge pull request #8258 from loopx-project/codex/benchmark-duration-…
loopx-agent Nov 7, 2026
f1d30b2
fix(benchmark): bound and diagnose native receiver startup (#8262)
loopx-agent Nov 7, 2026
2c3f3cc
Merge main into CLI composition cost branch
loopx-agent Nov 7, 2026
2b00198
Merge pull request #8257 from loopx-project/codex/small-kernel-cli-co…
loopx-agent Nov 7, 2026
c4f045b
test(quota): opt in to local usage-cycle qualification (#8215)
huangruiteng Nov 7, 2026
d7a566e
fix(explore): bind execution evidence to the finding once (#8268)
loopx-agent Nov 7, 2026
80d37a2
fix(chat): preserve steward native context across answer review (#8269)
loopx-agent Nov 7, 2026
46959c7
fix(agents): distinguish native child tools from bound delegation (#8…
huangruiteng Nov 7, 2026
60089b0
fix(benchmark): preserve failed workers before terminal teardown (#8216)
loopx-agent Nov 7, 2026
10f66ec
fix(quota): retain composed settlement failure diagnostics (#8267)
loopx-agent Nov 7, 2026
6b8b857
Ground Yardstick work choices in paired Goal outcomes (#8261)
loopx-agent Nov 7, 2026
22d8daf
Reuse the committed quota capture in native benchmark preflight (#8259)
loopx-agent Nov 7, 2026
438966b
feat(benchmark): register source-bound authored case studies
loopx-agent Nov 7, 2026
6b9353d
feat(research): read case studies inside benchmark child Goals
loopx-agent Nov 7, 2026
1a4d868
test(workspace): exercise return behavior without stale source assert…
loopx-agent Nov 7, 2026
97a34a4
feat(benchmark): align optional native child authorization across arm…
huangruiteng Nov 7, 2026
5d4dbc8
fix(benchmark): explicitly continue repaired quota failures on the or…
huangruiteng Nov 7, 2026
c3dcb92
Merge pull request #8272 from loopx-project/codex/case-research-workb…
huangruiteng Nov 7, 2026
1aa9c3d
Merge main into write-scope coverage branch
Jim-jimu Nov 7, 2026
118b4c7
fix(benchmark): reconcile native terminal evidence before releasing s…
huangruiteng Nov 7, 2026
0e170b1
fix(quota): preserve legacy character-class authorization
Jim-jimu Nov 7, 2026
31efb62
Merge main into write-scope coverage branch
Jim-jimu Nov 7, 2026
3e3e3f8
fix(benchmark): qualify returned legacy budget interruptions (#8221)
loopx-agent Nov 7, 2026
9691995
fix(quota): stage saved replan reads in the shared native presenter (…
huangruiteng Nov 7, 2026
7108b92
fix(workspace): read original state for same-named source Goals (#8278)
loopx-agent Nov 7, 2026
694163d
Merge remote-tracking branch 'upstream/main' into fix/write-scope-cov…
Jim-jimu Nov 7, 2026
e165096
fix(codex): reuse the captured managed claim after lost readiness
loopx-agent Nov 7, 2026
2effc18
fix(workspace): use the current source Goal projection types
loopx-agent Nov 7, 2026
bfecc92
test(benchmark): read direct scheduler state in the budget-stop smoke…
huangruiteng Nov 7, 2026
172d80d
fix(control-plane): preserve original-Turn closeout after automatic a…
cocolord Nov 7, 2026
0d7d15b
fix(benchmark): distinguish unresolved command usage from zero
loopx-agent Nov 7, 2026
7ce7a28
fix(quota): lead saved context responses with the returned page
loopx-agent Nov 7, 2026
f301bfc
fix(workspace): show retained strategy when linked research has no Ex…
loopx-agent Nov 7, 2026
d9934fb
Merge pull request #8280 from loopx-project/codex/fix-goal-source-sta…
loopx-agent Nov 7, 2026
5eebce4
Merge remote-tracking branch 'upstream/main' into fix/write-scope-cov…
Jim-jimu Nov 7, 2026
60167ed
Merge pull request #8282 from loopx-project/codex/benchmark-command-u…
loopx-agent Nov 7, 2026
3216751
Merge pull request #8284 from loopx-project/codex/saved-context-page-…
loopx-agent Nov 7, 2026
d343d4e
Merge commit 'e165096b71969f088185e14f77855ca1a8d46c5a' into codex/fi…
loopx-agent Nov 7, 2026
354c6d5
fix(quota): isolate inactive scope diagnostics and preserve leading h…
Jim-jimu Nov 7, 2026
3dab48a
Merge remote-tracking branch 'upstream/main' into fix/write-scope-cov…
Jim-jimu Nov 7, 2026
8e7c9d6
Require a capture-bound claim identity across native Host upgrades
loopx-agent Nov 7, 2026
9202e66
fix(workspace): group linked research records in the loaded list
loopx-agent Nov 7, 2026
97799d3
Keep native claim capture binding within the quota decision budget
loopx-agent Nov 7, 2026
cb249c0
Verify the live native guard before replaying a captured claim
loopx-agent Nov 7, 2026
4236279
Keep native claim provenance inside the capture writer boundary
loopx-agent Nov 7, 2026
bc84107
Show sourced clocks on child Goal research cards
loopx-agent Nov 7, 2026
b341dd8
Bind explicit Codex preflight to its existing native session
loopx-agent Nov 7, 2026
ec13f27
test(workspace): timestamp stored conversation smoke messages
loopx-agent Nov 7, 2026
0a63389
test(workspace): timestamp stored conversation smoke messages
loopx-agent Nov 7, 2026
8667bdb
fix(dashboard): use workspace theme tokens in the report reader
loopx-agent Nov 7, 2026
538814d
test(workspace): exercise complete and bounded attention separately
loopx-agent Nov 7, 2026
c5f151b
Stage saved replans in stateless progressive context
loopx-agent Nov 7, 2026
3b9b8df
fix(doctor): resolve Windows RECORD paths with native drive semantics
loopx-agent Nov 7, 2026
58be771
test(research): wait for grouped history after expansion
loopx-agent Nov 7, 2026
967c724
test(workspace): timestamp simulated conversation turns
loopx-agent Nov 7, 2026
89c6cb1
Attach saved progressive first pages with Delta off
loopx-agent Nov 7, 2026
388ed5e
docs: qualify closeout recovery and select the next guard cost slice …
cocolord Nov 7, 2026
94b91e4
Merge pull request #8271 from Jim-jimu/fix/write-scope-coverage
huangruiteng Nov 7, 2026
7a2accc
Merge current main into dashboard validation branch
loopx-agent Nov 7, 2026
92d10e2
Skip the root index before an attached progressive page
loopx-agent Nov 7, 2026
edf32c7
Integrate native Windows and Dashboard qualification fixes for local …
loopx-agent Nov 7, 2026
6abb50c
Integrate main before validating saved progressive pages
loopx-agent Nov 7, 2026
775394b
Preserve the early initial-read command diagnostic
loopx-agent Nov 7, 2026
bdb80e1
refactor(material-lifecycle): decide the apply outcome vocabulary in …
Jiaqiiii523 Nov 7, 2026
b586e92
Present generated Goal objectives once on native first reads (#8293)
huangruiteng Nov 7, 2026
8225c89
Reuse the projected body in full native context delivery (#8285)
huangruiteng Nov 7, 2026
98b923c
Defer write scope detail until a progressive Turn is ready
loopx-agent Nov 7, 2026
3b7c4d4
Keep progressive scope selection with the interaction owner
loopx-agent Nov 7, 2026
92a2686
Keep the legacy scope read inside the Turn projection
loopx-agent Nov 7, 2026
dee5496
Pass the shared plan to the stateless progressive reader
loopx-agent Nov 7, 2026
c0b6ade
Merge pull request #8296 from loopx-project/codex/report-reader-theme…
loopx-agent Nov 7, 2026
e9afae0
Merge remote-tracking branch 'origin/main' into codex/research-histor…
loopx-agent Nov 7, 2026
1c71b8b
fix(workspace): preserve complete history with persisted replays
loopx-agent Nov 7, 2026
7cd9c1b
Merge pull request #8287 from loopx-project/codex/research-history-gr…
loopx-agent Nov 7, 2026
b7dab22
Bound native context openings and linked first reads
loopx-agent Nov 7, 2026
72ca8f8
fix(context): show one ready settlement recipe
loopx-agent Nov 7, 2026
3b2a8c7
fix(stride): expose the existing review citation limit
loopx-agent Nov 7, 2026
b2ca568
Merge main into stateless replan capture branch
loopx-agent Nov 7, 2026
34e06a0
Merge remote-tracking branch 'origin/main' into codex/research-child-…
loopx-agent Nov 7, 2026
0a9dda8
Merge pull request #8298 from loopx-project/codex/stateless-replan-ca…
loopx-agent Nov 7, 2026
7f480c0
Merge main into Codex claim capture replay branch
loopx-agent Nov 7, 2026
90ffbe2
Merge pull request #8279 from loopx-project/codex/codex-claim-capture…
loopx-agent Nov 7, 2026
e2371c4
Merge pull request #8291 from loopx-project/codex/research-child-goal…
loopx-agent Nov 7, 2026
cc054bb
Merge current main into the complete Turn lifecycle proposal
loopx-agent Nov 7, 2026
d9cedec
Qualify generic CLI adapters for the complete Turn request
loopx-agent Nov 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
15 changes: 14 additions & 1 deletion .github/GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ access, and takes effect only when the invited contributor accepts it.
| [`@hhyykk`](https://github.com/hhyykk) | Write | Active external collaborator; the 2026-10-21 organization invitation is no longer pending, and active organization membership was not established |
| [`@maxliux5`](https://github.com/maxliux5) | Write | Active; external collaborator |
| [`@JackyCSer`](https://github.com/JackyCSer) | Write | Active; organization Member |
| [`@cocolord`](https://github.com/cocolord) | Write | Active; organization Member |
| [`@cocolord`](https://github.com/cocolord) | Maintain | Active organization Member; repository role updated 2026-11-07 |
| [`@wujc12`](https://github.com/wujc12) | Write | Active; organization Member |
| [`@songoow`](https://github.com/songoow) | Maintain | Active; organization Member |
| [`@now-ing`](https://github.com/now-ing) | Write | Active; organization Member |
Expand Down Expand Up @@ -274,6 +274,19 @@ Update this snapshot through a pull request when invitations are accepted,
expire or are otherwise resolved. Maintainer appointments follow the separate
process below.

On 2026-11-07, the lead maintainer granted `@cocolord` repository Maintain
to help carry the small-kernel refactors through review and merge. Recent
work includes deferred loading at the Host and process boundaries in
[#8185](https://github.com/loopx-project/loopx/pull/8185) and CLI registry
inspection in [#8233](https://github.com/loopx-project/loopx/pull/8233).
The existing [main ruleset](https://github.com/loopx-project/loopx/rules/18121976)
allows Maintain holders to bypass branch rules when merging through a PR.
The [repository merge policy](../AGENTS.md#merge-authority-and-validation)
still requires explicit maintainer authorization, review of the proposed
revision and validation for core refactors. Its limits on merging one's own
PRs continue to apply. Code ownership and release authority follow their
separate procedures.

## Subsystem Maintainers

A subsystem maintainer is accountable for review quality and contract
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,14 @@ assert.deepEqual(goalReplanCadenceEditorValue(null, enabledStrideDefault), enabl
assert.equal(goalReplanCadenceEditorValue({stride_strategy: "off"}, enabledStrideDefault).stride_strategy, "off");
assert.equal(goalReplanCadenceEditorValue({count_unit: "effective_turns", count: 3}, enabledStrideDefault).stride_strategy, "off");
assert.equal(projectEditableCapabilityConfiguration(cadenceEditor, {}, enabledStrideDefault).stride_strategy, "evidence_conditioned_v1");

// Independent Yardstick inheritance must not be pinned by editing the interval.
const yardstickEffective = {...inheritedReview, yardstick_enabled: true};
assert.equal(goalReplanCadenceEditorValue({count_unit: "effective_turns", count: 2}, yardstickEffective).yardstick_enabled, true);
assert.deepEqual(goalReplanCadenceWriteValue({...yardstickEffective, count: 2}, true, false, false),
{...inheritedReview, count: 2}, "an unchanged inherited measurement choice stays live");
for (const enabled of [true, false]) {
assert.deepEqual(goalReplanCadenceWriteValue({...yardstickEffective, yardstick_enabled: enabled}, false, false, true),
{stride_strategy: "off", yardstick_enabled: enabled}, "explicit measurement opt-in and opt-out survive form saving");
}
assert.equal(replanCadenceEditorDefaults(yardstickEffective, {...cadenceDefault, yardstick_enabled: false}).yardstick_enabled, true);
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ for (const copy of Object.values(deliveryReviewCopy)) {
scope: { kind: "selected_work", todo_ids: ["todo_confirmed"] } }), snapshot.goal_id);
assert.ok(deliveryReviewMarkdown(selected, copy).includes(copy.contract.selectedWork));
assert.ok(deliveryReviewMarkdown(parseDeliveryReview(withContract(contract), snapshot.goal_id), copy).includes(copy.contract.allWork));
const goalOnly = parseDeliveryReview(withContract({ ...contract,
scope: { kind: "goal" }, tasks: [], held_todo_ids: [] }), snapshot.goal_id);
assert.ok(deliveryReviewMarkdown(goalOnly, copy).includes(copy.contract.goalOnly));
const empty = parseDeliveryReview(withContract({ ...contract, criteria: [], tasks: [] }), snapshot.goal_id);
assert.ok(deliveryReviewMarkdown(empty, copy).includes(copy.contract.noTasks));
assert.ok(deliveryReviewMarkdown(empty, copy).includes(copy.contract.noCriteria));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,50 @@ import {mkdtemp, mkdir, readFile, writeFile, rm} from "node:fs/promises";
import {tmpdir} from "node:os";
import {resolve} from "node:path";
import {createInterface} from "node:readline";
import {createElement} from "react";
import {renderToStaticMarkup} from "react-dom/server";
import {resolveTestPython} from "../../../../scripts/test-python.mjs";
import {readAnalysisSnapshot} from "../src/features/personal-workspace/goal-research-analysis.tsx";
import {readAnalysisSnapshot, ResearchAnalysisPanel} from "../src/features/personal-workspace/goal-research-analysis.tsx";
import {ResearchCaseStudy} from "../src/features/personal-workspace/research-case-study.tsx";
import {fetchExploreResults} from "../src/data/chat.ts";
import {ResearchGoalNavigation} from "../src/features/personal-workspace/goal-research-workbench.tsx";

// The persisted directory can organize explicitly linked physical sources.
// Navigation must not turn organization into shared evidence or execution.
const caseFixture = JSON.parse(await readFile(resolve('src/data/fixtures/case-study-child.json'), 'utf8'));
const study = caseFixture.analyses[0].case_study;
const renderedStudy = renderToStaticMarkup(createElement(ResearchCaseStudy, {
study, zh: false, historical: true, scope: caseFixture.analyses[0].input.scope, onCitation: () => {throw Error('read executed a citation');},
}));
assert.ok(renderedStudy.includes(study.title));
assert.ok(renderedStudy.includes('Showing the saved Chinese original.'));
assert.ok(renderedStudy.includes('not current results'));
assert.ok(renderedStudy.includes('unknown'), 'Missing values do not become zero');
for (const section of study.sections) assert.ok(renderedStudy.includes(section.title));
const goals = [
{goalId:'parent',title:'Benchmark'},
{goalId:'child',parentGoalId:'parent',title:'Local case'},
{goalId:'foreign',parentGoalId:'parent',title:'Foreign case',sourceAccess:{namespace:'other',label:'Other',readOnly:true}},
];
const navigate = goalId => renderToStaticMarkup(createElement(ResearchGoalNavigation, {
goalId, goals, zh:false, onSelect: () => {throw Error('read navigated');},
}));
assert.ok(navigate('parent').includes('Local case'));
assert.ok(navigate('parent').includes('Foreign case'));
assert.ok(navigate('child').includes('Benchmark'));
assert.ok(navigate('foreign').includes('Benchmark'), 'Explicit directory links retain their local parent');

// Transport uses the workspace view; immutable findings still identify the native Goal.
const sourceFetch = globalThis.fetch, sourceWindow = globalThis.window;
try {
globalThis.window = {location: {href: "http://127.0.0.1:12345/chat/"}};
globalThis.fetch = async input => {
assert.equal(new URL(input, window.location.href).searchParams.get("goal_id"), "view-a");
return Response.json({ok: true, goal_id: "native", total: 0, items: [], next_cursor: null});
};
assert.equal((await fetchExploreResults("view-a", undefined, undefined, "native")).goal_id, "native");
await assert.rejects(fetchExploreResults("view-a"), /mismatch/);
} finally { globalThis.fetch = sourceFetch; globalThis.window = sourceWindow; }

// A language-specific cache miss must not hide the same registered pack's
// older, unspecified-language result or turn a read into model execution.
Expand Down Expand Up @@ -86,6 +128,25 @@ try {
failOriginal = false;
assert.deepEqual((await read()).originalPack, original, "The ordinary read retries recovery without execution");

// A failed/in-flight observation is unknown; only a successful empty read
// supports the absence message. Keep the existing read retry as recovery.
for (const zh of [true, false]) {
const empty = {...pack, availability: "available", selection: {exhaustive: false}, episode_count: 0};
const controller = {zh, pack: empty, requestPack: empty, location: {open: true}, panel: {current: null},
readOnly: true, loading: false, snapshot: {catalog: {}}, read: async () => {}, close: () => {}};
const noSaved = zh ? "此来源尚无已保存的分析。" : "This source has no saved analysis yet.";
const render = state => renderToStaticMarkup(createElement(ResearchAnalysisPanel, {analysis: {...controller, ...state}}));
assert.ok(render({}).includes(noSaved), "A successful empty source retains its ordinary absence state");
for (const state of [{loading: true}, {readError: "Source unavailable"}, {snapshot: {catalog: {}, originalReadError: "Original unavailable"}}]) {
const html = render(state);
assert.ok(!html.includes(noSaved), "An incomplete source read must not claim that no saved analysis exists");
if (!state.loading) {
assert.ok(html.includes('role="alert"'));
assert.ok(html.includes(zh ? 'aria-label="重试读取"' : 'aria-label="Retry reading"'));
}
}
}

for (const options of [{current: () => false}, {signal: AbortSignal.abort()}]) {
reset("zh");
value = await read(true, undefined, options.signal, options.current);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export function replanCadenceEditorDefaults(effective: unknown, fallback: unknow
if (Object.hasOwn(live, "count_unit")) defaults.count_unit = live.count_unit;
if (Object.hasOwn(live, "count")) defaults.count = live.count;
if (Object.hasOwn(live, "planning_horizon_goal")) defaults.planning_horizon_goal = live.planning_horizon_goal;
if (Object.hasOwn(live, "yardstick_enabled")) defaults.yardstick_enabled = live.yardstick_enabled;
// Strategy omission keeps the existing default-off meaning.
return defaults;
}
Expand All @@ -102,13 +103,15 @@ export function goalReplanCadenceWriteValue(
draft: Record<string, unknown>,
cadenceOverride: boolean,
horizonOverride = Object.hasOwn(draft, "planning_horizon_goal"),
yardstickOverride = Object.hasOwn(draft, "yardstick_enabled"),
): Record<string, unknown> {
const value = { ...draft };
if (!cadenceOverride) {
delete value.count_unit;
delete value.count;
}
if (!horizonOverride) delete value.planning_horizon_goal;
if (!yardstickOverride) delete value.yardstick_enabled;
return value;
}

Expand Down
36 changes: 34 additions & 2 deletions apps/presentation/dashboard/src/data/chat.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {normalizeWorkYardstickCapture} from "../../../../../loopx/control_plane/work_items/yardstick_decision.js";
import {sourceReadApiUrl} from "./local-status-query";
import type {OracleEvidence} from "./goal-acceptance-observation";
import { HANDOFF_MODES, EXECUTION_HANDOFF_MODES, type ExecutionHandoffMode } from "../../../../../loopx/control_plane/coordination/handoff_mode_vocabulary.js";
Expand Down Expand Up @@ -3008,16 +3009,47 @@ const exploreResultPageSchema = z.object({
evidence_refs: z.array(z.string()), last_updated_at: z.string(),
measurements: z.unknown().transform(value => normalizeExploreMeasurements(value)).optional(),
measurement_error: z.string().optional(),
work_yardstick: z.unknown().transform(capture => normalizeWorkYardstickCapture({capture})).optional(),
linked_todos_available: z.boolean().optional(),
linked_todos: z.array(z.object({
todo_id: z.string(), text: z.string(), status: z.string(), claimed_by: z.string(),
})),
})),
});
export type ExploreResultPage = z.infer<typeof exploreResultPageSchema>;
export async function fetchExploreResults(goalId: string, cursor?: string, readSourceUrl?: string) {
export async function fetchExploreResults(goalId: string, cursor?: string, readSourceUrl?: string, originalGoalId = goalId) {
const query = new URLSearchParams({goal_id: goalId, view: "findings", ...(cursor ? {cursor} : {})});
const page = exploreResultPageSchema.parse(await requestJson<unknown>(sourceReadApiUrl(readSourceUrl, `/api/chat/explore-results?${query}`)));
if (page.goal_id !== goalId) throw new Error("Explore result Goal mismatch");
if (page.goal_id !== originalGoalId) throw new Error("Explore result Goal mismatch");
for (const item of page.items) if (item.work_yardstick) normalizeWorkYardstickCapture({
capture: item.work_yardstick, goal_id: originalGoalId, agent_id: item.agent_id, evidence_refs: item.evidence_refs,
});
return page;
}

const researchHistoryTimingSchema = z.object({
ok: z.literal(true),
goal_id: z.string(),
source_namespace: z.string().nullable(),
first_recorded_at: z.iso.datetime().nullable(),
last_recorded_at: z.iso.datetime().nullable(),
untimed_record_count: z.number().int().nonnegative(),
strategy_recorded_at: z.iso.datetime().nullable(),
}).refine(value => value.first_recorded_at === null
? value.last_recorded_at === null
: value.last_recorded_at !== null && Date.parse(value.first_recorded_at) <= Date.parse(value.last_recorded_at));
export type ResearchHistoryTiming = z.infer<typeof researchHistoryTimingSchema>;

export async function fetchResearchHistoryTiming(
goalId: string, originalGoalId: string, namespace: string | null, directoryUrl: string | undefined, signal: AbortSignal,
): Promise<ResearchHistoryTiming> {
const statusUrl = namespace ? `/status.json?source_namespace=${namespace}` : directoryUrl;
const query = new URLSearchParams({goal_id: goalId, view: "history-timing"});
const timing = researchHistoryTimingSchema.parse(await requestJson<unknown>(
sourceReadApiUrl(statusUrl, `/api/chat/explore-results?${query}`), {signal},
));
if (timing.goal_id !== originalGoalId || timing.source_namespace !== namespace) {
throw new Error("Research timing source mismatch");
}
return timing;
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,28 @@ assert.deepEqual(distinct.map(row => row.marker), ["stream", "foreign", "another
"Equal text, other Sessions/Turns and separate follow-up identities remain distinct");
console.log("conversation-returns: history-before-admission preserves the live identity and distinct replies");

// A fast queued Turn can finish before its start/stream is observed. Its
// canonical answer replaces the waiting receipt, never a live stream.
const queuedReply = {sourceSessionId: "current", sourceTurnId: "same-turn", role: "assistant",
text: "", queued: true, pending: false};
const completedQueue = reconcileConversationHistory([queuedReply], [storedRoles[1]], createHistory);
assert.equal(completedQueue.length, 1);
assert.equal(completedQueue[0].text, storedRoles[1].text);
assert.equal(completedQueue[0].queued, false);
assert.equal(completedQueue[0].sourceMessageId, "agent-message");
assert.equal(reconcileConversationHistory(completedQueue, [storedRoles[1]], createHistory), completedQueue);
const unboundQueuedReply = {...queuedReply, sourceTurnId: undefined, sourceMessageId: "agent-message"};
const unboundAnswer = reconcileConversationReturns([unboundQueuedReply], "current", [{...storedRoles[1], turn_id: undefined}], createHistory);
assert.equal(unboundAnswer[0].text, "");
assert.equal(unboundAnswer[0].queued, true, "Two absent Turn identities do not prove queue execution");
for (const change of [{pending: true, text: "Live fragment"}, {queued: false, text: "Existing answer"},
{sourceSessionId: "foreign"}, {sourceTurnId: "another-turn"}]) {
const original = {...queuedReply, ...change};
const observed = reconcileConversationHistory([original], [storedRoles[1]], createHistory);
assert.equal(observed.find(row => row === original || row.sourceTurnId === original.sourceTurnId
&& row.sourceSessionId === original.sourceSessionId)?.text, original.text);
}

// A revised brief keeps its return obligation despite a delivered old conclusion.
const revised = [{sourceSessionId: "original", collaborations: [
{request_id: "a", request_revision: 2, returns: [{phase: "conclusion", status: "delivered", request_revision: 1}]},
Expand Down
10 changes: 7 additions & 3 deletions apps/presentation/dashboard/src/data/conversation-returns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type ConversationMessage = {
sourceCreatedAt?: string;
role?: string;
pending?: boolean;
queued?: boolean;
collaboration?: ChatVisibleMessage["collaboration"];
collaborations?: ChatVisibleMessage["collaborations"];
returnDelivery?: ChatVisibleMessage["return_delivery"];
Expand Down Expand Up @@ -81,16 +82,19 @@ export function reconcileConversationReturns<T extends ConversationMessage>(
&& source.queue_input_revision > (row.queueInputRevision ?? 0);
const queueInputRevision = inputChanged ? source.queue_input_revision : row.queueInputRevision;
const queueInputCancelled = inputChanged ? source.queue_input_cancelled : row.queueInputCancelled;
const text = inputChanged ? source.text : row.role === "user" && source.reply_context && row.text === source.text
const queueAnswered = row.queued === true && !row.pending && row.role === "assistant"
&& Boolean(row.sourceTurnId) && source.role !== "user" && source.origin !== "manager_followup" && source.turn_id === row.sourceTurnId;
const queued = queueAnswered ? false : row.queued;
const text = inputChanged || queueAnswered ? source.text : row.role === "user" && source.reply_context && row.text === source.text
? source.reply_context.source_text : row.text;
if (row.sourceMessageId === source.message_id && JSON.stringify(row.returnDelivery) === JSON.stringify(returnDelivery)
&& JSON.stringify(row.collaboration) === JSON.stringify(collaboration)
&& JSON.stringify(row.collaborations) === JSON.stringify(collaborations) && row.sourceCreatedAt === sourceCreatedAt
&& JSON.stringify(row.replyContext) === JSON.stringify(replyContext) && row.text === text
&& row.queueInputRevision === queueInputRevision && row.queueInputCancelled === queueInputCancelled) return row;
&& row.queueInputRevision === queueInputRevision && row.queueInputCancelled === queueInputCancelled && row.queued === queued) return row;
changed = true;
return { ...row, sourceMessageId: source.message_id, sourceCreatedAt, returnDelivery, collaboration, collaborations, replyContext, text,
queueInputRevision, queueInputCancelled };
queueInputRevision, queueInputCancelled, ...(queueAnswered ? {queued: false} : {}) };
});
for (const message of messages) {
if (message.origin !== "manager_followup" || seen.has(message.message_id)) continue;
Expand Down
Loading
Loading