Skip to content

Commit c797daf

Browse files
committed
Merge remote-tracking branch 'origin/main' into codex/goalref-owner-fence-regression
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
2 parents b2ee0eb + 3b73108 commit c797daf

138 files changed

Lines changed: 10198 additions & 898 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎AGENTS.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ dependency and why the boundary improves verification or rollback. Reuse or
3636
update an existing successor; do not create ceremonial follow-up tasks for a
3737
completed request. Real authorization, cost and operational stop gates remain.
3838

39+
When a change introduces or extends a state classification, protocol literal,
40+
Enum, `Literal`, named closed set, or TypeScript `as const` vocabulary, answer
41+
whether it reuses an existing owner, extends a registered vocabulary, remains
42+
local, or creates a new shared contract. Before the full-tree semantic check,
43+
run the development-time advisory over the current diff:
44+
45+
```bash
46+
uv run python scripts/generate_semantic_inventory.py --changed-from HEAD
47+
```
48+
49+
Pass each intentional untracked source with `--include-untracked loopx/path.py`;
50+
the probe never scans untracked or ignored files automatically. Findings are
51+
review prompts, not a gate or proof of semantic equivalence, and an empty result
52+
does not cover dynamic construction or unsupported syntax.
53+
3954
For multi-Agent changes, qualify the relationship the user needs: dependency
4055
artifacts, receiver adoption, claim/lease handling, independent acceptance and
4156
result return as applicable. Sending a message or registering workers does not

‎CONTRIBUTING.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,28 @@ large or behavior-changing work whose outcome is not yet agreed.
5656

5757
Small docs typo fixes and obviously safe cleanups can go straight to a PR.
5858

59+
### Check New Semantic Vocabulary While Developing
60+
61+
If a change adds or extends an Enum, `Literal`, named closed set, TypeScript
62+
`as const` array, or another state/protocol vocabulary, run the diff-scoped
63+
advisory before the full-tree semantic check:
64+
65+
```bash
66+
uv run python scripts/generate_semantic_inventory.py --changed-from HEAD
67+
```
68+
69+
The command compares the named Git baseline with committed, staged, and working
70+
tree source changes, and reads the Git index and the working tree as separate
71+
snapshots so a staged addition survives a restored working file. It does not
72+
discover untracked files; include each intended
73+
new source explicitly, for example
74+
`--include-untracked loopx/control_plane/new_contract.ts`. It reports supported
75+
candidate carriers, registered-vocabulary reuse hints, and the disposition
76+
question to answer in review. Findings exit successfully because this is an
77+
advisory prompt, while invalid revisions, unreadable inputs, and tool failures
78+
exit non-zero. An empty report is not evidence that dynamic or unsupported
79+
semantic forms were analyzed.
80+
5981

6082
## Public And Private Boundaries
6183

