fix: enable running composer delivery
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
"dev": "deno run -A npm:vite@7.2.7 dev",
|
"dev": "deno run -A npm:vite@7.2.7 dev",
|
||||||
"dev:backend": "cd ../.. && cargo run -p yoi-workspace-server --bin yoi-server -- serve --listen 127.0.0.1:8787",
|
"dev:backend": "cd ../.. && cargo run -p yoi-workspace-server --bin yoi-server -- serve --listen 127.0.0.1:8787",
|
||||||
"check": "deno run -A npm:@sveltejs/kit@2.49.4 sync && deno run -A npm:svelte-check@4.3.4 --tsconfig ./tsconfig.json",
|
"check": "deno run -A npm:@sveltejs/kit@2.49.4 sync && deno run -A npm:svelte-check@4.3.4 --tsconfig ./tsconfig.json",
|
||||||
"test": "deno test --allow-read=src,test,tests --allow-env=LOG,VSCODE_TEXTMATE_DEBUG,NODE_ENV tests/workspace-model.test.ts tests/workspace-catalog.test.ts tests/profile-api.test.ts tests/skill-api.test.ts src/lib/workspace/auth/model.test.ts tests/auth-api.test.ts src/lib/workspace/api/http.test.ts src/lib/workspace/api/workers.test.ts src/lib/workspace/header/breadcrumb-model.test.ts src/lib/workspace/console/chat-submit.test.ts test/composer-history.test.ts tests/composer-paste.test.ts src/lib/workspace/console/composer-command.test.ts src/lib/workspace/console/composer-draft.test.ts src/lib/workspace/console/composer-completion.test.ts src/lib/workspace/console/markdown.test.ts test/console/ansi.test.ts src/lib/workspace/console/model.test.ts src/lib/workspace/companion/api.test.ts tests/workdir-api.test.ts src/lib/workspace/console/tasks.test.ts test/ticket-detail-route-reuse.test.ts test/repositories/ui.test.ts src/lib/workspace/console/worker-console.ui.test.ts src/lib/workspace/settings/model.test.ts src/lib/workspace/sidebar/override-stack.test.ts src/lib/workspace/sidebar/workers.test.ts src/lib/workspace/sidebar/workspace-switcher.test.ts src/lib/workspace/sidebar/worker-subscription.test.ts src/lib/workspace/sidebar/worker-launch.test.ts test/sidebar/worker-actions.test.ts src/lib/workspace/tickets/merge-request-resources.test.ts src/lib/workspace/tickets/ticket-panel.test.ts test/merge-request-status.test.ts test/config-source/decodal-grammar.test.ts test/config-source/editor-state.test.ts test/config-source/fixed-schema-wrapper.test.ts test/config-source/toolchain.test.ts test/config-source/wasm-parity.test.ts test/repository-access/api.test.ts test/repository-access/loader.test.ts test/repository-access/ui.test.ts",
|
"test": "deno test --allow-read=src,test,tests --allow-env=LOG,VSCODE_TEXTMATE_DEBUG,NODE_ENV tests/workspace-model.test.ts tests/workspace-catalog.test.ts tests/profile-api.test.ts tests/skill-api.test.ts src/lib/workspace/auth/model.test.ts tests/auth-api.test.ts src/lib/workspace/api/http.test.ts src/lib/workspace/api/workers.test.ts src/lib/workspace/header/breadcrumb-model.test.ts src/lib/workspace/console/chat-submit.test.ts src/lib/workspace/console/composer-delivery.test.ts test/composer-history.test.ts tests/composer-paste.test.ts src/lib/workspace/console/composer-command.test.ts src/lib/workspace/console/composer-draft.test.ts src/lib/workspace/console/composer-completion.test.ts src/lib/workspace/console/markdown.test.ts test/console/ansi.test.ts src/lib/workspace/console/model.test.ts src/lib/workspace/companion/api.test.ts tests/workdir-api.test.ts src/lib/workspace/console/tasks.test.ts test/ticket-detail-route-reuse.test.ts test/repositories/ui.test.ts src/lib/workspace/console/worker-console.ui.test.ts src/lib/workspace/settings/model.test.ts src/lib/workspace/sidebar/override-stack.test.ts src/lib/workspace/sidebar/workers.test.ts src/lib/workspace/sidebar/workspace-switcher.test.ts src/lib/workspace/sidebar/worker-subscription.test.ts src/lib/workspace/sidebar/worker-launch.test.ts test/sidebar/worker-actions.test.ts src/lib/workspace/tickets/merge-request-resources.test.ts src/lib/workspace/tickets/ticket-panel.test.ts test/merge-request-status.test.ts test/config-source/decodal-grammar.test.ts test/config-source/editor-state.test.ts test/config-source/fixed-schema-wrapper.test.ts test/config-source/toolchain.test.ts test/config-source/wasm-parity.test.ts test/repository-access/api.test.ts test/repository-access/loader.test.ts test/repository-access/ui.test.ts",
|
||||||
"build": "deno run -A npm:vite@7.2.7 build",
|
"build": "deno run -A npm:vite@7.2.7 build",
|
||||||
"preview": "deno run -A npm:vite@7.2.7 preview"
|
"preview": "deno run -A npm:vite@7.2.7 preview"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
declare const Deno: {
|
||||||
|
test(name: string, fn: () => void): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
import {
|
||||||
|
canDeliverComposerDraft,
|
||||||
|
sendComposerDelivery,
|
||||||
|
} from "./composer-delivery.ts";
|
||||||
|
|
||||||
|
function assertEquals(actual: unknown, expected: unknown): void {
|
||||||
|
if (actual !== expected) {
|
||||||
|
throw new Error(`Expected ${String(expected)}, got ${String(actual)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = {
|
||||||
|
protocolOpen: true,
|
||||||
|
sending: false,
|
||||||
|
hasText: true,
|
||||||
|
hasAttachments: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
Deno.test("running Composer enables Queue Submit and Notify but not immediate Submit", () => {
|
||||||
|
assertEquals(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
...base,
|
||||||
|
delivery: "queue",
|
||||||
|
workerState: "running",
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
...base,
|
||||||
|
delivery: "notify",
|
||||||
|
workerState: "running",
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
...base,
|
||||||
|
delivery: "submit",
|
||||||
|
workerState: "running",
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("running Queue Submit and Notify dispatch their protocol methods", () => {
|
||||||
|
const sent: string[] = [];
|
||||||
|
assertEquals(
|
||||||
|
sendComposerDelivery(
|
||||||
|
{ ...base, delivery: "queue", workerState: "running" },
|
||||||
|
"submit",
|
||||||
|
(method) => sent.push(method),
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
sendComposerDelivery(
|
||||||
|
{ ...base, delivery: "notify", workerState: "running" },
|
||||||
|
"notify",
|
||||||
|
(method) => sent.push(method),
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assertEquals(sent.join(","), "submit,notify");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("idle Composer enables only immediate Submit", () => {
|
||||||
|
assertEquals(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
...base,
|
||||||
|
delivery: "submit",
|
||||||
|
workerState: "idle",
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
...base,
|
||||||
|
delivery: "queue",
|
||||||
|
workerState: "idle",
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
...base,
|
||||||
|
delivery: "notify",
|
||||||
|
workerState: "idle",
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("running delivery remains fenced by protocol, send state, and payload kind", () => {
|
||||||
|
assertEquals(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
...base,
|
||||||
|
delivery: "queue",
|
||||||
|
workerState: "running",
|
||||||
|
protocolOpen: false,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
...base,
|
||||||
|
delivery: "notify",
|
||||||
|
workerState: "running",
|
||||||
|
sending: true,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
...base,
|
||||||
|
delivery: "notify",
|
||||||
|
workerState: "running",
|
||||||
|
hasAttachments: true,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
...base,
|
||||||
|
delivery: "queue",
|
||||||
|
workerState: "running",
|
||||||
|
hasText: false,
|
||||||
|
hasAttachments: true,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
export type ComposerDelivery = "submit" | "queue" | "notify";
|
||||||
|
|
||||||
|
export type ComposerDeliveryState = {
|
||||||
|
delivery: ComposerDelivery;
|
||||||
|
workerState: string;
|
||||||
|
protocolOpen: boolean;
|
||||||
|
sending: boolean;
|
||||||
|
hasText: boolean;
|
||||||
|
hasAttachments: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve whether the current Composer draft can use one delivery action.
|
||||||
|
* Immediate Submit is idle-only; Queue and Notify are running-only.
|
||||||
|
*/
|
||||||
|
export function canDeliverComposerDraft(state: ComposerDeliveryState): boolean {
|
||||||
|
if (!state.protocolOpen || state.sending) return false;
|
||||||
|
|
||||||
|
const hasInput = state.hasText || state.hasAttachments;
|
||||||
|
switch (state.delivery) {
|
||||||
|
case "submit":
|
||||||
|
return state.workerState === "idle" && hasInput;
|
||||||
|
case "queue":
|
||||||
|
return state.workerState === "running" && hasInput;
|
||||||
|
case "notify":
|
||||||
|
return state.workerState === "running" && state.hasText &&
|
||||||
|
!state.hasAttachments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendComposerDelivery<T>(
|
||||||
|
state: ComposerDeliveryState,
|
||||||
|
method: T,
|
||||||
|
send: (method: T) => void,
|
||||||
|
): boolean {
|
||||||
|
if (!canDeliverComposerDraft(state)) return false;
|
||||||
|
send(method);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -1068,7 +1068,8 @@ Deno.test("Web Console uses Notify while running and exposes durable pending con
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const token of [
|
for (
|
||||||
|
const token of [
|
||||||
'method: "submit"',
|
'method: "submit"',
|
||||||
'method: "notify"',
|
'method: "notify"',
|
||||||
"notification_request_id: crypto.randomUUID()",
|
"notification_request_id: crypto.randomUUID()",
|
||||||
@@ -1079,10 +1080,17 @@ Deno.test("Web Console uses Notify while running and exposes durable pending con
|
|||||||
'method: "continue_pending"',
|
'method: "continue_pending"',
|
||||||
"handleQueueSubmit",
|
"handleQueueSubmit",
|
||||||
"handleNotifySubmit",
|
"handleNotifySubmit",
|
||||||
|
'submitDraft(composerInputElement?.snapshot() ?? draft, "queue")',
|
||||||
|
"disabled={!canQueueDraft}",
|
||||||
|
"disabled={!canNotifyDraft}",
|
||||||
">Queue Submit</button>",
|
">Queue Submit</button>",
|
||||||
">Notify</button>",
|
">Notify</button>",
|
||||||
]) {
|
]
|
||||||
assert(consolePage.includes(token), `missing durable pending control token: ${token}`);
|
) {
|
||||||
|
assert(
|
||||||
|
consolePage.includes(token),
|
||||||
|
`missing durable pending control token: ${token}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userCase = consolePage.slice(
|
const userCase = consolePage.slice(
|
||||||
|
|||||||
+54
-10
@@ -5,6 +5,11 @@
|
|||||||
import ConsoleTimeline from "$lib/workspace/console/ConsoleTimeline.svelte";
|
import ConsoleTimeline from "$lib/workspace/console/ConsoleTimeline.svelte";
|
||||||
import ComposerInput from "$lib/workspace/console/ComposerInput.svelte";
|
import ComposerInput from "$lib/workspace/console/ComposerInput.svelte";
|
||||||
import type { ComposerDraftSnapshot } from "$lib/workspace/console/composer-draft";
|
import type { ComposerDraftSnapshot } from "$lib/workspace/console/composer-draft";
|
||||||
|
import {
|
||||||
|
canDeliverComposerDraft,
|
||||||
|
sendComposerDelivery,
|
||||||
|
type ComposerDelivery,
|
||||||
|
} from "$lib/workspace/console/composer-delivery";
|
||||||
import {
|
import {
|
||||||
buildComposerSegmentsRequest,
|
buildComposerSegmentsRequest,
|
||||||
type WorkerConsoleInputRequest,
|
type WorkerConsoleInputRequest,
|
||||||
@@ -247,13 +252,42 @@
|
|||||||
const workerState = $derived(liveWorkerState ?? worker?.state ?? "loading");
|
const workerState = $derived(liveWorkerState ?? worker?.state ?? "loading");
|
||||||
const workerRunning = $derived(workerState === "running");
|
const workerRunning = $derived(workerState === "running");
|
||||||
const workerPaused = $derived(workerState === "paused");
|
const workerPaused = $derived(workerState === "paused");
|
||||||
const inputReady = $derived(workerState === "idle");
|
|
||||||
const composerEditable = $derived(protocolState === "open" && !sending);
|
const composerEditable = $derived(protocolState === "open" && !sending);
|
||||||
const canSubmitDraft = $derived(inputReady && composerEditable);
|
const draftHasText = $derived(draft.content.trim().length > 0);
|
||||||
const canSend = $derived(canSubmitDraft && draft.content.trim().length > 0);
|
const draftHasAttachments = $derived(attachments.length > 0);
|
||||||
|
const canSubmitDraft = $derived(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
delivery: "submit",
|
||||||
|
workerState,
|
||||||
|
protocolOpen: protocolState === "open",
|
||||||
|
sending,
|
||||||
|
hasText: draftHasText,
|
||||||
|
hasAttachments: draftHasAttachments,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const canQueueDraft = $derived(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
delivery: "queue",
|
||||||
|
workerState,
|
||||||
|
protocolOpen: protocolState === "open",
|
||||||
|
sending,
|
||||||
|
hasText: draftHasText,
|
||||||
|
hasAttachments: draftHasAttachments,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const canNotifyDraft = $derived(
|
||||||
|
canDeliverComposerDraft({
|
||||||
|
delivery: "notify",
|
||||||
|
workerState,
|
||||||
|
protocolOpen: protocolState === "open",
|
||||||
|
sending,
|
||||||
|
hasText: draftHasText,
|
||||||
|
hasAttachments: draftHasAttachments,
|
||||||
|
}),
|
||||||
|
);
|
||||||
const canStopFromComposer = $derived(workerRunning && composerEditable);
|
const canStopFromComposer = $derived(workerRunning && composerEditable);
|
||||||
const composerSubmitDisabled = $derived(
|
const composerSubmitDisabled = $derived(
|
||||||
workerRunning ? !canStopFromComposer : !canSend,
|
workerRunning ? !canStopFromComposer : !canSubmitDraft,
|
||||||
);
|
);
|
||||||
|
|
||||||
async function getJson<T>(path: string): Promise<T> {
|
async function getJson<T>(path: string): Promise<T> {
|
||||||
@@ -664,7 +698,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleQueueSubmit() {
|
function handleQueueSubmit() {
|
||||||
void submitDraft(composerInputElement?.snapshot() ?? draft);
|
void submitDraft(composerInputElement?.snapshot() ?? draft, "queue");
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNotifySubmit() {
|
function handleNotifySubmit() {
|
||||||
@@ -772,7 +806,7 @@
|
|||||||
|
|
||||||
async function submitDraft(
|
async function submitDraft(
|
||||||
value: ComposerDraftSnapshot,
|
value: ComposerDraftSnapshot,
|
||||||
delivery: "submit" | "notify" = "submit",
|
delivery: ComposerDelivery = "submit",
|
||||||
) {
|
) {
|
||||||
if (delivery === "notify" && attachments.length > 0) {
|
if (delivery === "notify" && attachments.length > 0) {
|
||||||
composerNotice = null;
|
composerNotice = null;
|
||||||
@@ -808,7 +842,15 @@
|
|||||||
composerInputElement?.clear();
|
composerInputElement?.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sending || !inputReady) {
|
const deliveryState = {
|
||||||
|
delivery,
|
||||||
|
workerState,
|
||||||
|
protocolOpen: protocolState === "open",
|
||||||
|
sending,
|
||||||
|
hasText: value.content.trim().length > 0,
|
||||||
|
hasAttachments: attachments.length > 0,
|
||||||
|
};
|
||||||
|
if (!canDeliverComposerDraft(deliveryState)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,7 +867,9 @@
|
|||||||
sendError = null;
|
sendError = null;
|
||||||
try {
|
try {
|
||||||
const method = composerRequestToProtocolMethod(request);
|
const method = composerRequestToProtocolMethod(request);
|
||||||
sendProtocolMethod(method);
|
if (!sendComposerDelivery(deliveryState, method, sendProtocolMethod)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
composerInputElement?.recordHistory(value);
|
composerInputElement?.recordHistory(value);
|
||||||
composerInputElement?.clear();
|
composerInputElement?.clear();
|
||||||
attachments = [];
|
attachments = [];
|
||||||
@@ -1963,12 +2007,12 @@
|
|||||||
{#if workerRunning}
|
{#if workerRunning}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={sending || !inputReady}
|
disabled={!canQueueDraft}
|
||||||
onclick={handleQueueSubmit}
|
onclick={handleQueueSubmit}
|
||||||
>Queue Submit</button>
|
>Queue Submit</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={sending || !inputReady}
|
disabled={!canNotifyDraft}
|
||||||
onclick={handleNotifySubmit}
|
onclick={handleNotifySubmit}
|
||||||
>Notify</button>
|
>Notify</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user