Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions architecture.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/acp/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type {
AcpRuntimeEnsureInput,
AcpRuntimeEvent,
AcpRuntimeHandle,
AcpRuntimeMaterialization,
AcpRuntimeOptions,
AcpRuntimeTurn,
AcpRuntimeTurnInput,
Expand Down
390 changes: 349 additions & 41 deletions packages/acp/src/provider.ts

Large diffs are not rendered by default.

26 changes: 23 additions & 3 deletions packages/acp/src/session-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ export interface SessionCandidate {
cwd: string;
}

/**
* One candidate, and whether a session already lives there.
*
* `pending` means this key names where a session will be, and nothing has
* constructed one yet. `established` means a durable record already stands
* behind it — including a record written before deferred materialization
* existed, which carries no marker and asserted its identity when it was
* created.
*/
export interface SessionPlacement extends SessionCandidate {
state: "pending" | "established";
}

function digest(value: string): string {
return createHash("sha256").update(value).digest("hex").slice(0, 16);
}
Expand Down Expand Up @@ -82,13 +95,17 @@ export function* sessionCandidates(
* Select where a session lives: the nearest candidate whose ACPX record
* exists for the same agent command and directory, or the exact
* contextual cwd when none does.
*
* A record still marked pending is occupancy rather than a conversation — its
* first turn was never accepted — so the placement it answers with is pending
* too, and the next operation constructs rather than continues.
*/
export function* resolveSessionPlacement(
store: AcpSessionStore,
agentCommand: string,
cwdPath: string,
name?: string,
): Operation<SessionCandidate> {
): Operation<SessionPlacement> {
const candidates = yield* sessionCandidates(agentCommand, cwdPath, name);
for (const candidate of candidates) {
const record = yield* until(store.load(candidate.sessionKey));
Expand All @@ -97,8 +114,11 @@ export function* resolveSessionPlacement(
record.agentCommand === agentCommand &&
resolve(record.cwd) === resolve(candidate.cwd)
) {
return candidate;
return {
...candidate,
state: record.sessionMaterialization?.state === "pending" ? "pending" : "established",
};
}
}
return candidates[0]!;
return { ...candidates[0]!, state: "pending" };
}
2 changes: 2 additions & 0 deletions packages/acp/tests/acceptance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ describe("Tier WAP — same-named sibling Sessions", () => {
return {
sessionKey: `placed:${context.sessionIdentity ?? "none"}`,
cwd: "/placed",
state: "pending",
};
},
},
Expand Down Expand Up @@ -221,6 +222,7 @@ describe("Tier WAP — same-named sibling Sessions", () => {
return {
sessionKey: `placed:${context.sessionIdentity ?? "none"}`,
cwd: "/placed",
state: "pending",
};
},
},
Expand Down
Loading
Loading