@fungi.computer/parakeet 0.0.0-stage → 0.0.1
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/LICENSE +21 -0
- package/README.md +186 -2
- package/dist/account-commands.d.ts +102 -0
- package/dist/account-commands.js +239 -0
- package/dist/agent-head.d.ts +4 -0
- package/dist/agent-head.js +2 -0
- package/dist/client-contributions.d.ts +20 -0
- package/dist/client-contributions.js +41 -0
- package/dist/commands/advertised.d.ts +23 -0
- package/dist/commands/advertised.js +72 -0
- package/dist/commands/builtins.d.ts +14 -0
- package/dist/commands/builtins.js +91 -0
- package/dist/commands/hints.d.ts +36 -0
- package/dist/commands/hints.js +64 -0
- package/dist/commands/interactions.d.ts +55 -0
- package/dist/commands/interactions.js +101 -0
- package/dist/commands/run.d.ts +11 -0
- package/dist/commands/run.js +61 -0
- package/dist/commands/slash.d.ts +124 -0
- package/dist/commands/slash.js +182 -0
- package/dist/commands/use-slash-commands.d.ts +57 -0
- package/dist/commands/use-slash-commands.js +207 -0
- package/dist/components/AgentHead.d.ts +27 -0
- package/dist/components/AgentHead.js +53 -0
- package/dist/components/ChatComposer.d.ts +43 -0
- package/dist/components/ChatComposer.js +209 -0
- package/dist/components/ChatMessage.d.ts +8 -0
- package/dist/components/ChatMessage.js +16 -0
- package/dist/components/ClientRows.d.ts +11 -0
- package/dist/components/ClientRows.js +46 -0
- package/dist/components/CodeBlock.d.ts +11 -0
- package/dist/components/CodeBlock.js +38 -0
- package/dist/components/InteractionPanel.d.ts +15 -0
- package/dist/components/InteractionPanel.js +42 -0
- package/dist/components/MarkdownRenderer.d.ts +6 -0
- package/dist/components/MarkdownRenderer.js +48 -0
- package/dist/components/RunFailureNotice.d.ts +7 -0
- package/dist/components/RunFailureNotice.js +13 -0
- package/dist/components/ShiitakeAvatar.d.ts +19 -0
- package/dist/components/ShiitakeAvatar.js +20 -0
- package/dist/components/SlashMenu.d.ts +8 -0
- package/dist/components/SlashMenu.js +50 -0
- package/dist/components/ThinkingBlock.d.ts +3 -0
- package/dist/components/ThinkingBlock.js +18 -0
- package/dist/components/TimelineItem.d.ts +15 -0
- package/dist/components/TimelineItem.js +35 -0
- package/dist/components/ToolGroup.d.ts +12 -0
- package/dist/components/ToolGroup.js +90 -0
- package/dist/components/WaitingNotice.d.ts +7 -0
- package/dist/components/WaitingNotice.js +8 -0
- package/dist/components/avatar-presentation.d.ts +23 -0
- package/dist/components/avatar-presentation.js +16 -0
- package/dist/components/sprite-service.d.ts +2 -0
- package/dist/components/sprite-service.js +9 -0
- package/dist/components/still-frame-cache.d.ts +16 -0
- package/dist/components/still-frame-cache.js +63 -0
- package/dist/conversation.d.ts +164 -0
- package/dist/conversation.js +760 -0
- package/dist/core/assistant-visibility.d.ts +3 -0
- package/dist/core/assistant-visibility.js +4 -0
- package/dist/core/author-labels.d.ts +16 -0
- package/dist/core/author-labels.js +16 -0
- package/dist/core/build-timeline.d.ts +36 -0
- package/dist/core/build-timeline.js +328 -0
- package/dist/core/client-actions.d.ts +35 -0
- package/dist/core/client-actions.js +128 -0
- package/dist/core/client-contributions.d.ts +24 -0
- package/dist/core/client-contributions.js +115 -0
- package/dist/core/client-timeline.d.ts +33 -0
- package/dist/core/client-timeline.js +72 -0
- package/dist/core/format-context.d.ts +11 -0
- package/dist/core/format-context.js +20 -0
- package/dist/core/format-tokens.d.ts +2 -0
- package/dist/core/format-tokens.js +6 -0
- package/dist/core/model-identity.d.ts +3 -0
- package/dist/core/model-identity.js +7 -0
- package/dist/core/session-projection.d.ts +11 -0
- package/dist/core/session-projection.js +191 -0
- package/dist/core/session-query.d.ts +118 -0
- package/dist/core/session-query.js +187 -0
- package/dist/core/session-view-state.d.ts +66 -0
- package/dist/core/session-view-state.js +114 -0
- package/dist/core/tool-contract.d.ts +15 -0
- package/dist/core/tool-contract.js +3 -0
- package/dist/core/tool-groups.d.ts +36 -0
- package/dist/core/tool-groups.js +151 -0
- package/dist/core/tool-view.d.ts +182 -0
- package/dist/core/tool-view.js +618 -0
- package/dist/headless.d.ts +28 -0
- package/dist/headless.js +24 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +4 -0
- package/dist/lib/failure.d.ts +2 -0
- package/dist/lib/failure.js +9 -0
- package/dist/markdown.d.ts +3 -0
- package/dist/markdown.js +6 -0
- package/dist/model-commands.d.ts +44 -0
- package/dist/model-commands.js +232 -0
- package/dist/parakeet.d.ts +77 -0
- package/dist/parakeet.js +276 -0
- package/dist/provider-choices.d.ts +8 -0
- package/dist/provider-choices.js +43 -0
- package/dist/reload-command.d.ts +43 -0
- package/dist/reload-command.js +72 -0
- package/dist/session-commands.d.ts +53 -0
- package/dist/session-commands.js +259 -0
- package/dist/session.d.ts +24 -0
- package/dist/session.js +1 -0
- package/dist/store/types.d.ts +163 -0
- package/dist/store/types.js +1 -0
- package/dist/styles.css +19156 -0
- package/docs/api/README.md +15 -0
- package/docs/api/commands/slash.md +321 -0
- package/docs/api/headless.md +2510 -0
- package/docs/api/index.md +1253 -0
- package/package.json +79 -3
- package/src/styles.css +268 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { clientContributionSchemaV1, parseClientEntryV1, } from "@fungi.computer/shiitake/client-entry";
|
|
2
|
+
import { parseCatalog } from "@fungi.computer/whistle/catalog";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
const decodeContribution = Schema.decodeUnknownSync(clientContributionSchemaV1, {
|
|
5
|
+
onExcessProperty: "error",
|
|
6
|
+
});
|
|
7
|
+
function actions(value) {
|
|
8
|
+
return [
|
|
9
|
+
...value.panel.flatMap((row) => row.actions ?? []),
|
|
10
|
+
...value.timeline.cells.flatMap((cell) => cell.rows.flatMap((row) => row.actions ?? [])),
|
|
11
|
+
...(value.waiting === null ? [] : [value.waiting.cancel]),
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
function admitActions(value, slot) {
|
|
15
|
+
const declaredActions = actions(value);
|
|
16
|
+
const inspections = value.panel.flatMap((row) => row.inspection === undefined ? [] : [row.inspection]);
|
|
17
|
+
if (declaredActions.length === 0 && inspections.length === 0)
|
|
18
|
+
return;
|
|
19
|
+
if (slot?.status !== "running" || slot.catalog === undefined)
|
|
20
|
+
throw new Error("Client row actions require a running advertised catalog");
|
|
21
|
+
const catalog = parseCatalog(slot.catalog);
|
|
22
|
+
const ids = new Set(catalog.commands.map((command) => command.id));
|
|
23
|
+
for (const inspection of inspections)
|
|
24
|
+
if (!ids.has(inspection.commandId))
|
|
25
|
+
throw new Error(`Client inspection ${inspection.commandId} is not advertised by its entry`);
|
|
26
|
+
for (const action of declaredActions)
|
|
27
|
+
if (!ids.has(action.id))
|
|
28
|
+
throw new Error(`Client row action ${action.id} is not advertised by its entry`);
|
|
29
|
+
}
|
|
30
|
+
function admitContribution(contribute, slot) {
|
|
31
|
+
try {
|
|
32
|
+
const value = decodeContribution(contribute(slot));
|
|
33
|
+
admitActions(value, slot);
|
|
34
|
+
return { status: "admitted", value };
|
|
35
|
+
}
|
|
36
|
+
catch (cause) {
|
|
37
|
+
return {
|
|
38
|
+
status: "failed",
|
|
39
|
+
error: cause instanceof Error
|
|
40
|
+
? cause
|
|
41
|
+
: new Error("Client contribution failed", { cause }),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The selected-Session evaluation owner. Feed existing observed slots here;
|
|
47
|
+
* renderers subscribe to admitted data and never evaluate an entry in render.
|
|
48
|
+
*/
|
|
49
|
+
export function createClientContributions(installed, onError) {
|
|
50
|
+
const entries = installed.map((entry) => parseClientEntryV1(entry));
|
|
51
|
+
if (new Set(entries.map((entry) => entry.id)).size !== entries.length)
|
|
52
|
+
throw new Error("Duplicate client contribution entry");
|
|
53
|
+
const previous = new Map();
|
|
54
|
+
const admitted = new Map();
|
|
55
|
+
const failures = new Map();
|
|
56
|
+
const listeners = new Set();
|
|
57
|
+
let state = [];
|
|
58
|
+
let disposed = false;
|
|
59
|
+
return {
|
|
60
|
+
snapshot: () => state,
|
|
61
|
+
subscribe: (listener) => {
|
|
62
|
+
if (disposed)
|
|
63
|
+
return () => undefined;
|
|
64
|
+
listeners.add(listener);
|
|
65
|
+
return () => {
|
|
66
|
+
listeners.delete(listener);
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
update: (slots) => {
|
|
70
|
+
if (disposed)
|
|
71
|
+
return;
|
|
72
|
+
let changed = false;
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
if (entry.contribute === undefined)
|
|
75
|
+
continue;
|
|
76
|
+
const slot = slots[entry.id];
|
|
77
|
+
if (previous.has(entry.id) && previous.get(entry.id) === slot)
|
|
78
|
+
continue;
|
|
79
|
+
previous.set(entry.id, slot);
|
|
80
|
+
admitted.delete(entry.id);
|
|
81
|
+
changed = true;
|
|
82
|
+
const result = admitContribution(entry.contribute, slot);
|
|
83
|
+
if (result.status === "admitted") {
|
|
84
|
+
admitted.set(entry.id, { entryId: entry.id, value: result.value });
|
|
85
|
+
failures.delete(entry.id);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
const { error } = result;
|
|
89
|
+
const signature = `${error.name}: ${error.message}`;
|
|
90
|
+
if (failures.get(entry.id) !== signature)
|
|
91
|
+
onError(entry.id, error);
|
|
92
|
+
failures.set(entry.id, signature);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!changed)
|
|
96
|
+
return;
|
|
97
|
+
state = entries.flatMap((entry) => {
|
|
98
|
+
const contribution = admitted.get(entry.id);
|
|
99
|
+
return contribution === undefined ? [] : [contribution];
|
|
100
|
+
});
|
|
101
|
+
for (const listener of listeners)
|
|
102
|
+
listener();
|
|
103
|
+
},
|
|
104
|
+
dispose: () => {
|
|
105
|
+
disposed = true;
|
|
106
|
+
previous.clear();
|
|
107
|
+
failures.clear();
|
|
108
|
+
admitted.clear();
|
|
109
|
+
state = [];
|
|
110
|
+
for (const listener of listeners)
|
|
111
|
+
listener();
|
|
112
|
+
listeners.clear();
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ClientTimelineCellV1 } from "@fungi.computer/shiitake/client-entry";
|
|
2
|
+
import type { ClientContribution } from "./client-contributions.js";
|
|
3
|
+
/** A committed cell retains its producer namespace in every renderer. */
|
|
4
|
+
export type PlacedClientCell = Readonly<{
|
|
5
|
+
/** Namespace of the entry that produced this committed event. */
|
|
6
|
+
entryId: string;
|
|
7
|
+
/** Producer event, unchanged by host placement. */
|
|
8
|
+
cell: ClientTimelineCellV1;
|
|
9
|
+
}>;
|
|
10
|
+
/** Page visibility follows real durable command anchors, never event time. */
|
|
11
|
+
export declare function placeClientTimeline(entries: readonly ClientContribution[], anchors: ReadonlyMap<string, string>): {
|
|
12
|
+
/** Loaded durable row IDs mapped to their following committed events. */
|
|
13
|
+
after: Map<string, Readonly<{
|
|
14
|
+
/** Namespace of the entry that produced this committed event. */
|
|
15
|
+
entryId: string;
|
|
16
|
+
/** Producer event, unchanged by host placement. */
|
|
17
|
+
cell: ClientTimelineCellV1;
|
|
18
|
+
}>[]>;
|
|
19
|
+
/** Null-anchor events for the separate Extension changes section. */
|
|
20
|
+
unanchored: Readonly<{
|
|
21
|
+
/** Namespace of the entry that produced this committed event. */
|
|
22
|
+
entryId: string;
|
|
23
|
+
/** Producer event, unchanged by host placement. */
|
|
24
|
+
cell: ClientTimelineCellV1;
|
|
25
|
+
}>[];
|
|
26
|
+
/** Factual producer retention counts, independent of loaded message pages. */
|
|
27
|
+
omitted: {
|
|
28
|
+
/** Producer namespace for the retention notice. */
|
|
29
|
+
entryId: string;
|
|
30
|
+
/** Number of committed events omitted by this producer. */
|
|
31
|
+
count: number;
|
|
32
|
+
}[];
|
|
33
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Merge producer heads: clock rollback never reverses a producer's sequence. */
|
|
2
|
+
function mergeCells(cells) {
|
|
3
|
+
const streams = new Map();
|
|
4
|
+
for (const cell of cells) {
|
|
5
|
+
const stream = streams.get(cell.entryId);
|
|
6
|
+
if (stream === undefined)
|
|
7
|
+
streams.set(cell.entryId, [cell]);
|
|
8
|
+
else
|
|
9
|
+
stream.push(cell);
|
|
10
|
+
}
|
|
11
|
+
for (const stream of streams.values())
|
|
12
|
+
stream.sort((left, right) => left.cell.sequence - right.cell.sequence);
|
|
13
|
+
const merged = [];
|
|
14
|
+
while (streams.size > 0) {
|
|
15
|
+
const heads = [...streams.values()].flatMap((stream) => stream[0] === undefined ? [] : [stream[0]]);
|
|
16
|
+
heads.sort((left, right) => left.cell.at - right.cell.at ||
|
|
17
|
+
(left.entryId < right.entryId
|
|
18
|
+
? -1
|
|
19
|
+
: left.entryId > right.entryId
|
|
20
|
+
? 1
|
|
21
|
+
: 0) ||
|
|
22
|
+
left.cell.sequence - right.cell.sequence);
|
|
23
|
+
const next = heads[0];
|
|
24
|
+
if (next === undefined)
|
|
25
|
+
break;
|
|
26
|
+
merged.push(next);
|
|
27
|
+
const stream = streams.get(next.entryId);
|
|
28
|
+
stream?.shift();
|
|
29
|
+
if (stream?.length === 0)
|
|
30
|
+
streams.delete(next.entryId);
|
|
31
|
+
}
|
|
32
|
+
return merged;
|
|
33
|
+
}
|
|
34
|
+
/** Page visibility follows real durable command anchors, never event time. */
|
|
35
|
+
export function placeClientTimeline(entries, anchors) {
|
|
36
|
+
const anchored = new Map();
|
|
37
|
+
const unanchored = [];
|
|
38
|
+
for (const { entryId, value } of entries) {
|
|
39
|
+
for (const cell of value.timeline.cells) {
|
|
40
|
+
const placed = { entryId, cell };
|
|
41
|
+
if (cell.commandId === null) {
|
|
42
|
+
unanchored.push(placed);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const itemId = anchors.get(cell.commandId);
|
|
46
|
+
if (itemId === undefined)
|
|
47
|
+
continue;
|
|
48
|
+
const existing = anchored.get(itemId);
|
|
49
|
+
if (existing === undefined)
|
|
50
|
+
anchored.set(itemId, [placed]);
|
|
51
|
+
else
|
|
52
|
+
existing.push(placed);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
/** Loaded durable row IDs mapped to their following committed events. */
|
|
57
|
+
after: new Map([...anchored].map(([id, cells]) => [id, mergeCells(cells)])),
|
|
58
|
+
/** Null-anchor events for the separate Extension changes section. */
|
|
59
|
+
unanchored: mergeCells(unanchored),
|
|
60
|
+
/** Factual producer retention counts, independent of loaded message pages. */
|
|
61
|
+
omitted: entries.flatMap(({ entryId, value }) => value.timeline.omitted === 0
|
|
62
|
+
? []
|
|
63
|
+
: [
|
|
64
|
+
{
|
|
65
|
+
/** Producer namespace for the retention notice. */
|
|
66
|
+
entryId,
|
|
67
|
+
/** Number of committed events omitted by this producer. */
|
|
68
|
+
count: value.timeline.omitted,
|
|
69
|
+
},
|
|
70
|
+
]),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SessionContext } from "@fungi.computer/shiitake/client";
|
|
2
|
+
/**
|
|
3
|
+
* One token count, compacted for a narrow readout, e.g. `182000` to `182K`.
|
|
4
|
+
* Shared by the context footer and the compaction divider and notices.
|
|
5
|
+
*/
|
|
6
|
+
export declare function formatTokenCount(tokens: number): string;
|
|
7
|
+
/**
|
|
8
|
+
* Context used over the model's window, e.g. `12K/200K`, shared by the
|
|
9
|
+
* browser and terminal footers. An unknown window shows the tokens alone.
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatContextUsage(context: SessionContext): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One token count, compacted for a narrow readout, e.g. `182000` to `182K`.
|
|
3
|
+
* Shared by the context footer and the compaction divider and notices.
|
|
4
|
+
*/
|
|
5
|
+
export function formatTokenCount(tokens) {
|
|
6
|
+
if (tokens < 1_000)
|
|
7
|
+
return String(tokens);
|
|
8
|
+
if (tokens < 1_000_000)
|
|
9
|
+
return `${String(Math.round(tokens / 1_000))}K`;
|
|
10
|
+
return `${String(Math.round(tokens / 100_000) / 10)}M`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Context used over the model's window, e.g. `12K/200K`, shared by the
|
|
14
|
+
* browser and terminal footers. An unknown window shows the tokens alone.
|
|
15
|
+
*/
|
|
16
|
+
export function formatContextUsage(context) {
|
|
17
|
+
return context.window === null
|
|
18
|
+
? formatTokenCount(context.tokens)
|
|
19
|
+
: `${formatTokenCount(context.tokens)}/${formatTokenCount(context.window)}`;
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SessionCompactionMarker, SessionCompactionNotice, SessionFrame, SessionMessage, SessionSnapshot, SessionTerminal } from "@fungi.computer/shiitake/client";
|
|
2
|
+
/** Derive completed durable message identity once per immutable history revision. */
|
|
3
|
+
export declare function completedMessageIds(messages: readonly SessionMessage[]): ReadonlySet<string>;
|
|
4
|
+
export declare function mergeMessages(current: readonly SessionMessage[], incoming: readonly SessionMessage[]): readonly SessionMessage[];
|
|
5
|
+
/** Preserve the first terminal for each command, in observed order. */
|
|
6
|
+
export declare function mergeTerminals(current: readonly SessionTerminal[], incoming: readonly SessionTerminal[]): readonly SessionTerminal[];
|
|
7
|
+
/** Accumulate compaction markers by identity; a frame never removes one. */
|
|
8
|
+
export declare function mergeCompactions(current: readonly SessionCompactionMarker[], incoming: readonly SessionCompactionMarker[]): readonly SessionCompactionMarker[];
|
|
9
|
+
/** Accumulate compaction notices by identity; a frame never removes one. */
|
|
10
|
+
export declare function mergeCompactionNotices(current: readonly SessionCompactionNotice[], incoming: readonly SessionCompactionNotice[]): readonly SessionCompactionNotice[];
|
|
11
|
+
export declare function applySessionFrame(current: SessionSnapshot, frame: SessionFrame): SessionSnapshot;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { sameRunningWork } from "./session-view-state.js";
|
|
2
|
+
const durableMessageIds = new WeakMap();
|
|
3
|
+
/** Derive completed durable message identity once per immutable history revision. */
|
|
4
|
+
export function completedMessageIds(messages) {
|
|
5
|
+
const known = durableMessageIds.get(messages);
|
|
6
|
+
if (known !== undefined)
|
|
7
|
+
return known;
|
|
8
|
+
const ids = new Set(messages.map(({ id }) => id));
|
|
9
|
+
durableMessageIds.set(messages, ids);
|
|
10
|
+
return ids;
|
|
11
|
+
}
|
|
12
|
+
function mergeOrderedUnique(current, incoming) {
|
|
13
|
+
if (incoming.length === 0)
|
|
14
|
+
return current;
|
|
15
|
+
const known = new Set(current.map(({ id }) => id));
|
|
16
|
+
const added = incoming.filter(({ id }) => !known.has(id));
|
|
17
|
+
if (added.length === 0)
|
|
18
|
+
return current;
|
|
19
|
+
return [...current, ...added].sort((left, right) => left.sequence - right.sequence);
|
|
20
|
+
}
|
|
21
|
+
export function mergeMessages(current, incoming) {
|
|
22
|
+
return mergeOrderedUnique(current, incoming);
|
|
23
|
+
}
|
|
24
|
+
function retainsProgress(progress, messages) {
|
|
25
|
+
if (progress === null)
|
|
26
|
+
return null;
|
|
27
|
+
return completedMessageIds(messages).has(progress.messageId)
|
|
28
|
+
? null
|
|
29
|
+
: progress;
|
|
30
|
+
}
|
|
31
|
+
/** Preserve the first terminal for each command, in observed order. */
|
|
32
|
+
export function mergeTerminals(current, incoming) {
|
|
33
|
+
const merged = [];
|
|
34
|
+
const positions = new Map();
|
|
35
|
+
for (const terminal of [...current, ...incoming]) {
|
|
36
|
+
const position = positions.get(terminal.commandId);
|
|
37
|
+
if (position === undefined) {
|
|
38
|
+
positions.set(terminal.commandId, merged.length);
|
|
39
|
+
merged.push(terminal);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (merged.length === current.length &&
|
|
43
|
+
merged.every((terminal, index) => terminal === current[index]))
|
|
44
|
+
return current;
|
|
45
|
+
return merged;
|
|
46
|
+
}
|
|
47
|
+
/** Accumulate compaction markers by identity; a frame never removes one. */
|
|
48
|
+
export function mergeCompactions(current, incoming) {
|
|
49
|
+
return mergeOrderedUnique(current, incoming);
|
|
50
|
+
}
|
|
51
|
+
/** Accumulate compaction notices by identity; a frame never removes one. */
|
|
52
|
+
export function mergeCompactionNotices(current, incoming) {
|
|
53
|
+
return mergeOrderedUnique(current, incoming);
|
|
54
|
+
}
|
|
55
|
+
function sameAttention(left, right) {
|
|
56
|
+
return (left.length === right.length &&
|
|
57
|
+
left.every((item, index) => {
|
|
58
|
+
const other = right[index];
|
|
59
|
+
return (item.extensionId === other?.extensionId &&
|
|
60
|
+
item.id === other.id &&
|
|
61
|
+
item.kind === other.kind &&
|
|
62
|
+
item.summary === other.summary &&
|
|
63
|
+
item.since === other.since &&
|
|
64
|
+
item.expiresAt === other.expiresAt);
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
function samePending(left, right) {
|
|
68
|
+
return (left.total === right.total &&
|
|
69
|
+
left.hasMore === right.hasMore &&
|
|
70
|
+
left.prompts.length === right.prompts.length &&
|
|
71
|
+
left.prompts.every((prompt, index) => {
|
|
72
|
+
const rightPrompt = right.prompts[index];
|
|
73
|
+
if (rightPrompt === undefined)
|
|
74
|
+
return false;
|
|
75
|
+
return (prompt.commandId === rightPrompt.commandId &&
|
|
76
|
+
prompt.delivery === rightPrompt.delivery &&
|
|
77
|
+
prompt.text === rightPrompt.text);
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
/** Activity facts are a few scalars; compare them by content. */
|
|
81
|
+
function sameActivity(left, right) {
|
|
82
|
+
return (left === right ||
|
|
83
|
+
(left.queue.state === right.queue.state &&
|
|
84
|
+
(left.queue.state === "idle" ||
|
|
85
|
+
right.queue.state === "idle" ||
|
|
86
|
+
(left.queue.commandId === right.queue.commandId &&
|
|
87
|
+
(left.queue.state !== "queued" ||
|
|
88
|
+
right.queue.state !== "queued" ||
|
|
89
|
+
sameRunningWork(left.queue.runningWork, right.queue.runningWork)))) &&
|
|
90
|
+
left.cancellationRequested === right.cancellationRequested &&
|
|
91
|
+
left.activeTool === right.activeTool &&
|
|
92
|
+
left.lastModelChoiceSequence === right.lastModelChoiceSequence &&
|
|
93
|
+
(left.retry === right.retry ||
|
|
94
|
+
(left.retry !== null &&
|
|
95
|
+
right.retry !== null &&
|
|
96
|
+
left.retry.attempt === right.retry.attempt &&
|
|
97
|
+
left.retry.maxAttempts === right.retry.maxAttempts &&
|
|
98
|
+
left.retry.delayMs === right.retry.delayMs &&
|
|
99
|
+
left.retry.errorMessage === right.retry.errorMessage &&
|
|
100
|
+
left.retry.scheduledAt === right.retry.scheduledAt)) &&
|
|
101
|
+
(left.compaction === right.compaction ||
|
|
102
|
+
(left.compaction !== null &&
|
|
103
|
+
right.compaction !== null &&
|
|
104
|
+
left.compaction.trigger === right.compaction.trigger))));
|
|
105
|
+
}
|
|
106
|
+
/** Apply one already-decoded Shiitake frame to its authoritative projection. */
|
|
107
|
+
/** Extension slots are small JSON values; compare them by content. */
|
|
108
|
+
function sameExtensionSlots(next, current) {
|
|
109
|
+
return next === current || JSON.stringify(next) === JSON.stringify(current);
|
|
110
|
+
}
|
|
111
|
+
export function applySessionFrame(current, frame) {
|
|
112
|
+
if (frame.kind === "reset")
|
|
113
|
+
return {
|
|
114
|
+
...frame.baseline,
|
|
115
|
+
terminals: mergeTerminals([], frame.baseline.terminals),
|
|
116
|
+
compactions: mergeCompactions([], frame.baseline.compactions ?? []),
|
|
117
|
+
compactionNotices: mergeCompactionNotices([], frame.baseline.compactionNotices ?? []),
|
|
118
|
+
};
|
|
119
|
+
if (frame.kind === "progress") {
|
|
120
|
+
const completed = completedMessageIds(current.messages).has(frame.progress.messageId);
|
|
121
|
+
const currentProgress = current.progress;
|
|
122
|
+
const sameMessage = currentProgress !== null &&
|
|
123
|
+
currentProgress.messageId === frame.progress.messageId;
|
|
124
|
+
const older = sameMessage && currentProgress.revision > frame.progress.revision;
|
|
125
|
+
if (completed || older)
|
|
126
|
+
return current;
|
|
127
|
+
const activity = sameActivity(current.activity, frame.activity)
|
|
128
|
+
? current.activity
|
|
129
|
+
: frame.activity;
|
|
130
|
+
// A progress observation can carry new owner facts even when its text
|
|
131
|
+
// revision did not change, such as the end of a retry backoff.
|
|
132
|
+
if (sameMessage && currentProgress.revision === frame.progress.revision)
|
|
133
|
+
return activity === current.activity ? current : { ...current, activity };
|
|
134
|
+
return {
|
|
135
|
+
...current,
|
|
136
|
+
progress: frame.progress,
|
|
137
|
+
activity,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const pending = samePending(current.pending, frame.pending)
|
|
141
|
+
? current.pending
|
|
142
|
+
: frame.pending;
|
|
143
|
+
const messages = mergeMessages(current.messages, frame.messages);
|
|
144
|
+
const terminals = mergeTerminals(current.terminals, frame.terminals);
|
|
145
|
+
const currentCompactions = current.compactions ?? [];
|
|
146
|
+
const compactions = mergeCompactions(currentCompactions, frame.compactions ?? []);
|
|
147
|
+
const currentCompactionNotices = current.compactionNotices ?? [];
|
|
148
|
+
const compactionNotices = mergeCompactionNotices(currentCompactionNotices, frame.compactionNotices ?? []);
|
|
149
|
+
const progress = frame.activity.queue.state === "idle"
|
|
150
|
+
? null
|
|
151
|
+
: retainsProgress(current.progress, messages);
|
|
152
|
+
// An update without a context figure leaves the last one in place.
|
|
153
|
+
const context = frame.context === undefined ||
|
|
154
|
+
(current.context?.tokens === frame.context.tokens &&
|
|
155
|
+
current.context.window === frame.context.window)
|
|
156
|
+
? current.context
|
|
157
|
+
: frame.context;
|
|
158
|
+
if (messages === current.messages &&
|
|
159
|
+
terminals === current.terminals &&
|
|
160
|
+
compactions === currentCompactions &&
|
|
161
|
+
compactionNotices === currentCompactionNotices &&
|
|
162
|
+
progress === current.progress &&
|
|
163
|
+
pending === current.pending &&
|
|
164
|
+
sameExtensionSlots(frame.extensions, current.extensions) &&
|
|
165
|
+
sameAttention(frame.attention, current.attention) &&
|
|
166
|
+
frame.cursor === current.cursor &&
|
|
167
|
+
sameActivity(frame.activity, current.activity) &&
|
|
168
|
+
context === current.context)
|
|
169
|
+
return current;
|
|
170
|
+
const next = {
|
|
171
|
+
...current,
|
|
172
|
+
cursor: frame.cursor,
|
|
173
|
+
messages,
|
|
174
|
+
terminals,
|
|
175
|
+
compactions,
|
|
176
|
+
compactionNotices,
|
|
177
|
+
progress,
|
|
178
|
+
activity: sameActivity(current.activity, frame.activity)
|
|
179
|
+
? current.activity
|
|
180
|
+
: frame.activity,
|
|
181
|
+
pending,
|
|
182
|
+
attention: sameAttention(frame.attention, current.attention)
|
|
183
|
+
? current.attention
|
|
184
|
+
: frame.attention,
|
|
185
|
+
extensions: sameExtensionSlots(frame.extensions, current.extensions)
|
|
186
|
+
? current.extensions
|
|
187
|
+
: frame.extensions,
|
|
188
|
+
};
|
|
189
|
+
// Undefined only while neither the snapshot nor the frame reported one.
|
|
190
|
+
return context === undefined ? next : { ...next, context };
|
|
191
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { SessionSnapshot } from "@fungi.computer/shiitake/client";
|
|
2
|
+
import { type QueryClient } from "@tanstack/react-query";
|
|
3
|
+
import type { ParakeetSession } from "../session.js";
|
|
4
|
+
/** One shared TanStack observation keyed by Shiitake's logical Session address. */
|
|
5
|
+
export declare function sessionQueryOptions(session: ParakeetSession): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<Readonly<{
|
|
6
|
+
/** Whether the current watch has supplied a fresh owner baseline. */
|
|
7
|
+
observation: "connecting" | "live";
|
|
8
|
+
/** Last coherent durable Session state, absent before the first snapshot. */
|
|
9
|
+
snapshot: SessionSnapshot | undefined;
|
|
10
|
+
/** One retryable user prompt survives live-page refreshes. */
|
|
11
|
+
latestPrompt: readonly [commandId: string, text: string] | undefined;
|
|
12
|
+
}>, Error, Readonly<{
|
|
13
|
+
/** Whether the current watch has supplied a fresh owner baseline. */
|
|
14
|
+
observation: "connecting" | "live";
|
|
15
|
+
/** Last coherent durable Session state, absent before the first snapshot. */
|
|
16
|
+
snapshot: SessionSnapshot | undefined;
|
|
17
|
+
/** One retryable user prompt survives live-page refreshes. */
|
|
18
|
+
latestPrompt: readonly [commandId: string, text: string] | undefined;
|
|
19
|
+
}>, readonly ["shiitake-session", string]>, "queryFn"> & {
|
|
20
|
+
queryFn?: import("@tanstack/react-query").QueryFunction<Readonly<{
|
|
21
|
+
/** Whether the current watch has supplied a fresh owner baseline. */
|
|
22
|
+
observation: "connecting" | "live";
|
|
23
|
+
/** Last coherent durable Session state, absent before the first snapshot. */
|
|
24
|
+
snapshot: SessionSnapshot | undefined;
|
|
25
|
+
/** One retryable user prompt survives live-page refreshes. */
|
|
26
|
+
latestPrompt: readonly [commandId: string, text: string] | undefined;
|
|
27
|
+
}>, readonly ["shiitake-session", string], never>;
|
|
28
|
+
} & import("@tanstack/react-query").QueryKeyWithDataTag<readonly ["shiitake-session", string], Readonly<{
|
|
29
|
+
/** Whether the current watch has supplied a fresh owner baseline. */
|
|
30
|
+
observation: "connecting" | "live";
|
|
31
|
+
/** Last coherent durable Session state, absent before the first snapshot. */
|
|
32
|
+
snapshot: SessionSnapshot | undefined;
|
|
33
|
+
/** One retryable user prompt survives live-page refreshes. */
|
|
34
|
+
latestPrompt: readonly [commandId: string, text: string] | undefined;
|
|
35
|
+
}>, Error>;
|
|
36
|
+
/** Seed the shared observation with a bounded read before its watch mounts. */
|
|
37
|
+
export declare function seedSessionQuery(queryClient: QueryClient, session: ParakeetSession, snapshot: SessionSnapshot): void;
|
|
38
|
+
/** Borrow activity from the existing cache without starting another watch. */
|
|
39
|
+
export declare function subscribeSessionActivity(queryClient: QueryClient, session: ParakeetSession, listener: (activity: "idle" | "working" | "unknown") => void): () => void;
|
|
40
|
+
/** Shared, demand-loaded history; the live watch remains a separate small tail. */
|
|
41
|
+
export declare function sessionHistoryQueryOptions(session: ParakeetSession, before?: string | null): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseInfiniteQueryOptions<Readonly<{
|
|
42
|
+
sessionId: string;
|
|
43
|
+
cursor: string | null;
|
|
44
|
+
hasMore: boolean;
|
|
45
|
+
oldestCursor?: string | null;
|
|
46
|
+
messages: readonly import("@fungi.computer/shiitake/client").SessionMessage[];
|
|
47
|
+
terminals: readonly import("@fungi.computer/shiitake/client").SessionTerminal[];
|
|
48
|
+
compactions?: readonly import("@fungi.computer/shiitake/client").SessionCompactionMarker[];
|
|
49
|
+
compactionNotices?: readonly import("@fungi.computer/shiitake/client").SessionCompactionNotice[];
|
|
50
|
+
sandboxFailures?: readonly import("@fungi.computer/mycelium").SandboxFailureEvent[];
|
|
51
|
+
pending: import("@fungi.computer/shiitake/client").SessionPendingPrompts;
|
|
52
|
+
activity: import("@fungi.computer/shiitake/client").SessionActivity;
|
|
53
|
+
progress: import("@fungi.computer/shiitake/client").SessionProgress | null;
|
|
54
|
+
extensions: import("@fungi.computer/shiitake/client").SessionExtensionSlots;
|
|
55
|
+
attention: readonly import("@fungi.computer/shiitake/client").SessionAttention[];
|
|
56
|
+
context?: import("@fungi.computer/shiitake/client").SessionContext;
|
|
57
|
+
}>, Error, {
|
|
58
|
+
pages: Readonly<{
|
|
59
|
+
sessionId: string;
|
|
60
|
+
cursor: string | null;
|
|
61
|
+
hasMore: boolean;
|
|
62
|
+
oldestCursor?: string | null;
|
|
63
|
+
messages: readonly import("@fungi.computer/shiitake/client").SessionMessage[];
|
|
64
|
+
terminals: readonly import("@fungi.computer/shiitake/client").SessionTerminal[];
|
|
65
|
+
compactions?: readonly import("@fungi.computer/shiitake/client").SessionCompactionMarker[];
|
|
66
|
+
compactionNotices?: readonly import("@fungi.computer/shiitake/client").SessionCompactionNotice[];
|
|
67
|
+
sandboxFailures?: readonly import("@fungi.computer/mycelium").SandboxFailureEvent[];
|
|
68
|
+
pending: import("@fungi.computer/shiitake/client").SessionPendingPrompts;
|
|
69
|
+
activity: import("@fungi.computer/shiitake/client").SessionActivity;
|
|
70
|
+
progress: import("@fungi.computer/shiitake/client").SessionProgress | null;
|
|
71
|
+
extensions: import("@fungi.computer/shiitake/client").SessionExtensionSlots;
|
|
72
|
+
attention: readonly import("@fungi.computer/shiitake/client").SessionAttention[];
|
|
73
|
+
context?: import("@fungi.computer/shiitake/client").SessionContext;
|
|
74
|
+
}>[];
|
|
75
|
+
newerOverflow: boolean;
|
|
76
|
+
}, string[], Readonly<{
|
|
77
|
+
before: string | null;
|
|
78
|
+
newerBefore: readonly string[];
|
|
79
|
+
newerOverflow: boolean;
|
|
80
|
+
}>>, "queryFn"> & {
|
|
81
|
+
queryFn?: import("@tanstack/react-query").QueryFunction<Readonly<{
|
|
82
|
+
sessionId: string;
|
|
83
|
+
cursor: string | null;
|
|
84
|
+
hasMore: boolean;
|
|
85
|
+
oldestCursor?: string | null;
|
|
86
|
+
messages: readonly import("@fungi.computer/shiitake/client").SessionMessage[];
|
|
87
|
+
terminals: readonly import("@fungi.computer/shiitake/client").SessionTerminal[];
|
|
88
|
+
compactions?: readonly import("@fungi.computer/shiitake/client").SessionCompactionMarker[];
|
|
89
|
+
compactionNotices?: readonly import("@fungi.computer/shiitake/client").SessionCompactionNotice[];
|
|
90
|
+
sandboxFailures?: readonly import("@fungi.computer/mycelium").SandboxFailureEvent[];
|
|
91
|
+
pending: import("@fungi.computer/shiitake/client").SessionPendingPrompts;
|
|
92
|
+
activity: import("@fungi.computer/shiitake/client").SessionActivity;
|
|
93
|
+
progress: import("@fungi.computer/shiitake/client").SessionProgress | null;
|
|
94
|
+
extensions: import("@fungi.computer/shiitake/client").SessionExtensionSlots;
|
|
95
|
+
attention: readonly import("@fungi.computer/shiitake/client").SessionAttention[];
|
|
96
|
+
context?: import("@fungi.computer/shiitake/client").SessionContext;
|
|
97
|
+
}>, string[], Readonly<{
|
|
98
|
+
before: string | null;
|
|
99
|
+
newerBefore: readonly string[];
|
|
100
|
+
newerOverflow: boolean;
|
|
101
|
+
}>>;
|
|
102
|
+
} & import("@tanstack/react-query").QueryKeyWithDataTag<string[], import("@tanstack/react-query").InfiniteData<Readonly<{
|
|
103
|
+
sessionId: string;
|
|
104
|
+
cursor: string | null;
|
|
105
|
+
hasMore: boolean;
|
|
106
|
+
oldestCursor?: string | null;
|
|
107
|
+
messages: readonly import("@fungi.computer/shiitake/client").SessionMessage[];
|
|
108
|
+
terminals: readonly import("@fungi.computer/shiitake/client").SessionTerminal[];
|
|
109
|
+
compactions?: readonly import("@fungi.computer/shiitake/client").SessionCompactionMarker[];
|
|
110
|
+
compactionNotices?: readonly import("@fungi.computer/shiitake/client").SessionCompactionNotice[];
|
|
111
|
+
sandboxFailures?: readonly import("@fungi.computer/mycelium").SandboxFailureEvent[];
|
|
112
|
+
pending: import("@fungi.computer/shiitake/client").SessionPendingPrompts;
|
|
113
|
+
activity: import("@fungi.computer/shiitake/client").SessionActivity;
|
|
114
|
+
progress: import("@fungi.computer/shiitake/client").SessionProgress | null;
|
|
115
|
+
extensions: import("@fungi.computer/shiitake/client").SessionExtensionSlots;
|
|
116
|
+
attention: readonly import("@fungi.computer/shiitake/client").SessionAttention[];
|
|
117
|
+
context?: import("@fungi.computer/shiitake/client").SessionContext;
|
|
118
|
+
}>, unknown>, Error>;
|