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
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,38 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

## [Unreleased]

## [0.0.6] - 2026-07-29

### Added

- Add durable Session attachments with guarded upload, model projection, and
composer support.
- Stream live Bash output into the workbench while preserving bounded,
redacted tool results.
- Add execution navigation and sortable Project Todo workflows for moving
between parallel work and entering multiple Sessions.
- Add safe Session and Automation deletion with active-run shutdown,
Automation reference protection, and cross-tab cleanup.

### Changed

- Unify suspend, resume, steer, approval, and terminal handling under one
logical Execution lifecycle.
- Preserve persisted message phases when projecting model workstreams so
commentary, reasoning, tools, and final output retain their real order.
- Refine the Session composer, navigation, dashboard, and work activity
presentation for denser engineering workflows.

### Fixed

- Restore live Execution state from authoritative Session snapshots after
reconnecting or refreshing.
- Stabilize streaming work presentation and temporal text updates during
long-running executions.
- Require the one-time setup token before exposing the first-run setup form.
- Preserve structured tool payloads while keeping secret redaction at the
finalized output boundary.

## [0.0.5] - 2026-07-27

### Changed
Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@archcode/server",
"version": "0.0.5",
"version": "0.0.6",
"private": true,
"type": "module",
"main": "./src/main.ts",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@archcode/web",
"version": "0.0.5",
"version": "0.0.6",
"private": true,
"type": "module",
"scripts": {
Expand Down
10 changes: 5 additions & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "archcode",
"version": "0.0.5",
"version": "0.0.6",
"private": true,
"description": "ArchCode — Not just a coding agent. An always-on workbench for AI engineering.",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@archcode/agent-core",
"version": "0.0.5",
"version": "0.0.6",
"private": true,
"type": "module",
"main": "./src/index.ts",
Expand Down
78 changes: 64 additions & 14 deletions packages/agent-core/src/execution/session-execution-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2421,13 +2421,41 @@ describe("SessionExecutionManager", () => {
} as unknown as MockAgent;
parentStore.getState().append(testExecutionStart(parentExecutionId));
const parentStartedAt = parentStore.getState().executions[0]!.startedAt;
const parentStepId = `step-${parentExecutionId}`;
const parentRequest = delegationRequest({
objective: `recover ${childState}`,
background: false,
});
parentStore.getState().append({
type: "step-start",
stepId: parentStepId,
step: 0,
});
parentStore.getState().append({
type: "tool-call",
toolCallId,
toolName: "delegate",
input: parentRequest,
});
parentStore.getState().append({
type: "step-end",
stepId: parentStepId,
step: 0,
finishReason: "tool-calls",
});
const parentAssistantMessage = parentStore.getState().messages.find(
(message) => message.role === "assistant" && message.stepId === parentStepId,
);
if (parentAssistantMessage === undefined) {
throw new Error(`Expected Assistant message for ${parentStepId}`);
}
const parentBatch: SessionToolBatch = {
batchId,
executionId: parentExecutionId,
runOrdinal: 0,
step: 0,
stepId: `step-${parentExecutionId}`,
assistantMessageId: `assistant-${parentExecutionId}`,
stepId: parentStepId,
assistantMessageId: parentAssistantMessage.id,
agentName: "lead",
allowedTools: ["delegate"],
agentSkills: [],
Expand All @@ -2437,7 +2465,7 @@ describe("SessionExecutionManager", () => {
partitionIndex: 0,
toolCallId,
toolName: "delegate",
input: delegationRequest({ objective: `recover ${childState}`, background: false }),
input: parentRequest,
traits: { readOnly: false, destructive: false, concurrencySafe: false },
state: "child_launch",
attempt: 1,
Expand All @@ -2455,7 +2483,7 @@ describe("SessionExecutionManager", () => {
createdAt: new Date(parentStartedAt).toISOString(),
updatedAt: new Date(parentStartedAt).toISOString(),
};
parentStore.setState({ toolBatches: [parentBatch] });
await storeManager.updateToolBatches(parentId, workspaceRoot, () => [parentBatch]);
parentStore.getState().append(testExecutionSuspended(
parentExecutionId,
{
Expand Down Expand Up @@ -2509,17 +2537,26 @@ describe("SessionExecutionManager", () => {
}

const childRun = deferred<MockAgentResult>();
const applyOutcomeSettled = deferred<void>();
let applyOutcomeFailure: { error: unknown } | undefined;
const applyOutcome = mock(async (outcome: Parameters<NonNullable<FakeManagerOptions["applyChildDependencyOutcome"]>>[0]) => {
await applySessionToolBatchChildOutcome({
storeManager,
sessionId: outcome.parentSessionId,
workspaceRoot: outcome.workspaceRoot,
batchId: outcome.parentToolBatchId,
toolCallId: outcome.parentToolCallId,
childSessionId: outcome.childSessionId,
childExecutionId: outcome.childExecutionId,
outcome: outcome.outcome,
});
try {
await applySessionToolBatchChildOutcome({
storeManager,
sessionId: outcome.parentSessionId,
workspaceRoot: outcome.workspaceRoot,
batchId: outcome.parentToolBatchId,
toolCallId: outcome.parentToolCallId,
childSessionId: outcome.childSessionId,
childExecutionId: outcome.childExecutionId,
outcome: outcome.outcome,
});
} catch (error) {
applyOutcomeFailure = { error };
throw error;
} finally {
applyOutcomeSettled.resolve(undefined);
}
});
const { manager } = createManager({ [parentId]: parentAgent }, {
factory: makeFactory(),
Expand Down Expand Up @@ -2551,6 +2588,19 @@ describe("SessionExecutionManager", () => {
.toBe("waiting_for_human");
childRun.resolve({ text: "child recovered", steps: 1 });
await manager.getExecution(workspaceRoot, childId)!.promise;
await applyOutcomeSettled.promise;
if (applyOutcomeFailure !== undefined) throw applyOutcomeFailure.error;
expect(applyOutcome, childState).toHaveBeenCalledTimes(1);
expect(
parentStore.getState().toolBatches[0]!.calls[0]!.childDependency,
childState,
).toMatchObject({
kind: "child_dependency",
outcome: {
executionStatus: "completed",
output: "child recovered",
},
});
} else if (childState === "suspended") {
expect(canonicalChild!.getState().executions[0]!.status).toBe("suspended");
expect(parentStore.getState().executions[0]!.status).toBe("suspended");
Expand Down
9 changes: 6 additions & 3 deletions packages/agent-core/src/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1687,19 +1687,22 @@ describe("createRuntime", () => {
});
expect((await (await runtime2.contextResolver.resolve(workspaceRoot)).hitl.list()).find((record) => record.hitlId === first.hitlId)?.status).toBe("resolved");

const secondCallCompleted = nextSessionEvent(
const executionCompleted = nextSessionEvent(
runtime2,
project.slug,
session.sessionId,
(event) => event.payload.type === "tool-result" && event.payload.toolCallId === "question-2",
(event) => (
event.payload.type === "execution-end"
&& event.payload.executionId === batch.executionId
),
);
await runtime2.respondToHitl({
slug: project.slug,
workspaceRoot,
hitlId: second.hitlId,
response: { type: "question_answer", answers: ["Yes"] },
});
await secondCallCompleted;
await executionCompleted;

const recovered = await runtime2.getSessionFile(workspaceRoot, session.sessionId);
expect(recovered.executions).toHaveLength(1);
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@archcode/protocol",
"version": "0.0.5",
"version": "0.0.6",
"private": true,
"type": "module",
"main": "./src/index.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/utils/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@archcode/utils",
"version": "0.0.5",
"version": "0.0.6",
"private": true,
"type": "module",
"main": "./src/index.ts",
Expand Down
8 changes: 4 additions & 4 deletions scripts/release.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ describe("release metadata", () => {
test("compares complete release asset directories by content", async () => {
const expectedDir = await mkdtemp(join(tmpdir(), "archcode-release-expected-"));
const actualDir = await mkdtemp(join(tmpdir(), "archcode-release-actual-"));
const releaseAssetNames = releaseAssetNamesForVersion("0.0.5");
const releaseAssetNames = releaseAssetNamesForVersion("0.0.6");
try {
for (const name of releaseAssetNames) {
await Promise.all([
Expand All @@ -258,14 +258,14 @@ describe("release metadata", () => {
try {
for (const target of releaseTargets) {
await writeTestArchive(
join(assetDir, releaseArchiveAssetName(target, "0.0.5")),
"0.0.5",
join(assetDir, releaseArchiveAssetName(target, "0.0.6")),
"0.0.6",
);
}
const template = await Bun.file(join(import.meta.dir, "install.sh")).text();
await Bun.write(
join(assetDir, "install.sh"),
renderReleaseInstaller(template, "0.0.5"),
renderReleaseInstaller(template, "0.0.6"),
);

await writeBundleMetadata(assetDir);
Expand Down