@swirls/looms 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/dist/actor/host.js +4 -6
- package/dist/actor/index.d.ts +1 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/routing.d.ts +5 -0
- package/dist/actor/routing.js +14 -0
- package/dist/agent/agent-session.js +1 -0
- package/dist/agent/debugger/agent-chat.js +45 -38
- package/dist/agent/debugger/streaming-text.d.ts +6 -0
- package/dist/agent/debugger/streaming-text.js +33 -0
- package/dist/agent/debugger/summarize.js +2 -0
- package/dist/agent/effects.d.ts +30 -0
- package/dist/agent/events.d.ts +2 -0
- package/dist/agent/events.js +2 -0
- package/dist/agent/llm.js +1 -8
- package/dist/agent/module.d.ts +230 -160
- package/dist/agent/module.js +11 -7
- package/dist/agent/projections.d.ts +5 -0
- package/dist/agent/projections.js +43 -2
- package/dist/agent/scope.d.ts +2 -0
- package/dist/agent/signals.d.ts +5 -0
- package/dist/agent/signals.js +7 -0
- package/dist/agent/threads.js +35 -21
- package/dist/agent/transcript.d.ts +7 -0
- package/dist/agent/transcript.js +19 -0
- package/dist/agent/types.d.ts +10 -0
- package/dist/agent/types.js +4 -0
- package/dist/approval/debugger/approvals-view.js +5 -3
- package/dist/approval/effects.d.ts +12 -0
- package/dist/approval/module.d.ts +24 -0
- package/dist/client/client.d.ts +292 -0
- package/dist/client/client.js +17 -1
- package/dist/cloudflare/router.d.ts +1 -1
- package/dist/cloudflare/router.js +6 -8
- package/dist/core/attempts.d.ts +8 -0
- package/dist/core/attempts.js +27 -0
- package/dist/core/effects.d.ts +7 -0
- package/dist/core/effects.js +1 -0
- package/dist/core/fold.js +12 -6
- package/dist/core/fork.d.ts +35 -0
- package/dist/core/fork.js +129 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +2 -0
- package/dist/core/module-scope.d.ts +1 -0
- package/dist/core/module.d.ts +2 -2
- package/dist/core/projection.d.ts +5 -4
- package/dist/core/projection.js +1 -0
- package/dist/core/protocol.d.ts +10 -1
- package/dist/core/protocol.js +11 -0
- package/dist/{react → core}/register.d.ts +9 -8
- package/dist/core/replay.d.ts +8 -0
- package/dist/core/snapshots.js +3 -17
- package/dist/core/state.d.ts +14 -0
- package/dist/core/state.js +25 -2
- package/dist/core/store.d.ts +1 -0
- package/dist/debugger/app/assets/index-CgDzWJg7.css +2 -0
- package/dist/debugger/app/assets/index-D--9esm9.js +22 -0
- package/dist/debugger/app/assets/routes-Dakrdusf.js +47 -0
- package/dist/debugger/app/index.html +2 -2
- package/dist/debugger/components/composer.d.ts +5 -2
- package/dist/debugger/components/composer.js +5 -4
- package/dist/debugger/components/run-tree.js +2 -5
- package/dist/debugger/events/catalog.js +1 -3
- package/dist/debugger/events/protocol-summarize.js +7 -0
- package/dist/debugger/lib/event-counts.d.ts +2 -2
- package/dist/debugger/lib/event-counts.js +5 -9
- package/dist/debugger/shell/debugger-shell.js +1 -1
- package/dist/debugger/shell/run-controls.d.ts +2 -1
- package/dist/debugger/shell/run-controls.js +40 -13
- package/dist/debugger/shell/run-search.js +1 -3
- package/dist/evaluator/effects.d.ts +45 -33
- package/dist/evaluator/module.d.ts +90 -66
- package/dist/projectors/index-model.d.ts +13 -7
- package/dist/projectors/index-model.js +21 -26
- package/dist/projectors/memory.js +19 -11
- package/dist/projectors/postgres.js +20 -19
- package/dist/projectors/schema.js +20 -18
- package/dist/projectors/sqlite.js +32 -27
- package/dist/react/derived.d.ts +26 -8
- package/dist/react/derived.js +23 -26
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.js +2 -2
- package/dist/react/materialize.js +16 -11
- package/dist/react/react.d.ts +12 -9
- package/dist/react/react.js +9 -6
- package/dist/react/store.d.ts +4 -5
- package/dist/react/tables.d.ts +3 -2
- package/dist/runtime/cancellation.js +14 -27
- package/dist/runtime/errors.d.ts +39 -0
- package/dist/runtime/errors.js +32 -0
- package/dist/runtime/helpers.d.ts +2 -0
- package/dist/runtime/helpers.js +23 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/looms.d.ts +4 -0
- package/dist/runtime/looms.js +1 -0
- package/dist/runtime/run-start.js +1 -3
- package/dist/runtime/runtime.d.ts +1 -1
- package/dist/runtime/runtime.js +45 -18
- package/dist/runtime/server/http.d.ts +2 -0
- package/dist/runtime/server/http.js +17 -2
- package/dist/runtime/server/routes/runs.js +35 -2
- package/dist/runtime/types.d.ts +8 -0
- package/dist/runtime/wake-engine.js +5 -2
- package/dist/s2/snapshot-store.js +2 -6
- package/dist/workflow/effects.d.ts +24 -0
- package/dist/workflow/effects.js +1 -0
- package/dist/workflow/module.d.ts +48 -0
- package/package.json +2 -2
- package/dist/debugger/app/assets/index-B3Ah8WAE.css +0 -2
- package/dist/debugger/app/assets/index-Qb371t_O.js +0 -22
- package/dist/debugger/app/assets/routes-CjtEHgPQ.js +0 -47
- /package/dist/{react → core}/register.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @swirls/looms
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 46be9ee: `LoomsRegister`, `RegisteredEvent`, and `AnyEventEnvelope` now live in `@swirls/looms/core`; `@swirls/looms/react` re-exports them, so existing `declare module '@swirls/looms/react'` registrations keep working. Standalone `defineProjection` reducers receive the registered event union, so client-only projections are typed without annotations. Registered event payloads were previously typed as `any`; they now carry their catalog types, so code that relied on `any` may need to narrow on `event.type` first.
|
|
8
|
+
- 46be9ee: `createFold`, `useEventFold`, `EventFoldDefinition`, and `foldEvents` in `@swirls/looms/react` are now `defineLiveFold`, `useLiveFold`, `LiveFoldDefinition`, and `foldLive(fold, events)`. A live fold always sees ephemeral events, so the `includeEphemeral` option is gone; use a projection for anything that does not need them. Live folds take an optional `shape` schema that infers their state type, and passing a projection where a live fold is expected is a type error.
|
|
9
|
+
|
|
10
|
+
Live folds no longer see ephemeral events from an effect attempt that the runtime retried, so streamed text from a failed attempt disappears without reducer code. `@swirls/looms/core` exports the rule as `dropSupersededAttempts(events)`, with `retriedEffectId(event)`.
|
|
11
|
+
|
|
12
|
+
## 0.3.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- 331ff55: `stopAgentTurn()` builds an `agent.turn.stopped` signal that ends the turn in progress without ending the run. A conversational agent aborts its in-flight model call or tool, cancels child threads its tool calls started, answers open tool calls with a stopped result, and waits for the next message. An agent session cancels its active turn and stays open. The debugger chat shows a Stop button while a turn is running.
|
|
17
|
+
|
|
18
|
+
An interrupting `steer` now ends the turn the same way before it starts the next one, so it no longer leaves tool calls unanswered or tool children running.
|
|
19
|
+
|
|
20
|
+
Cancelling a thread now cancels every live thread below it, and a signal interrupt also aborts in-flight work in the named thread's descendants.
|
|
21
|
+
|
|
22
|
+
The new `turnActivity` projection reports whether the root agent thread has a turn in progress.
|
|
23
|
+
|
|
24
|
+
- 8da450e: `POST /runs/:id/cancel` and `client.cancel(runId, threadId?)` cancel a run or thread over HTTP. The debugger's Cancel button now uses it, so cancelling an in-flight agent turn stops the run instead of retrying the turn. Cancel stays available while a wake is running. `POST /runs/:id/wake` and `client.wake()` return `appended`, the number of events the run gained during the wake, and the debugger's Wake button reports it. Client run tables, the debugger, and projector indexes now mark a cancelled run as `cancelled` instead of `running`, and projector indexes mark a run that completed with an error as `failed` instead of `completed`. `RunRow.status` and `RunSummary.status` are typed as `RunStatus`, and `completedRunStatus()` reads the outcome of a `runtime.run.completed` payload.
|
|
25
|
+
|
|
26
|
+
Signals sent to a completed, failed, or cancelled run now reject with `RunTerminalError` (HTTP 409) instead of being appended and ignored. Retrying a signal whose `idempotencyKey` was already accepted still succeeds. `useRunSummary` exposes `finished`, and the debugger disables the agent follow-up composer and approval decisions on finished runs.
|
|
27
|
+
|
|
28
|
+
- 6230fe1: `looms.fork(runId)`, `POST /runs/:id/fork`, and `client.fork()` start a new run that continues where a failed or cancelled run stopped. The fork copies the source's history up to the point it finished, keeping thread ids and sequence numbers, records `runtime.run.forked`, and re-issues the effects outstanding there. A cancelled agent re-runs the model call the cancel cut off; a failed run retries the effect whose failure ended it. Effects can declare `deterministic: true` so a fork retries the work behind them instead; workflow scheduling does. Forking a missing run rejects with `RunNotFoundError` (404 over HTTP). Actor and Durable Object hosts return 501. The debugger shows Continue on failed and cancelled runs, and `useRunSummary` reports `resumable` for them.
|
|
29
|
+
|
|
30
|
+
`runtime.thread.cancelled` records `resumeSeq` on the thread a cancel targeted: the run's last seq before the cancellation began.
|
|
31
|
+
|
|
32
|
+
- 275e6a8: The projector index is keyed by run, so runs that share thread or approval ids stay apart. `getActor(runId, actorId?)` returns the run row by default or one of its threads, review rows carry `runId` instead of `actorId`, and the tables are now `looms_run_actors` and `looms_run_reviews`. The built-in index projectors moved to version 2, so delivery rebuilds them; the old `looms_actors` and `looms_reviews` tables are no longer written.
|
|
33
|
+
|
|
3
34
|
## 0.2.0
|
|
4
35
|
|
|
5
36
|
### Minor Changes
|
package/dist/actor/host.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Data, Effect } from 'effect';
|
|
|
2
2
|
import { EventStoreTag } from '@swirls/looms/core';
|
|
3
3
|
import { createTimeoutScheduler, isLoomsApiPath } from '@swirls/looms/runtime';
|
|
4
4
|
import { createActorCell } from './cell.js';
|
|
5
|
-
import { resolveRunTarget, RunTargetError } from './routing.js';
|
|
5
|
+
import { crossRunResponse, resolveRunTarget, RunTargetError } from './routing.js';
|
|
6
6
|
class ActorWakeError extends Data.TaggedError('ActorWakeError') {
|
|
7
7
|
constructor(cause) {
|
|
8
8
|
super({
|
|
@@ -76,11 +76,9 @@ export function createLocalActorHost(options) {
|
|
|
76
76
|
if (url.pathname === '/health') {
|
|
77
77
|
return Promise.resolve(Response.json({ ok: true }));
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return Promise.resolve(
|
|
82
|
-
error: 'Global run listing is not supported in the actor host without a global index',
|
|
83
|
-
}, { status: 501 }));
|
|
79
|
+
const unsupported = crossRunResponse(req, 'the actor host');
|
|
80
|
+
if (unsupported) {
|
|
81
|
+
return Promise.resolve(unsupported);
|
|
84
82
|
}
|
|
85
83
|
return Effect.runPromise(Effect.gen(function* () {
|
|
86
84
|
const { runId, request } = yield* Effect.tryPromise({
|
package/dist/actor/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { createActorCell, type ActorCell, type ActorCellOptions } from './cell';
|
|
2
|
-
export { runIdFromRequest, resolveRunTarget, RunTargetError, type ResolvedRunTarget, } from './routing';
|
|
2
|
+
export { crossRunResponse, runIdFromRequest, resolveRunTarget, RunTargetError, type ResolvedRunTarget, } from './routing';
|
|
3
3
|
export { createLocalActorHost, type LocalActorHost, type LocalActorHostOptions } from './host';
|
package/dist/actor/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { createActorCell } from './cell.js';
|
|
2
|
-
export { runIdFromRequest, resolveRunTarget, RunTargetError, } from './routing.js';
|
|
2
|
+
export { crossRunResponse, runIdFromRequest, resolveRunTarget, RunTargetError, } from './routing.js';
|
|
3
3
|
export { createLocalActorHost } from './host.js';
|
package/dist/actor/routing.d.ts
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
* Inspects `x-looms-run-id` and `/runs/:runId/*`.
|
|
4
4
|
*/
|
|
5
5
|
export declare function runIdFromRequest(req: Request): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* A 501 for requests that span runs, which a host with one cell per run cannot answer: listing
|
|
8
|
+
* runs needs a global index, and a fork writes a second run. `host` names the backend.
|
|
9
|
+
*/
|
|
10
|
+
export declare function crossRunResponse(req: Request, host: string): Response | null;
|
|
6
11
|
export interface ResolvedRunTarget {
|
|
7
12
|
readonly runId: string;
|
|
8
13
|
readonly request: Request;
|
package/dist/actor/routing.js
CHANGED
|
@@ -22,6 +22,20 @@ export function runIdFromRequest(req) {
|
|
|
22
22
|
}
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* A 501 for requests that span runs, which a host with one cell per run cannot answer: listing
|
|
27
|
+
* runs needs a global index, and a fork writes a second run. `host` names the backend.
|
|
28
|
+
*/
|
|
29
|
+
export function crossRunResponse(req, host) {
|
|
30
|
+
const { pathname } = new URL(req.url);
|
|
31
|
+
if (req.method === 'GET' && (pathname === '/runs' || pathname === '/runs/summaries')) {
|
|
32
|
+
return Response.json({ error: `Global run listing is not supported in ${host} without a global index` }, { status: 501 });
|
|
33
|
+
}
|
|
34
|
+
if (req.method === 'POST' && /^\/runs\/[^/]+\/fork$/.test(pathname)) {
|
|
35
|
+
return Response.json({ error: `Forking runs is not supported in ${host}; each run lives in its own cell` }, { status: 501 });
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
25
39
|
export class RunTargetError extends Data.TaggedError('RunTargetError') {
|
|
26
40
|
constructor(method, path) {
|
|
27
41
|
super({
|
|
@@ -140,6 +140,7 @@ export function createAgentSessionThread(definitions) {
|
|
|
140
140
|
pendingClosures: state.pendingClosures.filter((closure) => closure.messageId !== event.payload.messageId),
|
|
141
141
|
};
|
|
142
142
|
case 'agent.session.cancel.requested':
|
|
143
|
+
case 'agent.turn.stopped':
|
|
143
144
|
case 'runtime.thread.cancel.requested':
|
|
144
145
|
return state.activeTurn ? { ...state, cancelling: true } : state;
|
|
145
146
|
case 'runtime.wait.satisfied': {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Bot, CornerDownRight, MessageSquare, Settings2, User, Wrench } from 'lucide-react';
|
|
2
|
+
import { Bot, CornerDownRight, GitFork, MessageSquare, Settings2, User, Wrench } from 'lucide-react';
|
|
3
3
|
import { useMemo, useState } from 'react';
|
|
4
|
-
import { createEventId, isJsonObject, isJsonString } from '@swirls/looms/core';
|
|
5
|
-
import { cn, Composer, EmptyState, errorMessage, FollowList, inputForm, JsonTree, messageInput, parseStructuredJson, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, shortId, useDebugger, useStartRun, } from '@swirls/looms/debugger';
|
|
6
|
-
import {
|
|
7
|
-
import { conversation } from '../projections.js';
|
|
8
|
-
import { agentSessionMessageEvents, userMessage } from '../signals.js';
|
|
4
|
+
import { createEventId, isJsonObject, isJsonString, } from '@swirls/looms/core';
|
|
5
|
+
import { Button, cn, Composer, EmptyState, errorMessage, FollowList, inputForm, JsonTree, messageInput, parseStructuredJson, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, shortId, useDebugger, useStartRun, } from '@swirls/looms/debugger';
|
|
6
|
+
import { useLiveFold, useProjection, useRunStore, useRunSummary } from '@swirls/looms/react';
|
|
7
|
+
import { conversation, turnActivity } from '../projections.js';
|
|
8
|
+
import { agentSessionMessageEvents, stopAgentTurn, userMessage } from '../signals.js';
|
|
9
|
+
import { streamingText } from './streaming-text.js';
|
|
9
10
|
/** Child thread of the session turn that is still open, from the events the store already has. */
|
|
10
11
|
function activeSessionChild(events) {
|
|
11
12
|
let child;
|
|
@@ -26,32 +27,6 @@ function activeSessionChild(events) {
|
|
|
26
27
|
}
|
|
27
28
|
return child;
|
|
28
29
|
}
|
|
29
|
-
const streamingFold = createFold({
|
|
30
|
-
name: 'agentStreamingText',
|
|
31
|
-
initialState: { lastMessageSeq: -1, turnStarted: false, parts: [] },
|
|
32
|
-
includeEphemeral: true,
|
|
33
|
-
reduce(state, event) {
|
|
34
|
-
if (event.type === 'agent.message') {
|
|
35
|
-
return { lastMessageSeq: event.seq, turnStarted: false, parts: [] };
|
|
36
|
-
}
|
|
37
|
-
if (event.seq <= state.lastMessageSeq) {
|
|
38
|
-
return state;
|
|
39
|
-
}
|
|
40
|
-
if (event.type === 'agent.turn.started') {
|
|
41
|
-
return { ...state, turnStarted: true };
|
|
42
|
-
}
|
|
43
|
-
if (event.type === 'agent.turn.text_delta' &&
|
|
44
|
-
isJsonObject(event.payload) &&
|
|
45
|
-
isJsonString(event.payload.delta)) {
|
|
46
|
-
return {
|
|
47
|
-
...state,
|
|
48
|
-
turnStarted: true,
|
|
49
|
-
parts: state.parts.concat(event.payload.delta),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return state;
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
30
|
const deliveryOptions = [
|
|
56
31
|
{ value: 'followUp', label: 'Follow up', description: 'Queue after the current turn' },
|
|
57
32
|
{ value: 'steer', label: 'Steer', description: 'Redirect the active turn' },
|
|
@@ -95,7 +70,7 @@ function StreamingItem({ text }) {
|
|
|
95
70
|
function Transcript({ runId }) {
|
|
96
71
|
const store = useRunStore(runId);
|
|
97
72
|
const convo = useProjection(store, conversation);
|
|
98
|
-
const streaming =
|
|
73
|
+
const streaming = useLiveFold(store, streamingText);
|
|
99
74
|
const stream = streaming.turnStarted ? streaming.parts.join('') : null;
|
|
100
75
|
const toolNames = useMemo(() => {
|
|
101
76
|
const names = new Map();
|
|
@@ -143,17 +118,49 @@ export function AgentChat(context) {
|
|
|
143
118
|
? (started) => client.signal(started.runId, agentSessionMessageEvents(createEventId(), content, { threadId: started.threadId }))
|
|
144
119
|
: undefined);
|
|
145
120
|
}
|
|
146
|
-
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [runId ? (_jsx(Transcript, { runId: runId })) : (_jsx("div", { className: "min-h-0 flex-1", children: _jsx(EmptyState, { icon: _jsx(MessageSquare, {}), title: `Talk to ${definition.name}`, children: "Your first message starts a new run." }) })), runId ? (_jsx(FollowUp, { runId: runId, session: session, delivery: delivery, setDelivery: setDelivery, startError: error })) : (_jsx(Composer, { value: draft, onChange: setDraft, onSubmit: send, pending: pending, placeholder: `Message ${definition.name}…`, submitLabel: "Start", error: error }))] }));
|
|
121
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [runId ? (_jsx(Transcript, { runId: runId })) : (_jsx("div", { className: "min-h-0 flex-1", children: _jsx(EmptyState, { icon: _jsx(MessageSquare, {}), title: `Talk to ${definition.name}`, children: "Your first message starts a new run." }) })), runId ? (_jsx(FollowUp, { runId: runId, session: session, delivery: delivery, setDelivery: setDelivery, startError: error, onForked: context.onStarted })) : (_jsx(Composer, { value: draft, onChange: setDraft, onSubmit: send, pending: pending, placeholder: `Message ${definition.name}…`, submitLabel: "Start", error: error }))] }));
|
|
122
|
+
}
|
|
123
|
+
function FollowUp({ onForked, ...props }) {
|
|
124
|
+
const { status, resumable } = useRunSummary(useRunStore(props.runId));
|
|
125
|
+
return resumable ? (_jsx(ContinueRun, { runId: props.runId, status: status, onForked: onForked })) : (_jsx(Reply, { ...props }));
|
|
147
126
|
}
|
|
148
|
-
function
|
|
127
|
+
function ContinueRun({ runId, status, onForked, }) {
|
|
128
|
+
const { client } = useDebugger();
|
|
129
|
+
const [pending, setPending] = useState(false);
|
|
130
|
+
const [error, setError] = useState(null);
|
|
131
|
+
function continueInNewRun() {
|
|
132
|
+
setPending(true);
|
|
133
|
+
setError(null);
|
|
134
|
+
client
|
|
135
|
+
.fork(runId)
|
|
136
|
+
.then((forked) => onForked(forked.runId))
|
|
137
|
+
.catch((cause) => setError(errorMessage(cause)))
|
|
138
|
+
.finally(() => setPending(false));
|
|
139
|
+
}
|
|
140
|
+
return (_jsxs("div", { className: "border-border shrink-0 border-t p-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("p", { className: "text-muted-foreground min-w-0 flex-1 text-xs", children: ["Run ", status, ". Continue from where it stopped in a new run."] }), _jsxs(Button, { size: "sm", variant: "outline", disabled: pending, onClick: continueInNewRun, children: [_jsx(GitFork, {}), "Continue in new run"] })] }), error ? _jsx("p", { className: "text-status-failed mt-2 text-[11px]", children: error }) : null] }));
|
|
141
|
+
}
|
|
142
|
+
function Reply({ runId, session, delivery, setDelivery, startError }) {
|
|
149
143
|
const store = useRunStore(runId);
|
|
150
|
-
const { rootThreadId } = useRunSummary(store);
|
|
144
|
+
const { rootThreadId, status, finished } = useRunSummary(store);
|
|
145
|
+
const turn = useProjection(store, turnActivity);
|
|
151
146
|
const [draft, setDraft] = useState('');
|
|
152
147
|
const [pending, setPending] = useState(false);
|
|
148
|
+
const [stopping, setStopping] = useState(false);
|
|
153
149
|
const [error, setError] = useState(null);
|
|
150
|
+
function stop() {
|
|
151
|
+
if (!rootThreadId) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
setStopping(true);
|
|
155
|
+
setError(null);
|
|
156
|
+
store
|
|
157
|
+
.commit(stopAgentTurn({ threadId: rootThreadId }))
|
|
158
|
+
.catch((cause) => setError(errorMessage(cause)))
|
|
159
|
+
.finally(() => setStopping(false));
|
|
160
|
+
}
|
|
154
161
|
function send() {
|
|
155
162
|
const content = draft.trim();
|
|
156
|
-
if (!content || !rootThreadId) {
|
|
163
|
+
if (!content || !rootThreadId || finished) {
|
|
157
164
|
return;
|
|
158
165
|
}
|
|
159
166
|
setPending(true);
|
|
@@ -172,5 +179,5 @@ function FollowUp({ runId, session, delivery, setDelivery, startError, }) {
|
|
|
172
179
|
.catch((cause) => setError(errorMessage(cause)))
|
|
173
180
|
.finally(() => setPending(false));
|
|
174
181
|
}
|
|
175
|
-
return (_jsx(Composer, { value: draft, onChange: setDraft, onSubmit: send, pending: pending, disabled: !rootThreadId, placeholder:
|
|
182
|
+
return (_jsx(Composer, { value: draft, onChange: setDraft, onSubmit: send, onStop: turn.active && !finished ? stop : undefined, stopping: stopping, pending: pending, disabled: !rootThreadId || finished, placeholder: finished ? `Run ${status}. Start a new run to continue.` : 'Send a follow-up…', error: error ?? startError, toolbar: session ? _jsx(DeliverySelect, { value: delivery, onChange: setDelivery }) : null }));
|
|
176
183
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Text the active turn has streamed since the last durable agent message. */
|
|
2
|
+
export declare const streamingText: import("@swirls/looms/react").LiveFoldDefinition<{
|
|
3
|
+
readonly lastMessageSeq: number;
|
|
4
|
+
readonly turnStarted: boolean;
|
|
5
|
+
readonly parts: readonly string[];
|
|
6
|
+
}, import("@swirls/looms/core").EventEnvelope>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
import { isJsonObject, isJsonString } from '@swirls/looms/core';
|
|
3
|
+
import { defineLiveFold } from '@swirls/looms/react';
|
|
4
|
+
const StreamingTextSchema = Schema.Struct({
|
|
5
|
+
lastMessageSeq: Schema.Finite,
|
|
6
|
+
turnStarted: Schema.Boolean,
|
|
7
|
+
parts: Schema.Array(Schema.String),
|
|
8
|
+
});
|
|
9
|
+
/** Text the active turn has streamed since the last durable agent message. */
|
|
10
|
+
export const streamingText = defineLiveFold({
|
|
11
|
+
name: 'agentStreamingText',
|
|
12
|
+
shape: StreamingTextSchema,
|
|
13
|
+
initialState: { lastMessageSeq: -1, turnStarted: false, parts: [] },
|
|
14
|
+
reduce(state, event) {
|
|
15
|
+
if (event.type === 'agent.message' ||
|
|
16
|
+
event.type === 'agent.turn.stopped' ||
|
|
17
|
+
event.type === 'runtime.thread.cancelled') {
|
|
18
|
+
return { lastMessageSeq: event.seq, turnStarted: false, parts: [] };
|
|
19
|
+
}
|
|
20
|
+
if (event.seq <= state.lastMessageSeq) {
|
|
21
|
+
return state;
|
|
22
|
+
}
|
|
23
|
+
if (event.type === 'agent.turn.started') {
|
|
24
|
+
return { ...state, turnStarted: true };
|
|
25
|
+
}
|
|
26
|
+
if (event.type === 'agent.turn.text_delta' &&
|
|
27
|
+
isJsonObject(event.payload) &&
|
|
28
|
+
isJsonString(event.payload.delta)) {
|
|
29
|
+
return { ...state, turnStarted: true, parts: state.parts.concat(event.payload.delta) };
|
|
30
|
+
}
|
|
31
|
+
return state;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -38,6 +38,8 @@ export function summarizeAgentEvent(event) {
|
|
|
38
38
|
};
|
|
39
39
|
case 'agent.steered':
|
|
40
40
|
return { title: 'steered', detail: messageContent(event.payload) };
|
|
41
|
+
case 'agent.turn.stopped':
|
|
42
|
+
return { title: 'turn stopped' };
|
|
41
43
|
case 'agent.spawn.requested':
|
|
42
44
|
return {
|
|
43
45
|
title: 'agent spawn',
|
package/dist/agent/effects.d.ts
CHANGED
|
@@ -203,6 +203,9 @@ export declare const callLlmEffect: NoInfer<import("@swirls/looms/core").EffectD
|
|
|
203
203
|
readonly payload: {
|
|
204
204
|
readonly turn: number;
|
|
205
205
|
};
|
|
206
|
+
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
207
|
+
readonly type: "agent.turn.stopped";
|
|
208
|
+
readonly payload: {};
|
|
206
209
|
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
207
210
|
readonly type: "agent.turn.text_delta";
|
|
208
211
|
readonly payload: {
|
|
@@ -291,6 +294,11 @@ export declare const callLlmEffect: NoInfer<import("@swirls/looms/core").EffectD
|
|
|
291
294
|
readonly attempt: number;
|
|
292
295
|
readonly deadlineAt: number | null;
|
|
293
296
|
};
|
|
297
|
+
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
298
|
+
readonly type: "runtime.fork.elided";
|
|
299
|
+
readonly payload: {
|
|
300
|
+
readonly type: string;
|
|
301
|
+
};
|
|
294
302
|
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
295
303
|
readonly type: "runtime.run.cancelled";
|
|
296
304
|
readonly payload: {
|
|
@@ -302,6 +310,12 @@ export declare const callLlmEffect: NoInfer<import("@swirls/looms/core").EffectD
|
|
|
302
310
|
readonly output: Schema.Json;
|
|
303
311
|
readonly error: string | null;
|
|
304
312
|
};
|
|
313
|
+
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
314
|
+
readonly type: "runtime.run.forked";
|
|
315
|
+
readonly payload: {
|
|
316
|
+
readonly sourceRunId: string;
|
|
317
|
+
readonly sourceSeq: number;
|
|
318
|
+
};
|
|
305
319
|
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
306
320
|
readonly type: "runtime.run.started";
|
|
307
321
|
readonly payload: {
|
|
@@ -334,6 +348,7 @@ export declare const callLlmEffect: NoInfer<import("@swirls/looms/core").EffectD
|
|
|
334
348
|
readonly payload: {
|
|
335
349
|
readonly threadId: string;
|
|
336
350
|
readonly reason?: string | undefined;
|
|
351
|
+
readonly resumeSeq?: number | undefined;
|
|
337
352
|
};
|
|
338
353
|
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
339
354
|
readonly type: "runtime.thread.completed";
|
|
@@ -587,6 +602,9 @@ export declare const executeToolEffect: NoInfer<import("@swirls/looms/core").Eff
|
|
|
587
602
|
readonly payload: {
|
|
588
603
|
readonly turn: number;
|
|
589
604
|
};
|
|
605
|
+
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
606
|
+
readonly type: "agent.turn.stopped";
|
|
607
|
+
readonly payload: {};
|
|
590
608
|
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
591
609
|
readonly type: "agent.turn.text_delta";
|
|
592
610
|
readonly payload: {
|
|
@@ -675,6 +693,11 @@ export declare const executeToolEffect: NoInfer<import("@swirls/looms/core").Eff
|
|
|
675
693
|
readonly attempt: number;
|
|
676
694
|
readonly deadlineAt: number | null;
|
|
677
695
|
};
|
|
696
|
+
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
697
|
+
readonly type: "runtime.fork.elided";
|
|
698
|
+
readonly payload: {
|
|
699
|
+
readonly type: string;
|
|
700
|
+
};
|
|
678
701
|
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
679
702
|
readonly type: "runtime.run.cancelled";
|
|
680
703
|
readonly payload: {
|
|
@@ -686,6 +709,12 @@ export declare const executeToolEffect: NoInfer<import("@swirls/looms/core").Eff
|
|
|
686
709
|
readonly output: Schema.Json;
|
|
687
710
|
readonly error: string | null;
|
|
688
711
|
};
|
|
712
|
+
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
713
|
+
readonly type: "runtime.run.forked";
|
|
714
|
+
readonly payload: {
|
|
715
|
+
readonly sourceRunId: string;
|
|
716
|
+
readonly sourceSeq: number;
|
|
717
|
+
};
|
|
689
718
|
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
690
719
|
readonly type: "runtime.run.started";
|
|
691
720
|
readonly payload: {
|
|
@@ -718,6 +747,7 @@ export declare const executeToolEffect: NoInfer<import("@swirls/looms/core").Eff
|
|
|
718
747
|
readonly payload: {
|
|
719
748
|
readonly threadId: string;
|
|
720
749
|
readonly reason?: string | undefined;
|
|
750
|
+
readonly resumeSeq?: number | undefined;
|
|
721
751
|
};
|
|
722
752
|
}) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
|
|
723
753
|
readonly type: "runtime.thread.completed";
|
package/dist/agent/events.d.ts
CHANGED
|
@@ -69,6 +69,7 @@ export declare const AgentSteeredPayloadSchema: Schema.Struct<{
|
|
|
69
69
|
readonly interrupt: Schema.optional<Schema.Boolean>;
|
|
70
70
|
}>;
|
|
71
71
|
export declare const AgentSessionDeliverySchema: Schema.Union<readonly [Schema.Literal<"steer">, Schema.Literal<"followUp">, Schema.Literal<"nextTurn">]>;
|
|
72
|
+
export declare const AgentTurnStoppedPayloadSchema: Schema.Struct<{}>;
|
|
72
73
|
export declare const AgentSessionMessageSubmittedPayloadSchema: Schema.Struct<{
|
|
73
74
|
readonly messageId: Schema.String;
|
|
74
75
|
readonly text: Schema.String;
|
|
@@ -231,6 +232,7 @@ export declare const agentCatalog: import("@swirls/looms/core").EventCatalog<"ag
|
|
|
231
232
|
}>;
|
|
232
233
|
readonly interrupt: Schema.optional<Schema.Boolean>;
|
|
233
234
|
}>;
|
|
235
|
+
'turn.stopped': Schema.Struct<{}>;
|
|
234
236
|
'session.message.submitted': Schema.Struct<{
|
|
235
237
|
readonly messageId: Schema.String;
|
|
236
238
|
readonly text: Schema.String;
|
package/dist/agent/events.js
CHANGED
|
@@ -38,6 +38,7 @@ export const AgentSessionDeliverySchema = Schema.Union([
|
|
|
38
38
|
Schema.Literal('followUp'),
|
|
39
39
|
Schema.Literal('nextTurn'),
|
|
40
40
|
]);
|
|
41
|
+
export const AgentTurnStoppedPayloadSchema = Schema.Struct({});
|
|
41
42
|
export const AgentSessionMessageSubmittedPayloadSchema = Schema.Struct({
|
|
42
43
|
messageId: Schema.String,
|
|
43
44
|
text: Schema.String,
|
|
@@ -86,6 +87,7 @@ export const agentCatalog = defineEventCatalog('agent', {
|
|
|
86
87
|
'tool_call.requested': AgentToolCallRequestedPayloadSchema,
|
|
87
88
|
'tool.result': AgentToolResultPayloadSchema,
|
|
88
89
|
steered: AgentSteeredPayloadSchema,
|
|
90
|
+
'turn.stopped': AgentTurnStoppedPayloadSchema,
|
|
89
91
|
'session.message.submitted': AgentSessionMessageSubmittedPayloadSchema,
|
|
90
92
|
'session.steer.delivered': AgentSessionSteerDeliveredPayloadSchema,
|
|
91
93
|
'session.turn.started': AgentSessionTurnStartedPayloadSchema,
|
package/dist/agent/llm.js
CHANGED
|
@@ -36,14 +36,7 @@ export const makeStubLlm = (policy = {}) => ({
|
|
|
36
36
|
toolCalls,
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
for (let i = args.messages.length - 1; i >= 0; i--) {
|
|
41
|
-
const item = args.messages[i];
|
|
42
|
-
if (item?.role === 'user') {
|
|
43
|
-
lastUser = item;
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
39
|
+
const lastUser = args.messages.findLast((item) => item.role === 'user');
|
|
47
40
|
const lastContent = args.messages.at(-1)?.content;
|
|
48
41
|
const content = lastUser?.content ??
|
|
49
42
|
(Predicate.isString(lastContent) ? lastContent : stringifyJson(lastContent ?? null));
|