@fungi.computer/parakeet 0.1.0 → 0.2.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/README.md +45 -6
- package/dist/account-commands.js +3 -11
- package/dist/commands/advertised.d.ts +3 -1
- package/dist/commands/advertised.js +89 -4
- package/dist/commands/interactions.d.ts +22 -7
- package/dist/commands/interactions.js +41 -17
- package/dist/commands/slash.js +3 -5
- package/dist/commands/use-slash-commands.js +2 -2
- package/dist/components/AgentHead.d.ts +2 -0
- package/dist/components/AgentHead.js +5 -4
- package/dist/components/ChatComposer.d.ts +3 -1
- package/dist/components/ChatComposer.js +79 -15
- package/dist/components/ChatMessage.d.ts +4 -0
- package/dist/components/ChatMessage.js +6 -2
- package/dist/components/ClientRows.js +2 -2
- package/dist/components/InteractionPanel.d.ts +2 -4
- package/dist/components/InteractionPanel.js +2 -2
- package/dist/components/TimelineItem.d.ts +2 -0
- package/dist/components/TimelineItem.js +31 -7
- package/dist/conversation-opening.d.ts +47 -0
- package/dist/conversation-opening.js +173 -0
- package/dist/conversation.d.ts +47 -3
- package/dist/conversation.js +584 -86
- package/dist/core/author-labels.d.ts +2 -0
- package/dist/core/build-timeline.d.ts +4 -2
- package/dist/core/build-timeline.js +46 -7
- package/dist/core/client-actions.js +6 -3
- package/dist/core/client-timeline.d.ts +9 -2
- package/dist/core/client-timeline.js +15 -4
- package/dist/core/draft-storage.d.ts +63 -0
- package/dist/core/draft-storage.js +50 -0
- package/dist/core/model-identity.d.ts +2 -0
- package/dist/core/model-identity.js +6 -0
- package/dist/core/queued-recall.d.ts +35 -0
- package/dist/core/queued-recall.js +138 -0
- package/dist/core/session-projection.js +17 -0
- package/dist/core/session-query.d.ts +54 -11
- package/dist/core/session-query.js +38 -12
- package/dist/core/tool-groups.d.ts +1 -1
- package/dist/core/tool-groups.js +3 -1
- package/dist/core/tool-view.js +2 -1
- package/dist/core/use-timeline-item-key.d.ts +8 -0
- package/dist/core/use-timeline-item-key.js +29 -0
- package/dist/headless.d.ts +6 -4
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/model-commands.js +64 -39
- package/dist/parakeet.js +17 -18
- package/dist/session-commands.d.ts +0 -9
- package/dist/session-commands.js +39 -68
- package/dist/session.d.ts +1 -1
- package/dist/store/types.d.ts +10 -4
- package/dist/styles.css +27 -12
- package/docs/api/commands/slash.md +24 -21
- package/docs/api/headless.md +375 -232
- package/docs/api/index.md +157 -45
- package/package.json +16 -10
- package/src/styles.css +0 -9
package/README.md
CHANGED
|
@@ -126,17 +126,56 @@ It owns parsing, completion, stable request identities and interaction state.
|
|
|
126
126
|
The browser and terminal render the same questions, choices and notices.
|
|
127
127
|
`/model` and `/thinking` use that interaction path.
|
|
128
128
|
|
|
129
|
+
`ParakeetProvider` accepts `onToast` for transient command information. The host
|
|
130
|
+
renders it through its existing toast owner: the terminal's shared toast
|
|
131
|
+
viewport or Grove's `ui.showToast`. `ParakeetInteractions.toast` and
|
|
132
|
+
`subscribeToasts` expose the same renderer-neutral seam. Ordinary first-success
|
|
133
|
+
echoes are silent, including after a dialog completes. Progress, questions and
|
|
134
|
+
handoffs remain in their interaction while active; failures retain their retry
|
|
135
|
+
or attention UI rather than expiring as information.
|
|
136
|
+
|
|
137
|
+
After Try again, a final information or success notice stays in that
|
|
138
|
+
interaction's existing result row until Done/close, without an extra toast. A
|
|
139
|
+
producer that supplies no completion notice does not gain a fabricated result.
|
|
140
|
+
|
|
129
141
|
Account commands are provider-neutral. The host's account owner and policy
|
|
130
142
|
decide which login methods are available. The presentation layer does not gain a
|
|
131
143
|
credential store or make an advertised command authorized.
|
|
132
144
|
|
|
145
|
+
Queued timeline rows offer Cancel through
|
|
146
|
+
`useTimeline().cancelQueued(commandId)`. The row disappears immediately while
|
|
147
|
+
the existing Session owner decides the pending-only cancellation. This never
|
|
148
|
+
stops the running turn or restores text to the composer. TooLate or an
|
|
149
|
+
unconfirmed response produces a warning and restores any row still reported as
|
|
150
|
+
queued; an accepted response stays hidden through stale queued snapshots until
|
|
151
|
+
the owner projection catches up. Only one queued gesture waits for its owner at
|
|
152
|
+
a time, and switching conversations discards its presentation state. Recall
|
|
153
|
+
remains a separate restore-to-composer gesture.
|
|
154
|
+
|
|
133
155
|
The composer reconciles optimistic prompts with owner facts by request identity.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
156
|
+
Submitting clears the draft immediately and adds a faded pending message.
|
|
157
|
+
Acceptance restores normal emphasis; owner-queued messages remain faded. If
|
|
158
|
+
admission fails, the exact submitted text returns to the composer and the host
|
|
159
|
+
receives an error toast. Any newer draft stays first, followed by a blank line
|
|
160
|
+
and the restored text; acceptance never clears newer typing. An uncertain
|
|
161
|
+
admission retains its request identity while the owner resolves it. An accepted
|
|
162
|
+
resolution confirms the admitted command or recorded entry; only confirmed
|
|
163
|
+
withdrawal restores the draft and permits retry with a new request identity,
|
|
164
|
+
because the old identity is durably fenced. Retrying failed or interrupted
|
|
165
|
+
durable work also submits a new request identity, preserving the failed prompt's
|
|
166
|
+
text and image cargo, including image-only prompts after live-page eviction. It
|
|
167
|
+
does not consume a newer unsent draft. Failed observation shows connection
|
|
168
|
+
failure; reconnecting does not prove pending work failed or resubmit it
|
|
169
|
+
automatically. Typed command errors remain visible and retryable. The host
|
|
170
|
+
remains responsible for the Session transport, current permissions and operator
|
|
171
|
+
diagnostics.
|
|
172
|
+
|
|
173
|
+
An owner's accepted withdrawal receipt can name a run-free recorded entry:
|
|
174
|
+
`receipt.commandId === message.id` even though `message.commandId === null`.
|
|
175
|
+
That receipt confirms durable recording, not cancellation or model admission. A
|
|
176
|
+
translating host retains the original composer identity in its durable metadata
|
|
177
|
+
and projects it as `messages[].requestId` or `pending.prompts[].requestId`; its
|
|
178
|
+
internal admission key is not that identity.
|
|
140
179
|
|
|
141
180
|
## API and examples
|
|
142
181
|
|
package/dist/account-commands.js
CHANGED
|
@@ -91,17 +91,13 @@ export async function signInProvider(owner, provider, interaction, request = {})
|
|
|
91
91
|
type: "progress",
|
|
92
92
|
message: `Signing in to ${provider.name}…`,
|
|
93
93
|
});
|
|
94
|
-
await owner.login(request.option === undefined
|
|
94
|
+
await interaction.run(() => owner.login(request.option === undefined
|
|
95
95
|
? { providerId: provider.id, method: method.kind }
|
|
96
96
|
: {
|
|
97
97
|
providerId: provider.id,
|
|
98
98
|
method: method.kind,
|
|
99
99
|
option: request.option,
|
|
100
|
-
}, interaction);
|
|
101
|
-
interaction.notify({
|
|
102
|
-
type: "success",
|
|
103
|
-
message: `Signed in to ${provider.name}.`,
|
|
104
|
-
});
|
|
100
|
+
}, interaction));
|
|
105
101
|
}
|
|
106
102
|
async function signOutProvider(owner, provider, interaction) {
|
|
107
103
|
const confirmed = await interaction.prompt({
|
|
@@ -112,11 +108,7 @@ async function signOutProvider(owner, provider, interaction) {
|
|
|
112
108
|
});
|
|
113
109
|
if (!confirmed)
|
|
114
110
|
return;
|
|
115
|
-
await owner.logout(provider.id, interaction);
|
|
116
|
-
interaction.notify({
|
|
117
|
-
type: "success",
|
|
118
|
-
message: `Signed out of ${provider.name}.`,
|
|
119
|
-
});
|
|
111
|
+
await interaction.run(() => owner.logout(provider.id, interaction));
|
|
120
112
|
}
|
|
121
113
|
/** Cancelling a sign-in is an ordinary ending, not a failure. */
|
|
122
114
|
async function unlessCancelled(interaction, work) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SessionExtensionSlots } from "@fungi.computer/shiitake/client";
|
|
2
2
|
import type { WhistleRuntime } from "@fungi.computer/whistle";
|
|
3
3
|
import type { ParakeetSession } from "../session.js";
|
|
4
|
+
import type { ClientContribution } from "../core/client-contributions.js";
|
|
4
5
|
/** Why one advertised catalog could not become commands. */
|
|
5
6
|
export type AdvertisedCatalogError = Readonly<{
|
|
6
7
|
/** The extension that advertised it. */
|
|
@@ -19,5 +20,6 @@ export type AdvertisedCommands = Readonly<{
|
|
|
19
20
|
* Turn each running extension's advertised `whistle.catalog/v1` catalog into
|
|
20
21
|
* Whistle commands that call that extension's own door. The client names no
|
|
21
22
|
* command: a new extension's catalog appears as new commands.
|
|
23
|
+
* @param contributions - Current admitted panels that own unanswered prompts.
|
|
22
24
|
*/
|
|
23
|
-
export declare function advertisedCommands(runtime: Pick<WhistleRuntime, "contribute">, session: Pick<ParakeetSession, "extension">, onInspect?: (entryId: string, commandId: string) => void): AdvertisedCommands;
|
|
25
|
+
export declare function advertisedCommands(runtime: Pick<WhistleRuntime, "contribute">, session: Pick<ParakeetSession, "extension">, contributions: () => readonly ClientContribution[], onInspect?: (entryId: string, commandId: string) => void): AdvertisedCommands;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as Schema from "effect/Schema";
|
|
2
|
-
import { catalogCommands, parseCatalog } from "@fungi.computer/whistle/catalog";
|
|
2
|
+
import { catalogCommands, parseCatalog, parseCatalogStep, } from "@fungi.computer/whistle/catalog";
|
|
3
3
|
const cancelled = Schema.is(Schema.Struct({ cancelled: Schema.Literal(true) }));
|
|
4
4
|
/** A bare invocation views; one carrying action arguments (`/plan clear`) acts. */
|
|
5
5
|
const bare = Schema.is(Schema.UndefinedOr(Schema.Record(Schema.String, Schema.Never)));
|
|
@@ -7,9 +7,28 @@ const bare = Schema.is(Schema.UndefinedOr(Schema.Record(Schema.String, Schema.Ne
|
|
|
7
7
|
* Turn each running extension's advertised `whistle.catalog/v1` catalog into
|
|
8
8
|
* Whistle commands that call that extension's own door. The client names no
|
|
9
9
|
* command: a new extension's catalog appears as new commands.
|
|
10
|
+
* @param contributions - Current admitted panels that own unanswered prompts.
|
|
10
11
|
*/
|
|
11
|
-
export function advertisedCommands(runtime, session, onInspect) {
|
|
12
|
+
export function advertisedCommands(runtime, session, contributions, onInspect) {
|
|
12
13
|
const live = new Map();
|
|
14
|
+
const panelActions = (id, command) => contributions()
|
|
15
|
+
.find((entry) => entry.entryId === id)
|
|
16
|
+
?.value.panel.flatMap((row) => row.actions ?? [])
|
|
17
|
+
.filter((action) => action.id === command) ?? [];
|
|
18
|
+
const intent = (action) => JSON.stringify([action.id, action.input ?? {}]);
|
|
19
|
+
// A prompt borrows its panel intent, not the entire backend operation.
|
|
20
|
+
// Once answered, its pending response must retain its ordinary lifetime.
|
|
21
|
+
const prompts = new Set();
|
|
22
|
+
const refreshPrompts = () => {
|
|
23
|
+
for (const prompt of prompts) {
|
|
24
|
+
const visible = new Set(panelActions(prompt.entryId, prompt.commandId).map(intent));
|
|
25
|
+
if (live.has(prompt.entryId) &&
|
|
26
|
+
prompt.intents.every((id) => visible.has(id)))
|
|
27
|
+
continue;
|
|
28
|
+
prompt.execution.withdrawn = true;
|
|
29
|
+
prompt.controller.abort();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
13
32
|
const withdraw = (id) => {
|
|
14
33
|
live.get(id)?.lease.dispose();
|
|
15
34
|
live.delete(id);
|
|
@@ -31,7 +50,8 @@ export function advertisedCommands(runtime, session, onInspect) {
|
|
|
31
50
|
withdraw(id);
|
|
32
51
|
try {
|
|
33
52
|
const address = session.extension(id);
|
|
34
|
-
const
|
|
53
|
+
const catalog = parseCatalog(slot.catalog);
|
|
54
|
+
const commands = catalogCommands(catalog, (command, args) => address.call(command, args));
|
|
35
55
|
live.set(id, {
|
|
36
56
|
catalog: encoded,
|
|
37
57
|
lease: runtime.contribute({
|
|
@@ -40,7 +60,70 @@ export function advertisedCommands(runtime, session, onInspect) {
|
|
|
40
60
|
commands: commands.map((command) => ({
|
|
41
61
|
...command,
|
|
42
62
|
handler: async (input) => {
|
|
43
|
-
const
|
|
63
|
+
const actions = panelActions(id, command.id);
|
|
64
|
+
const execution = {
|
|
65
|
+
intents: actions.map(intent),
|
|
66
|
+
withdrawn: false,
|
|
67
|
+
};
|
|
68
|
+
// Invocation-local closures retain admitted identities only.
|
|
69
|
+
// A caller may share its signal and pass secret arguments.
|
|
70
|
+
const scopedCommand = catalogCommands(catalog, async (name, args) => {
|
|
71
|
+
const reply = await address.call(name, args);
|
|
72
|
+
const step = parseCatalogStep(reply);
|
|
73
|
+
if (step.type === "prompt") {
|
|
74
|
+
const selected = actions.filter((action) => Object.entries(action.input ?? {}).every(([key, value]) => JSON.stringify(Object.hasOwn(step.next.arguments, key)
|
|
75
|
+
? step.next.arguments[key]
|
|
76
|
+
: args[key]) === JSON.stringify(value)));
|
|
77
|
+
// Before selection, a chooser borrows all its candidates.
|
|
78
|
+
execution.intents = (selected.length > 0 ? selected : actions).map(intent);
|
|
79
|
+
}
|
|
80
|
+
return reply;
|
|
81
|
+
}).find((candidate) => candidate.id === command.id);
|
|
82
|
+
if (scopedCommand === undefined)
|
|
83
|
+
throw new Error("The advertised command is unavailable");
|
|
84
|
+
let scoped = input;
|
|
85
|
+
const interaction = input.interaction;
|
|
86
|
+
if (interaction !== undefined) {
|
|
87
|
+
const bound = {
|
|
88
|
+
...interaction,
|
|
89
|
+
prompt: (request, options) => {
|
|
90
|
+
const prompt = {
|
|
91
|
+
entryId: id,
|
|
92
|
+
commandId: command.id,
|
|
93
|
+
intents: execution.intents,
|
|
94
|
+
execution,
|
|
95
|
+
controller: new AbortController(),
|
|
96
|
+
};
|
|
97
|
+
prompts.add(prompt);
|
|
98
|
+
refreshPrompts();
|
|
99
|
+
return interaction
|
|
100
|
+
.prompt(request, {
|
|
101
|
+
signal: options?.signal === undefined
|
|
102
|
+
? prompt.controller.signal
|
|
103
|
+
: AbortSignal.any([
|
|
104
|
+
options.signal,
|
|
105
|
+
prompt.controller.signal,
|
|
106
|
+
]),
|
|
107
|
+
})
|
|
108
|
+
.finally(() => prompts.delete(prompt));
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
scoped = { ...scoped, interaction: bound };
|
|
112
|
+
}
|
|
113
|
+
let result;
|
|
114
|
+
try {
|
|
115
|
+
result = await scopedCommand.handler(scoped);
|
|
116
|
+
}
|
|
117
|
+
catch (cause) {
|
|
118
|
+
if (execution.withdrawn)
|
|
119
|
+
return { cancelled: true };
|
|
120
|
+
throw cause;
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
for (const prompt of prompts)
|
|
124
|
+
if (prompt.execution === execution)
|
|
125
|
+
prompts.delete(prompt);
|
|
126
|
+
}
|
|
44
127
|
if (input.origin !== "agent" &&
|
|
45
128
|
bare(input.arguments) &&
|
|
46
129
|
!cancelled(result) &&
|
|
@@ -62,11 +145,13 @@ export function advertisedCommands(runtime, session, onInspect) {
|
|
|
62
145
|
});
|
|
63
146
|
}
|
|
64
147
|
}
|
|
148
|
+
refreshPrompts();
|
|
65
149
|
return errors;
|
|
66
150
|
},
|
|
67
151
|
dispose: () => {
|
|
68
152
|
for (const id of live.keys())
|
|
69
153
|
withdraw(id);
|
|
154
|
+
refreshPrompts();
|
|
70
155
|
},
|
|
71
156
|
};
|
|
72
157
|
}
|
|
@@ -6,13 +6,24 @@ export type ParakeetOpenInteraction = Readonly<{
|
|
|
6
6
|
key: number;
|
|
7
7
|
/** The renderer-neutral interaction store to draw. */
|
|
8
8
|
host: WhistleInteractionHost;
|
|
9
|
-
/** Run the same command again with the same input. */
|
|
10
|
-
retry: () => void;
|
|
11
9
|
}>;
|
|
12
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* An attention item shown beside the composer until it is resolved: something
|
|
12
|
+
* failed or waits on the person. Everything else is a toast.
|
|
13
|
+
*/
|
|
13
14
|
export type ParakeetNotice = Readonly<{
|
|
14
15
|
/** How to color it. */
|
|
15
|
-
tone: "info" | "
|
|
16
|
+
tone: "info" | "error";
|
|
17
|
+
/** What happened. */
|
|
18
|
+
message: string;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* A transient message a renderer shows as a toast. A command's confirmation of
|
|
22
|
+
* what the person just did is never one: it is an echo and is not announced.
|
|
23
|
+
*/
|
|
24
|
+
export type ParakeetToast = Readonly<{
|
|
25
|
+
/** How to color it. */
|
|
26
|
+
variant: "info" | "warning" | "error";
|
|
16
27
|
/** What happened. */
|
|
17
28
|
message: string;
|
|
18
29
|
}>;
|
|
@@ -27,7 +38,7 @@ export type ParakeetInteractionState = Readonly<{
|
|
|
27
38
|
/** Stable identity of the panel row being inspected. */
|
|
28
39
|
rowId: string;
|
|
29
40
|
}> | undefined;
|
|
30
|
-
/** The latest
|
|
41
|
+
/** The latest attention item: it stays until the person resolves it. */
|
|
31
42
|
notice?: ParakeetNotice | undefined;
|
|
32
43
|
}>;
|
|
33
44
|
/** Parakeet's command conversation store, shared by every renderer. */
|
|
@@ -40,8 +51,12 @@ export type ParakeetInteractions = Readonly<{
|
|
|
40
51
|
close: () => void;
|
|
41
52
|
/** Open or dismiss an admitted panel inspection without starting a command. */
|
|
42
53
|
inspect: (inspection: ParakeetInteractionState["inspection"]) => void;
|
|
43
|
-
/** Replace or clear the
|
|
54
|
+
/** Replace or clear the attention notice, starting a new command turn. */
|
|
44
55
|
report: (notice: ParakeetNotice | undefined) => void;
|
|
56
|
+
/** Announce a transient message to every toast subscriber. */
|
|
57
|
+
toast: (toast: ParakeetToast) => void;
|
|
58
|
+
/** Receive transient messages; the renderer decides how to show them. */
|
|
59
|
+
subscribeToasts: (listener: (toast: ParakeetToast) => void) => () => void;
|
|
45
60
|
/** Become the runtime's presenter until the returned disposer runs. */
|
|
46
61
|
attach: (present: WhistleRuntime["present"]) => () => void;
|
|
47
62
|
/** How the most recent interaction ended; a dismissal is not a failure to report. */
|
|
@@ -50,6 +65,6 @@ export type ParakeetInteractions = Readonly<{
|
|
|
50
65
|
/**
|
|
51
66
|
* Own how commands talk to the human. An interaction opens only when it asks
|
|
52
67
|
* a question, hands off to a URL or shows progress; a command that just
|
|
53
|
-
* reports
|
|
68
|
+
* reports information emits a toast; ordinary success echoes are silent.
|
|
54
69
|
*/
|
|
55
70
|
export declare function createParakeetInteractions(): ParakeetInteractions;
|
|
@@ -2,13 +2,23 @@ import { createWhistleInteractionHost, } from "@fungi.computer/whistle/interacti
|
|
|
2
2
|
/**
|
|
3
3
|
* Own how commands talk to the human. An interaction opens only when it asks
|
|
4
4
|
* a question, hands off to a URL or shows progress; a command that just
|
|
5
|
-
* reports
|
|
5
|
+
* reports information emits a toast; ordinary success echoes are silent.
|
|
6
6
|
*/
|
|
7
7
|
export function createParakeetInteractions() {
|
|
8
8
|
let state = {};
|
|
9
9
|
let sequence = 0;
|
|
10
10
|
let lastEnd;
|
|
11
11
|
const listeners = new Set();
|
|
12
|
+
const toastListeners = new Set();
|
|
13
|
+
const toast = (next) => {
|
|
14
|
+
for (const listener of toastListeners)
|
|
15
|
+
listener(next);
|
|
16
|
+
};
|
|
17
|
+
/** Information is a toast; a success only echoes what the person did. */
|
|
18
|
+
const announce = (message) => {
|
|
19
|
+
if (message.type === "info")
|
|
20
|
+
toast({ variant: "info", message: message.message });
|
|
21
|
+
};
|
|
12
22
|
const publish = (next) => {
|
|
13
23
|
state = next;
|
|
14
24
|
for (const listener of listeners)
|
|
@@ -18,9 +28,10 @@ export function createParakeetInteractions() {
|
|
|
18
28
|
if (state.open?.key === key)
|
|
19
29
|
publish({ ...state, open: undefined });
|
|
20
30
|
};
|
|
21
|
-
const presenter = ({ title
|
|
31
|
+
const presenter = ({ title }) => {
|
|
22
32
|
const key = ++sequence;
|
|
23
33
|
let shown = false;
|
|
34
|
+
let hadRetryableFailure = false;
|
|
24
35
|
const host = createWhistleInteractionHost({
|
|
25
36
|
title,
|
|
26
37
|
onEnd: (end) => {
|
|
@@ -32,35 +43,41 @@ export function createParakeetInteractions() {
|
|
|
32
43
|
...state,
|
|
33
44
|
notice: { tone: "error", message: end.message },
|
|
34
45
|
});
|
|
35
|
-
else if (message !== undefined
|
|
36
|
-
|
|
37
|
-
...state,
|
|
38
|
-
notice: {
|
|
39
|
-
tone: message.type === "success" ? "success" : "info",
|
|
40
|
-
message: message.message,
|
|
41
|
-
},
|
|
42
|
-
});
|
|
46
|
+
else if (message !== undefined)
|
|
47
|
+
announce(message);
|
|
43
48
|
return;
|
|
44
49
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
// A retried result stays in the same notice row until Done. A
|
|
51
|
+
// progress-only ending is not a completion notice to manufacture.
|
|
52
|
+
if (end.status === "handled" &&
|
|
53
|
+
hadRetryableFailure &&
|
|
54
|
+
message !== undefined &&
|
|
55
|
+
message.type !== "progress")
|
|
56
|
+
return;
|
|
57
|
+
const closing = host.snapshot().closing;
|
|
58
|
+
if (end.status === "cancelled" && closing !== undefined)
|
|
59
|
+
announce(closing);
|
|
60
|
+
if (end.status === "handled" && message !== undefined)
|
|
61
|
+
announce(message);
|
|
62
|
+
// Failed interactions retain their failure UI; ordinary results never
|
|
63
|
+
// leave a dialog behind just to confirm the completed action.
|
|
64
|
+
if (end.status !== "failed")
|
|
50
65
|
closeKey(key);
|
|
51
66
|
},
|
|
52
67
|
});
|
|
53
68
|
const unsubscribe = host.subscribe(() => {
|
|
69
|
+
const current = host.snapshot();
|
|
70
|
+
if (current.retryable)
|
|
71
|
+
hadRetryableFailure = true;
|
|
54
72
|
if (shown)
|
|
55
73
|
return;
|
|
56
|
-
const current = host.snapshot();
|
|
57
74
|
if (current.prompt === undefined &&
|
|
58
75
|
current.handoff === undefined &&
|
|
59
76
|
current.message?.type !== "progress")
|
|
60
77
|
return;
|
|
61
78
|
shown = true;
|
|
62
79
|
const previous = state.open;
|
|
63
|
-
publish({ ...state, open: { key, host
|
|
80
|
+
publish({ ...state, open: { key, host }, notice: undefined });
|
|
64
81
|
previous?.host.cancel();
|
|
65
82
|
});
|
|
66
83
|
const session = host.begin();
|
|
@@ -95,6 +112,13 @@ export function createParakeetInteractions() {
|
|
|
95
112
|
lastEnd = undefined;
|
|
96
113
|
publish({ ...state, notice });
|
|
97
114
|
},
|
|
115
|
+
toast,
|
|
116
|
+
subscribeToasts: (listener) => {
|
|
117
|
+
toastListeners.add(listener);
|
|
118
|
+
return () => {
|
|
119
|
+
toastListeners.delete(listener);
|
|
120
|
+
};
|
|
121
|
+
},
|
|
98
122
|
attach: (present) => present(presenter),
|
|
99
123
|
lastEnd: () => lastEnd,
|
|
100
124
|
};
|
package/dist/commands/slash.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/** Human key labels from Whistle bindings, with the platform's modifier name. */
|
|
2
2
|
export function keyLabel(binding, mac = /Mac|iPhone|iPad/u.test(globalThis.navigator.userAgent)) {
|
|
3
|
+
if (binding.startsWith("<leader>"))
|
|
4
|
+
return `Ctrl K ${binding.slice("<leader>".length)}`;
|
|
3
5
|
return binding
|
|
4
6
|
.split(" ")
|
|
5
|
-
.map((chord
|
|
6
|
-
if (chord === "<leader>")
|
|
7
|
-
return "Ctrl K";
|
|
8
|
-
if (index > 0 && binding.startsWith("<leader> "))
|
|
9
|
-
return chord;
|
|
7
|
+
.map((chord) => {
|
|
10
8
|
const parts = chord
|
|
11
9
|
.split("+")
|
|
12
10
|
.map((part) => part === "mod"
|
|
@@ -70,7 +70,7 @@ export function useSlashCommands(options) {
|
|
|
70
70
|
lease.dispose();
|
|
71
71
|
};
|
|
72
72
|
}, [commands, enabled, store, hasCommandPalette]);
|
|
73
|
-
const advertised = useMemo(() => advertisedCommands(commands, session, (entryId, commandId) => {
|
|
73
|
+
const advertised = useMemo(() => advertisedCommands(commands, session, () => contributionsRef.current, (entryId, commandId) => {
|
|
74
74
|
const row = contributionsRef.current
|
|
75
75
|
.find((entry) => entry.entryId === entryId)
|
|
76
76
|
?.value.panel.find((row) => row.inspection?.commandId === commandId);
|
|
@@ -91,7 +91,7 @@ export function useSlashCommands(options) {
|
|
|
91
91
|
tone: "error",
|
|
92
92
|
message: `${error.extensionId} commands are unavailable: ${error.message}`,
|
|
93
93
|
});
|
|
94
|
-
}, [advertised, enabled, slots, store]);
|
|
94
|
+
}, [advertised, enabled, slots, contributions, store]);
|
|
95
95
|
const registry = useMemo(() => slashRegistry(snapshot), [snapshot]);
|
|
96
96
|
const [dismissed, setDismissed] = useState();
|
|
97
97
|
const [highlighted, setHighlighted] = useState({ draft: "", index: 0 });
|
|
@@ -9,6 +9,8 @@ export type AgentHeadActivity = "idle" | "thinking" | "speaking";
|
|
|
9
9
|
* idle head shows its real look without animating.
|
|
10
10
|
*/
|
|
11
11
|
export declare const AgentHead: import("react").NamedExoticComponent<{
|
|
12
|
+
/** External animation clock in milliseconds; omitted uses live motion. */
|
|
13
|
+
timeMs?: number;
|
|
12
14
|
/** Motion presented by the head; defaults to idle. */
|
|
13
15
|
activity?: AgentHeadActivity;
|
|
14
16
|
/** The Agent's avatar, from its Agent record. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Monogram } from "@fungi.computer/caps/components/monogram";
|
|
3
|
-
import { Sprite } from "@fungi.computer/sprite/react";
|
|
3
|
+
import { Sprite, useAnimationTime } from "@fungi.computer/sprite/react";
|
|
4
4
|
import { memo, useMemo, useState } from "react";
|
|
5
5
|
import { avatarPresentation } from "./avatar-presentation.js";
|
|
6
6
|
import { shiitakeSprite } from "./sprite-service.js";
|
|
@@ -11,10 +11,11 @@ import { shiitakeSprite } from "./sprite-service.js";
|
|
|
11
11
|
* canvas stays mounted and is paused unless `thinking` or `speaking`, so an
|
|
12
12
|
* idle head shows its real look without animating.
|
|
13
13
|
*/
|
|
14
|
-
export const AgentHead = memo(function AgentHead({ activity = "idle", avatar, className, name, size = 24, }) {
|
|
14
|
+
export const AgentHead = memo(function AgentHead({ activity = "idle", avatar, className, name, size = 24, timeMs: explicitTimeMs, }) {
|
|
15
|
+
const timeMs = useAnimationTime(explicitTimeMs);
|
|
15
16
|
const [ready, setReady] = useState(false);
|
|
16
17
|
const presentation = useMemo(() => avatarPresentation(avatar), [avatar]);
|
|
17
|
-
return (_jsxs("span", { "aria-hidden": "true", className: `relative inline-block shrink-0 ${className ?? ""}`, "data-agent-head": ready ? "ready" : "loading", style: { height: size, width: size }, children: [ready ? null : (_jsx(Monogram, { className: "absolute inset-0", name: name, seed: name })), _jsx(Sprite, { activity: activity, appearance: presentation.appearance, className: "absolute inset-0", expression: presentation.expression, onState: (state) => {
|
|
18
|
+
return (_jsxs("span", { "aria-hidden": "true", className: `relative inline-block shrink-0 ${className ?? ""}`, "data-agent-head": ready ? "ready" : "loading", style: { height: size, width: size }, children: [ready ? null : (_jsx(Monogram, { className: "absolute inset-0", name: name, seed: name })), _jsx(Sprite, { activity: activity, ...(timeMs === undefined ? {} : { timeMs }), appearance: presentation.appearance, className: "absolute inset-0", expression: presentation.expression, onState: (state) => {
|
|
18
19
|
setReady(state.status === "ready");
|
|
19
|
-
}, paused: activity === "idle", renderStyle: presentation.renderStyle, service: shiitakeSprite, src: presentation.src })] }));
|
|
20
|
+
}, paused: timeMs !== undefined || activity === "idle", renderStyle: presentation.renderStyle, service: shiitakeSprite, src: presentation.src })] }));
|
|
20
21
|
});
|
|
@@ -11,6 +11,8 @@ export type ChatComposerUsage = Readonly<{
|
|
|
11
11
|
tokens: number;
|
|
12
12
|
}>;
|
|
13
13
|
export type ChatComposerProps = {
|
|
14
|
+
/** Undefined for visitor composers without a caller-owned pending queue. */
|
|
15
|
+
queuedRecall: Pick<ComposerController, "recall" | "recallState" | "images"> | undefined;
|
|
14
16
|
/** Caller-owned prompt hint; omitted for the ordinary Session composer. */
|
|
15
17
|
placeholder?: string | undefined;
|
|
16
18
|
/** Hide internal model, usage and status readouts for visitors. */
|
|
@@ -38,6 +40,6 @@ export type ChatComposerProps = {
|
|
|
38
40
|
/** Parakeet's slash controller; the composer only draws and forwards keys. */
|
|
39
41
|
slash?: SlashCommandController | undefined;
|
|
40
42
|
};
|
|
41
|
-
/**
|
|
43
|
+
/** Full Session readout for announcements; the footer only draws attention states. */
|
|
42
44
|
export declare function sessionReadout(status: ParakeetStatus, view: ParakeetSessionViewState): string;
|
|
43
45
|
export declare const ChatComposer: import("react").NamedExoticComponent<ChatComposerProps>;
|