chore: merge develop into hare/develop

This commit is contained in:
2026-10-02 01:02:37 +09:00
75 changed files with 13392 additions and 369 deletions
@@ -1,6 +1,6 @@
// @generated by yoi-companion-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:ae559492220ec118f04c2c0c9593e8b3cb44c3cb857be3ae85715ace26da8043)
// Input contract: 0.1.0 (sha256:a0956070b29f49aafac3b19a0fa54626f285bdf01337f26afc4389ff6d23495a)
// Options: companion-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/companion-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_companion_api_types -- --check
File diff suppressed because one or more lines are too long
+18 -5
View File
@@ -40,7 +40,7 @@ export type TurnResult = "finished" | "paused";
export type InvokeKind = "user_send" | "notify" | "worker_event" | "system_reminder" | "wakeup";
export type RunResult = "finished" | "paused" | "limit_reached" | "rolled_back";
export type RunResult = "finished" | "paused" | "limit_reached" | "cancelled" | "rolled_back";
export type ToolResultDisposition = "success" | "error" | "interrupted" | "cancelled" | "outcome_unknown";
@@ -136,7 +136,13 @@ export type SessionToolAttachment = { media_type: string,
*/
data_base64: string, };
export type SessionSnapshotEntryData = { "kind": "user_input", segments: Array<Segment>, } | { "kind": "message", role: SessionMessageRole, content: Array<SessionContentPart>, } | { "kind": "tool_call", call_id: string, name: string, arguments: string, } | { "kind": "tool_result", call_id: string, summary: string, content?: string | null, is_error: boolean, attachments?: Array<SessionToolAttachment>, } | { "kind": "system_item", item_kind: string, content: string, data?: unknown, } | { "kind": "run_error", message: string, };
export type RunYieldReason = "compaction";
export type RunResumeSource = "compaction" | "pause";
export type RunFailureKind = "engine" | "compaction";
export type SessionSnapshotEntryData = { "kind": "user_input", segments: Array<Segment>, } | { "kind": "message", role: SessionMessageRole, content: Array<SessionContentPart>, } | { "kind": "tool_call", call_id: string, name: string, arguments: string, } | { "kind": "tool_result", call_id: string, summary: string, content?: string | null, is_error: boolean, attachments?: Array<SessionToolAttachment>, } | { "kind": "system_item", item_kind: string, content: string, data?: unknown, } | { "kind": "run_yielded", reason: RunYieldReason, active_run_turn_count: number, } | { "kind": "run_resumed", source: RunResumeSource, active_run_turn_count: number, } | { "kind": "run_cancelled" } | { "kind": "run_error", message: string, failure?: RunFailureKind | null, };
export type SessionSnapshotEntry = {
/**
@@ -147,7 +153,7 @@ entry_id: string,
/**
* Timestamp copied from the durable log record that commits this entry.
*/
timestamp: number, provenance: SessionEntryProvenance, derived_from?: Array<string>, } & ({ "kind": "user_input", segments: Array<Segment>, } | { "kind": "message", role: SessionMessageRole, content: Array<SessionContentPart>, } | { "kind": "tool_call", call_id: string, name: string, arguments: string, } | { "kind": "tool_result", call_id: string, summary: string, content?: string | null, is_error: boolean, attachments?: Array<SessionToolAttachment>, } | { "kind": "system_item", item_kind: string, content: string, data?: unknown, } | { "kind": "run_error", message: string, });
timestamp: number, provenance: SessionEntryProvenance, derived_from?: Array<string>, } & ({ "kind": "user_input", segments: Array<Segment>, } | { "kind": "message", role: SessionMessageRole, content: Array<SessionContentPart>, } | { "kind": "tool_call", call_id: string, name: string, arguments: string, } | { "kind": "tool_result", call_id: string, summary: string, content?: string | null, is_error: boolean, attachments?: Array<SessionToolAttachment>, } | { "kind": "system_item", item_kind: string, content: string, data?: unknown, } | { "kind": "run_yielded", reason: RunYieldReason, active_run_turn_count: number, } | { "kind": "run_resumed", source: RunResumeSource, active_run_turn_count: number, } | { "kind": "run_cancelled" } | { "kind": "run_error", message: string, failure?: RunFailureKind | null, });
export type SessionConversationTurn = { turn_id: string, entries: Array<SessionSnapshotEntry>, };
@@ -279,6 +285,8 @@ export type SubscriptionWorkerWorkdirAttachment = {
*/
alias: string, repository_key?: string | null, working_directory_id: SubscriptionWorkdirId, };
export type SubscriptionWorkerJob = { job_id: string, attempt_id: string, purpose: string, };
export type SubscriptionWorker = { worker_id: SubscriptionWorkerId,
/**
* Set by the Workspace Server when projecting a Runtime-owned Worker to clients.
@@ -307,7 +315,12 @@ worker_state?: WorkerStateSnapshot | null,
/**
* Runtime catalog lifecycle compatibility projection; not foreground-state authority.
*/
state: SubscriptionWorkerState, has_running_internal_workers: boolean, workspace_id?: string | null, display_name?: string | null, profile?: string | null, workdir_attachments?: Array<SubscriptionWorkerWorkdirAttachment>, };
state: SubscriptionWorkerState, has_running_internal_workers: boolean, workspace_id?: string | null, display_name?: string | null, profile?: string | null,
/**
* Backend-owned Job binding. Runtime producers leave this unset; the
* Workspace projection enriches both snapshots and live updates.
*/
job?: SubscriptionWorkerJob | null, workdir_attachments?: Array<SubscriptionWorkerWorkdirAttachment>, };
export type WorkspaceSubscriptionWorkdir = { working_directory_id: SubscriptionWorkdirId, repository_key: string, state: string, };
@@ -333,7 +346,7 @@ export type SubscriptionFrame = { protocol_version: number, } & ({ "frame": "req
export type Method = { "method": "submit", "params": { submission_request_id: string, input: Array<Segment>, } } | { "method": "notify", "params": { notification_request_id: string, message: string, } } | { "method": "worker_event", "params": WorkerEvent } | { "method": "list_pending_submissions" } | { "method": "cancel_pending_submission", "params": { submission_id: string, expected_revision: number, } } | { "method": "clear_pending_submissions", "params": { expected_revision: number, } } | { "method": "continue_pending", "params": { expected_revision: number, expected_head_id: string, } } | { "method": "resume", "params": { command: WorkerCommandEnvelope, } } | { "method": "cancel", "params": { command: WorkerCommandEnvelope, } } | { "method": "pause", "params": { command: WorkerCommandEnvelope, } } | { "method": "compact", "params": { command: WorkerCommandEnvelope, } } | { "method": "list_rewind_targets" } | { "method": "rewind_to", "params": { target: RewindTargetId, expected_head_entries: number, } } | { "method": "shutdown", "params": { command: WorkerCommandEnvelope, } } | { "method": "list_completions", "params": { kind: CompletionKind, prefix: string, } } | { "method": "list_workers" } | { "method": "restore_worker", "params": { name: string, } } | { "method": "register_peer", "params": { name: string, } };
export type Event = { "event": "submission_accepted", "data": { submission_request_id: string, submission_id: string, disposition: SubmissionDisposition, } } | { "event": "submission_rejected", "data": { submission_request_id: string, message: string, } } | { "event": "pending_submissions_changed", "data": { pending: PendingSubmissionsSnapshot, } } | { "event": "user_message", "data": {
export type Event = { "event": "submission_accepted", "data": { submission_request_id: string, submission_id: string, disposition: SubmissionDisposition, } } | { "event": "submission_rejected", "data": { submission_request_id: string, message: string, } } | { "event": "notification_accepted", "data": { notification_request_id: string, } } | { "event": "notification_rejected", "data": { notification_request_id: string, message: string, } } | { "event": "pending_submissions_changed", "data": { pending: PendingSubmissionsSnapshot, } } | { "event": "user_message", "data": {
/**
* Stable durable history identity. Older mixed-version producers omit it.
*/
@@ -1,6 +1,6 @@
// @generated by yoi-repository-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:ae559492220ec118f04c2c0c9593e8b3cb44c3cb857be3ae85715ace26da8043)
// Input contract: 0.1.0 (sha256:a0956070b29f49aafac3b19a0fa54626f285bdf01337f26afc4389ff6d23495a)
// Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output
// Output: web/workspace/src/lib/generated/repository-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-runtime-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:ae559492220ec118f04c2c0c9593e8b3cb44c3cb857be3ae85715ace26da8043)
// Input contract: 0.1.0 (sha256:a0956070b29f49aafac3b19a0fa54626f285bdf01337f26afc4389ff6d23495a)
// Options: runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/runtime-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check
@@ -115,7 +115,7 @@ export type RuntimeWorkerSpawnAcceptanceRequirement = { kind: "socket_ready"; }
export type RuntimeWorkerSpawnIntent = { kind: "workspace_companion"; } | { kind: "workspace_orchestrator"; } | { kind: "workspace_coding"; } | { kind: "ticket_role"; role: RuntimeTicketWorkerRole; ticket_id: string; };
export type RuntimeWorkerSpawnRequest = { acceptance: RuntimeWorkerSpawnAcceptanceRequirement; initial_submit?: Array<unknown>; intent: RuntimeWorkerSpawnIntent; profile: RuntimeProfileSelector; requested_worker_name?: string | null; ticket_assignment?: RuntimeWorkerTicketAssignmentRequest | null; workdir_attachment_requests?: Array<RuntimeWorkerSpawnWorkingDirectoryAttachmentRequest>; };
export type RuntimeWorkerSpawnRequest = { acceptance: RuntimeWorkerSpawnAcceptanceRequirement; initial_submit?: Array<unknown>; intent: RuntimeWorkerSpawnIntent; profile: RuntimeProfileSelector; requested_worker_name?: string | null; singleton_key?: string | null; ticket_assignment?: RuntimeWorkerTicketAssignmentRequest | null; workdir_attachment_requests?: Array<RuntimeWorkerSpawnWorkingDirectoryAttachmentRequest>; };
export type RuntimeWorkerSpawnResponse = { acceptance_evidence?: Array<RuntimeWorkerSpawnAcceptanceEvidence>; diagnostics?: Array<Diagnostic>; state: WorkerOperationState; worker?: RuntimeWorkerSummary | null; };
@@ -1,6 +1,6 @@
// @generated by yoi-ticket-browser-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:ae559492220ec118f04c2c0c9593e8b3cb44c3cb857be3ae85715ace26da8043)
// Input contract: 0.1.0 (sha256:a0956070b29f49aafac3b19a0fa54626f285bdf01337f26afc4389ff6d23495a)
// Options: ticket-objective-merge-request-browser-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/ticket-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_ticket_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-workdir-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:7b06d005d3436758c96fc4cfd462df76460f89620fd1b58fdf05009d96bb9052)
// Input contract: 0.1.0 (sha256:a0956070b29f49aafac3b19a0fa54626f285bdf01337f26afc4389ff6d23495a)
// Options: workdir-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/workdir-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_workdir_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-worker-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:ae559492220ec118f04c2c0c9593e8b3cb44c3cb857be3ae85715ace26da8043)
// Input contract: 0.1.0 (sha256:a0956070b29f49aafac3b19a0fa54626f285bdf01337f26afc4389ff6d23495a)
// Options: worker-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/worker-launch-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_worker_launch_api_types -- --check
@@ -11,7 +11,7 @@ export type BrowserWorkerWorkingDirectorySelection = { alias: string; relative_c
export type BrowserWorkspaceOrchestratorResponse = { diagnostics: Array<Diagnostic>; disposition: string; online: boolean; worker?: WorkerLaunchWorkerSummary | null; workspace_id: string; };
export type CreateWorkspaceWorkerRequest = { control_operation_id?: string | null; display_name: string; initial_submit?: Array<Segment>; profile?: string | null; runtime_id: string; ticket_assignment?: CreateWorkspaceWorkerTicketAssignmentRequest | null; workdir_attachments?: Array<BrowserWorkerWorkingDirectorySelection>; };
export type CreateWorkspaceWorkerRequest = { control_operation_id?: string | null; display_name: string; initial_submit?: Array<Segment>; profile?: string | null; runtime_id: string; singleton_key?: string | null; ticket_assignment?: CreateWorkspaceWorkerTicketAssignmentRequest | null; workdir_attachments?: Array<BrowserWorkerWorkingDirectorySelection>; };
export type CreateWorkspaceWorkerTicketAssignmentRequest = { operation_id: string; ticket_id: string; };
@@ -132,12 +132,22 @@ function canonicalSession(logEntries: unknown[]): Event extends {
});
break;
}
case "run_yielded":
case "run_resumed":
case "run_cancelled":
entries.push({
entry_id: `legacy-test-${sequence++}`,
provenance: "legacy_unknown",
...entry,
});
break;
case "run_errored":
entries.push({
entry_id: `legacy-test-${sequence++}`,
provenance: "legacy_unknown",
kind: "run_error",
message: entry["message"] ?? "Worker run failed.",
failure: entry["failure"],
});
break;
}
@@ -291,6 +301,138 @@ Deno.test("Worker state events and acknowledgements replace the full state", ()
);
});
Deno.test("durable compaction failure reconciles live alert and error in both views", () => {
const message = "mid-run compaction failed: summary unavailable";
const transition = (
entryId: string,
data: Record<string, unknown>,
): Event => ({
event: "session_entry_committed",
data: {
entry: {
entry_id: entryId,
timestamp: 10,
provenance: "legacy_unknown",
...data,
},
},
} as Event);
const projection = projectConsole([
{
eventId: "yielded",
event: transition("yielded-entry", {
kind: "run_yielded",
reason: "compaction",
active_run_turn_count: 2,
}),
},
{
eventId: "alert",
event: {
event: "alert",
data: {
level: "error",
source: "compactor",
message,
timestamp_ms: 10,
},
},
},
{
eventId: "failed",
event: transition("failed-entry", {
kind: "run_error",
message,
failure: "compaction",
}),
},
{
eventId: "controller-error",
event: {
event: "error",
data: { code: "internal", message: "summary unavailable" },
},
},
]);
const errors = projection.lines.filter((line) => line.kind === "error");
assertEquals(errors.length, 1);
assertEquals(errors[0].title, "Compaction failed");
assertEquals(errors[0].body, message);
assertEquals(errors[0].entryId, "failed-entry");
assertEquals(projectConsoleLines(projection.lines, "normal"), errors);
assertEquals(projectConsoleLines(projection.lines, "overview"), errors);
});
Deno.test("snapshot restores compaction terminal without reviving transition progress", () => {
const projection = projectConsole([{
eventId: "snapshot",
event: snapshotEvent("/repo", [
{
kind: "run_yielded",
timestamp: 1,
reason: "compaction",
active_run_turn_count: 2,
},
{
kind: "run_resumed",
timestamp: 2,
source: "compaction",
active_run_turn_count: 2,
},
{
kind: "run_errored",
timestamp: 3,
interrupted: false,
message: "mid-run compaction failed: summary unavailable",
failure: "compaction",
},
]),
}]);
assertEquals(projection.compaction, null);
assertEquals(projection.lines.length, 1);
assertEquals(projection.lines[0].title, "Compaction failed");
assertEquals(projection.lines[0].kind, "error");
});
Deno.test("intentional cancellation stays non-error in live and snapshot projections", () => {
const live = projectConsole([
{
eventId: "cancelled-terminal",
event: {
event: "session_entry_committed",
data: {
entry: {
entry_id: "cancelled-entry",
timestamp: 10,
provenance: "legacy_unknown",
kind: "run_cancelled",
},
},
} as Event,
},
{
eventId: "cancelled-run-end",
event: {
event: "run_end",
data: { result: "cancelled" },
} as Event,
},
]);
assertEquals(live.lines.some((line) => line.kind === "error"), false);
const restored = projectConsole([{
eventId: "snapshot",
event: snapshotEvent("/repo", [{
kind: "run_cancelled",
timestamp: 10,
}]),
}]);
assertEquals(restored.lines.some((line) => line.kind === "error"), false);
assertEquals(restored.compaction, null);
});
Deno.test("snapshot replaces a live error with one durable run_errored row", () => {
const projector = createConsoleProjector();
let projection = projector.append([
@@ -1002,6 +1002,7 @@ export function applyProtocolEvent(
next.usage = usageText(event.data);
break;
case "error":
if (hasDurableRunFailure(next, event.data.message)) break;
next.lines.push(
line(
envelope.eventId,
@@ -1264,12 +1265,55 @@ function upsertStatusLine(
}
}
function runFailureMessagesMatch(left: string, right: string): boolean {
return left.length > 0 && right.length > 0 &&
(left === right || left.endsWith(right) || right.endsWith(left));
}
function isDurableRunFailure(line: ConsoleLine): boolean {
return line.kind === "error" &&
(line.title === "Run error" || line.title === "Compaction failed");
}
function hasDurableRunFailure(
projection: ConsoleProjection,
message: string,
): boolean {
return projection.lines.some((line) =>
isDurableRunFailure(line) && runFailureMessagesMatch(line.body, message)
);
}
function appendDurableRunFailure(
projection: ConsoleProjection,
eventId: string,
message: string,
failure: string | undefined,
): void {
projection.lines = projection.lines.filter((line) => {
if (isDurableRunFailure(line)) return true;
return !(line.kind === "error" && runFailureMessagesMatch(line.body, message));
});
projection.lines.push(
line(
eventId,
"error",
failure === "compaction" ? "Compaction failed" : "Run error",
message,
undefined,
false,
true,
),
);
}
function appendAlertLine(
projection: ConsoleProjection,
eventId: string,
alert: Alert,
): void {
const isError = alert.level === "error";
if (isError && hasDurableRunFailure(projection, alert.message)) return;
projection.lines.push(
line(
eventId,
@@ -2144,17 +2188,18 @@ function applySessionEntry(
applyTaskSystemItem(projection, item);
break;
}
case "run_yielded":
case "run_resumed":
case "run_cancelled":
// Durable logical-Run transitions and intentional cancellation are
// replayable lifecycle state, not errors or Console progress rows.
break;
case "run_error":
projection.lines.push(
line(
eventId,
"error",
"Run error",
stringField(value, "message") ?? "Worker run failed.",
undefined,
false,
true,
),
appendDurableRunFailure(
projection,
eventId,
stringField(value, "message") ?? "Worker run failed.",
stringField(value, "failure"),
);
break;
default:
@@ -30,6 +30,7 @@ import type {
Event as PodProtocolEvent,
Method as PodProtocolMethod,
Segment as PodProtocolSegment,
SubscriptionWorkerJob,
WorkerStateSnapshot,
} from "$lib/generated/protocol";
import type {
@@ -88,6 +89,7 @@ export type Worker =
display_name: string;
tags: string[];
worker_state?: WorkerStateSnapshot | null;
job?: SubscriptionWorkerJob | null;
diagnostics: Diagnostic[];
};
@@ -88,6 +88,47 @@ Deno.test('workspace Worker snapshot keeps equal local ids from different Runtim
assertEquals([...projection.workers.keys()].sort(), ['runtime-a:1', 'runtime-b:1']);
});
Deno.test('workspace Worker snapshot and live update preserve canonical Job metadata', () => {
const projection = createWorkspaceWorkersProjection();
const snapshotWorker = worker('runtime-a', 'job-worker', 1);
snapshotWorker.job = {
job_id: 'check:T-1:r1',
attempt_id: 'check:T-1:r1:attempt:1',
purpose: 'ticket_item_check',
};
applyWorkspaceWorkersFrame(projection, {
protocol_version: 1,
frame: 'response',
message: {
result: 'subscribed',
payload: {
request_id: 'request-1',
subscription_id: 'subscription-1',
selector: { topic: 'workspace_workers' },
snapshot_revision: 1,
snapshot: { topic: 'workers', data: { workers: [snapshotWorker] } },
},
},
});
assertEquals(projection.workers.get('runtime-a:job-worker')?.job, snapshotWorker.job);
const updated = { ...snapshotWorker, subject_revision: 2 };
updated.job = { ...snapshotWorker.job, attempt_id: 'check:T-1:r1:attempt:2' };
applyWorkspaceWorkersFrame(projection, {
protocol_version: 1,
frame: 'event',
message: {
event: 'event',
data: {
subscription_id: 'subscription-1',
subject_revision: 2,
payload: { event: 'worker_upserted', data: { worker: updated } },
},
},
});
assertEquals(projection.workers.get('runtime-a:job-worker')?.job, updated.job);
});
Deno.test('workspace Worker reducer ignores stale events and removes composite subject', () => {
const projection = createWorkspaceWorkersProjection();
projection.workers.set('runtime-a:1', worker('runtime-a', '1', 3));
@@ -143,6 +143,7 @@ function projectWorker(
display_name: displayName,
label: displayName,
profile: worker.profile ?? null,
job: worker.job ?? null,
tags: [],
workspace: {
visibility: "workspace",
@@ -63,6 +63,19 @@ Deno.test("live runtime workers are sidebar targets and console targets", () =>
assertEquals(canOpenWorkerConsole(liveWorker), true);
});
Deno.test("Backend Job Workers are hidden only from the sidebar View", () => {
const jobWorker = worker({
state: "running",
job: {
job_id: "check:T-1:r1",
attempt_id: "check:T-1:r1:attempt:1",
purpose: "ticket_item_check",
},
});
assertEquals(canShowWorkerInSidebar(jobWorker), false);
assertEquals(canOpenWorkerConsole(jobWorker), true);
});
Deno.test("sidebar workers sort running then idle then stopped", () => {
const workers = [
worker({ worker_id: "3", display_name: "Stopped", state: "stopped" }),
@@ -22,15 +22,15 @@ export function sidebarWorkerActivity(
}
export function canShowWorkerInSidebar(
worker: Pick<Worker, 'implementation'>,
worker: Pick<Worker, 'implementation' | 'job'>,
): boolean {
return worker.implementation.kind !== 'backend_worker_registry';
return !worker.job && worker.implementation.kind !== 'backend_worker_registry';
}
export function canOpenWorkerConsole(
worker: Pick<Worker, 'implementation'>,
): boolean {
return canShowWorkerInSidebar(worker);
return worker.implementation.kind !== 'backend_worker_registry';
}
type SidebarWorkerLink = Pick<Worker, 'resource_key' | 'display_name'>;