‎apps/presentation/dashboard/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"build:chat:vite": "tsc --noEmit && vite build --config vite.chat.config.ts",
6262
"smoke:chat-upgrade": "LOOPX_PLAYWRIGHT_PACKAGE=\"$PWD/node_modules/playwright\" node ../../../examples/chat-bundle-upgrade-browser-smoke.mjs",
6363
"test:conversation-returns": "node --experimental-strip-types src/data/conversation-returns.test.mjs",
64+
"smoke:conversation-history": "vite build --ssr smoke/conversation-history-smoke.ts --outDir node_modules/.cache/loopx-conversation-history --emptyOutDir && node node_modules/.cache/loopx-conversation-history/conversation-history-smoke.js",
6465
"smoke:recent-completions": "tsc --ignoreConfig --target ES2022 --module CommonJS --moduleResolution Node --ignoreDeprecations 6.0 --resolveJsonModule --esModuleInterop --jsx react-jsx --skipLibCheck --strict --types node --outDir /tmp/loopx-recent-completions-smoke smoke/recent-completions-smoke.ts && NODE_PATH=\"$PWD/node_modules\" node /tmp/loopx-recent-completions-smoke/apps/presentation/dashboard/smoke/recent-completions-smoke.js"
6566
},
6667
"dependencies": {

‎apps/presentation/dashboard/smoke/action-review-plan-smoke.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,35 @@ check(operationPlan.interaction === "gated", "Operation execution keeps its auth
121121
check(operationPlan.operationFrame?.kind === "confirmation", "Dashboard consumes the shared confirmation frame");
122122
check(operationPlan.operationFrame?.interactionMode === "confirm_reject", "The shared frame preserves confirm/reject interaction");
123123
check(operationPlan.operationFrame?.content.fields[0]?.value === "Limit · GTC", "The shared frame preserves bounded projection fields");
124+
125+
const agentPending = typedActionProposalSchema.parse({...operationProposal, status: "applying",
126+
normalized_parameters: {...operationProposal.normalized_parameters, executor: {kind: "agent_session"}},
127+
operation: {...operationProposal.operation, lifecycle_state: "claimed", agent_handoff: {consumption_id: "attempt-1"}}});
128+
const agentPendingFrame = compileActionReviewPlan(agentPending).operationFrame;
129+
check(agentPendingFrame?.kind === "pending" && agentPendingFrame.executionState === "consumed_outcome_pending",
130+
"Transport retains the original consumption; it does not imply an external result");
131+
const unauthenticatedFrame = compileActionReviewPlan({...agentPending,
132+
operation: {...agentPending.operation, agent_handoff: null}}).operationFrame;
133+
check(unauthenticatedFrame?.kind === "pending" && unauthenticatedFrame.executionState === "host_authentication_required",
134+
"Human confirmation alone cannot qualify original-host authentication");
135+
const unknownAgentResult = typedActionProposalSchema.parse({...agentPending, status: "applied",
136+
receipt: {projection_verified: true}, operation: {...agentPending.operation, lifecycle_state: "outcome_observed",
137+
outcome: {outcome: "submission_unknown", simulation: false}, result_delivery: {outcome_stage: "initial"}}});
138+
const managedPending = typedActionProposalSchema.parse({...agentPending,
139+
normalized_parameters: {...agentPending.normalized_parameters, agent_id: "managed-worker", executor: {
140+
kind: "managed_turn", todo_id: "todo-managed", session_id: "owned-thread", profile_digest: "a".repeat(64),
141+
model: "test-model", reasoning_effort: "xhigh", revision: "managed-turn-handoff-v0"}},
142+
operation: {...agentPending.operation, agent_handoff: null}});
143+
const managedFrame = compileActionReviewPlan(managedPending).operationFrame;
144+
check(managedFrame?.kind === "pending" && managedFrame.executionState === "managed_turn_pending",
145+
"Managed confirmation waits for its exact admitted executor rather than Desktop authentication");
146+
check(managedFrame?.content.fields.some(field => field.value.includes("test-model@xhigh")) === true,
147+
"The shared managed profile survives the frontend schema transport");
148+
check(compileActionReviewPlan(managedPending).canApply === false, "Managed approval exposes no local execute control");
149+
check(compileActionReviewPlan(unknownAgentResult).interaction === "repair", "Delivered unknown submission is not completion");
150+
const reconciledAgentResult = typedActionProposalSchema.parse({...unknownAgentResult,
151+
operation: {...unknownAgentResult.operation, reconciliation: {outcome: "not_executed", simulation: false}}});
152+
check(compileActionReviewPlan(reconciledAgentResult).interaction === "repair", "Old card delivery cannot certify a new reconciliation");
153+
check(compileActionReviewPlan({...reconciledAgentResult, operation: {...reconciledAgentResult.operation,
154+
result_delivery: {outcome_stage: "reconciled"}}}).interaction === "completed", "Only reconciled card readback completes presentation");
155+
console.log("PASS: original-Agent handoff and append-only reconciliation survive the frontend transport");
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""Disposable real Chat HTTP/store fixture; the fault changes reads only."""
2+
from __future__ import annotations
3+
4+
import json
5+
from pathlib import Path
6+
import sys
7+
import tempfile
8+
import threading
9+
10+
sys.path.insert(0, str(Path(__file__).resolve().parents[4]))
11+
12+
from loopx.chat_runtime import ChatRuntimeController
13+
from loopx.chat_server import ChatHTTPServer, ChatRequestHandler
14+
from loopx.chat_store import ChatSessionStore
15+
16+
17+
class HistoryReadFault(ChatRequestHandler):
18+
def _session_snapshot(self, session_id: str) -> None:
19+
if session_id == self.server.unavailable_session_id:
20+
self._send_error("Synthetic history read unavailable", status=503)
21+
else:
22+
super()._session_snapshot(session_id)
23+
24+
25+
def main() -> None:
26+
with tempfile.TemporaryDirectory(prefix="loopx-history-http-") as directory:
27+
root = Path(directory)
28+
registry = root / "registry.json"
29+
registry.write_text(json.dumps({"schema_version": "0.1", "goals": [
30+
{"id": "research", "repo": str(root), "status": "active"},
31+
]}), encoding="utf-8")
32+
store = ChatSessionStore(root / "runtime")
33+
runtime = ChatRuntimeController(store=store, codex_bin="missing-codex", registry_path=registry)
34+
for session_id, channel, text in [
35+
("old", "goal.research", "Earlier public report"),
36+
("current", "goal.research", "Current public report"),
37+
("other-channel", "manager", "Unrelated conversation"),
38+
]:
39+
store.create_session(goal_id="research", agent_id="codex", adapter_kind="codex_app_server",
40+
upstream_thread_id=session_id, channel_id=channel, session_id=session_id)
41+
store.append_message(session_id, role="agent", text=text, message_id="answer")
42+
before = {str(path.relative_to(store.sessions_root)): path.read_bytes()
43+
for path in store.sessions_root.rglob("*") if path.is_file()}
44+
server = ChatHTTPServer(("127.0.0.1", 0), HistoryReadFault)
45+
server.verbose = False
46+
server.registry_path = registry
47+
server.runtime_root = root / "runtime"
48+
server.chat_store = store
49+
server.runtime_controller = runtime
50+
server.unavailable_session_id = "old"
51+
thread = threading.Thread(target=server.serve_forever, daemon=True)
52+
thread.start()
53+
print(json.dumps({"origin": f"http://127.0.0.1:{server.server_port}"}), flush=True)
54+
try:
55+
if sys.stdin.readline().strip() != "recover":
56+
raise ValueError("Expected read recovery")
57+
server.unavailable_session_id = ""
58+
print(json.dumps({"recovered": True}), flush=True)
59+
if sys.stdin.readline().strip() != "inspect":
60+
raise ValueError("Expected final inspection")
61+
after = {str(path.relative_to(store.sessions_root)): path.read_bytes()
62+
for path in store.sessions_root.rglob("*") if path.is_file()}
63+
print(json.dumps({"store_unchanged": before == after,
64+
"turn_count": sum(1 for _ in store.sessions_root.glob("*/turns/*.json"))}), flush=True)
65+
finally:
66+
server.shutdown()
67+
server.server_close()
68+
thread.join(timeout=5)
69+
runtime.close()
70+
71+
72+
if __name__ == "__main__":
73+
main()
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import assert from "node:assert/strict";
2+
import { spawn } from "node:child_process";
3+
import { once } from "node:events";
4+
import { resolve } from "node:path";
5+
import { createInterface } from "node:readline";
6+
import { resolveTestPython } from "../../../../scripts/test-python.mjs";
7+
import { fetchChatHistory } from "../src/data/chat";
8+
9+
const repoRoot = resolve(process.cwd(), "../../..");
10+
const child = spawn(resolveTestPython({ repoRoot }), ["-u", "apps/presentation/dashboard/smoke/conversation-history-http-fixture.py"],
11+
{ cwd: repoRoot, stdio: ["pipe", "pipe", "pipe"] });
12+
const exited = once(child, "exit");
13+
let stderr = "";
14+
child.stderr.on("data", chunk => { stderr += String(chunk); });
15+
const output = createInterface({ input: child.stdout });
16+
const lines = output[Symbol.asyncIterator]();
17+
const originalFetch = globalThis.fetch;
18+
async function next() {
19+
const line = await lines.next();
20+
assert.equal(line.done, false, stderr);
21+
return JSON.parse(line.value!);
22+
}
23+
try {
24+
const { origin } = await next();
25+
const requests: string[] = [];
26+
globalThis.fetch = (input, init) => {
27+
const path = String(input);
28+
assert.ok(!init?.method || init.method === "GET", "History recovery is read-only");
29+
requests.push(path);
30+
return originalFetch(new URL(path, origin), init);
31+
};
32+
const options = { agentId: "codex", goalId: "research", channelId: "goal.research" };
33+
const partial = await fetchChatHistory(options);
34+
assert.deepEqual(partial.unavailableSessionIds, ["old"]);
35+
assert.deepEqual(partial.messages.map(row => row.text), ["Current public report"]);
36+
assert.equal(partial.sessions[0].session_id, "current");
37+
assert.equal(partial.sessions.some(row => row.session_id === "other-channel"), false);
38+
child.stdin.write("recover\n");
39+
assert.equal((await next()).recovered, true);
40+
requests.length = 0;
41+
const restored = await fetchChatHistory(options, partial);
42+
assert.deepEqual(requests, ["/api/chat/sessions/old"], "Recovery reads only the missing session");
43+
assert.deepEqual(restored.unavailableSessionIds, []);
44+
assert.deepEqual(restored.messages.map(row => row.text), ["Earlier public report", "Current public report"]);
45+
assert.deepEqual(restored.messages.map(row => row.session_id), ["old", "current"], "Colliding IDs preserve both sessions");
46+
requests.length = 0;
47+
await fetchChatHistory(options, restored);
48+
assert.deepEqual(requests, [], "A complete cached history does not poll");
49+
child.stdin.end("inspect\n");
50+
assert.deepEqual(await next(), { store_unchanged: true, turn_count: 0 });
51+
const [exitCode] = await exited;
52+
assert.equal(exitCode, 0, stderr);
53+
console.log("conversation-history: passed (real HTTP/store, partial read, channel isolation, missing-only recovery, scoped identity, zero writes or Turns)");
54+
} finally {
55+
globalThis.fetch = originalFetch;
56+
output.close();
57+
if (child.exitCode === null) { child.kill(); await exited; }
58+
}

‎apps/presentation/dashboard/smoke/delegation-preflight-browser-smoke.mjs‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
33
import {mkdir} from "node:fs/promises";
44
import {resolve} from "node:path";
55
import {delegationPreflight} from "../../../../loopx/control_plane/collaboration/delegation.ts";
6+
import {projectManagedOperationTransport} from "../../../../loopx/control_plane/work_items/operation_agent_handoff.ts";
67
import {launchBrowser, loadPlaywright, waitForHttp} from "../../../../examples/dashboard-browser-smoke-support.mjs";
78
import {outputDir, packaged, port, startServer} from "../../../../examples/personal-workspace-browser/fixture.mjs";
89
import {openWorkspacePage} from "../../../../examples/personal-workspace-browser/scenario-context.mjs";
@@ -35,7 +36,16 @@ try {
3536
acceptance_declaration_mismatch: "completion_validation_declaration_mismatch",
3637
acceptance_unknown: "/private/validator PRIVATE_VALUE",
3738
};
38-
const check = state.startsWith("acceptance_")
39+
const operation = projectManagedOperationTransport({host: "codex-cli", sandbox: "read-only",
40+
model: state === "operation_invalid" ? null : "test-model", reasoning_effort: "xhigh"});
41+
const check = state.startsWith("operation_")
42+
? delegationPreflight({binding, acceptance: {todo_id: binding.todo_id, state: "ready"},
43+
validation_files_current: true, preview: {dry_run: true, status: "preview",
44+
effects: {host_invoked: false, state_written: false, quota_spent: false, scheduler_acknowledged: false},
45+
route: {kind: "ready_for_host", would_invoke_host: true, selected_todo_id: binding.todo_id},
46+
managed_executor: {executor: "codex-cli", available: operation.reason ? false : null,
47+
unavailable_reason: operation.reason, execution_profile: "test-model@xhigh", operation_transport: operation.transport}}})
48+
: state.startsWith("acceptance_")
3949
? delegationPreflight({binding,
4050
acceptance: {todo_id: binding.todo_id, state: state === "acceptance_files" ? "ready" : "unbound",
4151
reason: acceptanceReasons[state]}, validation_files_current: false,
@@ -113,6 +123,19 @@ try {
113123
}
114124
}
115125
}
126+
for (const valid of [true, false]) {
127+
state = valid ? "operation_valid" : "operation_invalid";
128+
await team.getByRole("button", {name: zh ? "检查整个团队" : "Check whole team", exact: true}).click();
129+
const observations = team.locator(".goal-team-bindings > li > p[role=status]");
130+
const expected = valid ? (zh ? "运行未核验" : "runtime unqualified")
131+
: (zh ? "操作传输配置未获准" : "Operation transport configuration not admitted");
132+
await observations.filter({hasText: expected}).nth(2).waitFor();
133+
for (const [size, viewport] of [["desktop", {width: 1512, height: 982}], ["mobile", {width: 390, height: 844}]]) {
134+
await page.setViewportSize(viewport);
135+
assert(await dialog.evaluate(el => el.scrollWidth <= el.clientWidth), "Operation transport readback must not overflow");
136+
if (valid) await page.screenshot({path: resolve(outputDir, `operation-preflight-${zh ? "zh" : "en"}-${size}.png`), animations: "disabled"});
137+
}
138+
}
116139
const member = team.locator(".goal-team-bindings > li").filter({hasText: "local-analyst"});
117140
await member.locator("summary").click();
118141
state = "available";

‎apps/presentation/dashboard/smoke/delegation-preflight-smoke.tsx‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,20 @@ for (const zh of [true, false]) {
8080
}
8181

8282
console.log("delegation authority/workspace/validation preflight smoke passed");
83+
84+
for (const valid of [true, false]) {
85+
const check = {...unavailable, state: "runtime_unverified", authority_ready: true,
86+
authority_reason: null, authority_state: "promoted", authority_next_action: "none",
87+
executor: {host: "codex-cli", available: valid, reason: valid ? null : "operation_transport_profile_required",
88+
profile: "test-model@xhigh", operation_transport: {schema_version: "loopx_operation_transport_v0",
89+
configuration_valid: valid, runtime_qualified: false}}} as DelegationPreflight;
90+
for (const zh of [false, true]) {
91+
const html = renderToStaticMarkup(<DelegationPreflightStatus check={check} zh={zh}/>);
92+
if (!html.includes(valid ? (zh ? "运行未核验" : "runtime unqualified")
93+
: (zh ? "操作传输配置未获准" : "Operation transport configuration not admitted"))) {
94+
throw new Error("Shared transport projection lost truthful configuration readback");
95+
}
96+
if (/Runtime qualified|运行已核验/.test(html)) throw new Error("Preflight invented transport qualification");
97+
}
98+
}
99+
console.log("managed operation transport remains configuration-only in delegation preflight");

0 commit comments

Comments
 (0)