@fungi.computer/parakeet 0.3.0 → 0.5.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/dist/account-commands.js +20 -7
- package/dist/client-contributions.d.ts +1 -0
- package/dist/commands/awaiting-driver.d.ts +20 -0
- package/dist/commands/awaiting-driver.js +121 -0
- package/dist/commands/builtins.js +6 -4
- package/dist/commands/interactions.d.ts +25 -0
- package/dist/commands/interactions.js +76 -2
- package/dist/commands/run.js +3 -3
- package/dist/commands/slash.d.ts +7 -3
- package/dist/commands/slash.js +2 -0
- package/dist/commands/use-slash-commands.d.ts +13 -0
- package/dist/commands/use-slash-commands.js +58 -1
- package/dist/components/ChatComposer.d.ts +2 -0
- package/dist/components/ChatComposer.js +150 -38
- package/dist/components/ClientRows.js +42 -26
- package/dist/components/CompletionMenu.d.ts +54 -0
- package/dist/components/CompletionMenu.js +52 -0
- package/dist/components/DecisionComposer.d.ts +30 -0
- package/dist/components/DecisionComposer.js +146 -0
- package/dist/components/InteractionPanel.d.ts +10 -1
- package/dist/components/InteractionPanel.js +24 -5
- package/dist/components/RunFailureNotice.js +11 -2
- package/dist/components/SlashMenu.d.ts +1 -3
- package/dist/components/SlashMenu.js +5 -13
- package/dist/components/TimelineItem.d.ts +1 -0
- package/dist/components/TimelineItem.js +3 -3
- package/dist/components/ToolGroup.d.ts +1 -0
- package/dist/components/ToolGroup.js +7 -7
- package/dist/conversation.d.ts +5 -1
- package/dist/conversation.js +72 -20
- package/dist/core/approval-decision.d.ts +198 -0
- package/dist/core/approval-decision.js +291 -0
- package/dist/core/awaiting-answer.d.ts +229 -0
- package/dist/core/awaiting-answer.js +303 -0
- package/dist/core/build-timeline.js +56 -5
- package/dist/core/client-actions.d.ts +1 -0
- package/dist/core/client-actions.js +8 -21
- package/dist/core/draft-storage.d.ts +4 -0
- package/dist/core/draft-storage.js +12 -5
- package/dist/core/picker-text.d.ts +7 -0
- package/dist/core/picker-text.js +24 -0
- package/dist/core/session-query.d.ts +8 -0
- package/dist/core/session-query.js +13 -11
- package/dist/core/session-view-state.d.ts +1 -1
- package/dist/core/session-view-state.js +14 -6
- package/dist/headless.d.ts +7 -2
- package/dist/headless.js +6 -1
- package/dist/model-commands.js +14 -15
- package/dist/parakeet.d.ts +6 -2
- package/dist/parakeet.js +15 -6
- package/dist/session-commands.js +6 -2
- package/dist/store/types.d.ts +5 -1
- package/dist/styles.css +407 -113
- package/dist/use-approval-awaiting.d.ts +22 -0
- package/dist/use-approval-awaiting.js +81 -0
- package/dist/use-decision.d.ts +20 -0
- package/dist/use-decision.js +79 -0
- package/docs/api/commands/slash.md +30 -26
- package/docs/api/headless.md +822 -41
- package/docs/api/index.md +13 -4
- package/package.json +15 -8
- package/src/completion-menu.css +71 -0
- package/src/styles.css +174 -46
|
@@ -15,6 +15,7 @@ const recalledPrompt = z.object({
|
|
|
15
15
|
delivery,
|
|
16
16
|
});
|
|
17
17
|
const pendingDraft = z.object({
|
|
18
|
+
status: z.enum(["queued-local", "admitting"]),
|
|
18
19
|
requestId: z.string().min(1),
|
|
19
20
|
content: z.string(),
|
|
20
21
|
submittedDraft: z.string(),
|
|
@@ -31,15 +32,21 @@ const journal = z.object({
|
|
|
31
32
|
});
|
|
32
33
|
/** One owner-scoped journal commits pending text and composer text together. */
|
|
33
34
|
export function draftJournal(scope, storage) {
|
|
34
|
-
const key = `fungi.parakeet.drafts.
|
|
35
|
+
const key = `fungi.parakeet.drafts.v2:${scope}`;
|
|
35
36
|
return {
|
|
36
37
|
read() {
|
|
37
38
|
if (storage === undefined)
|
|
38
39
|
return { draft: "", pending: [] };
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
try {
|
|
41
|
+
const saved = storage.getItem(key);
|
|
42
|
+
return saved === null
|
|
43
|
+
? { draft: "", pending: [] }
|
|
44
|
+
: journal.parse(JSON.parse(saved));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Preview journals are disposable; a broken load must not break chat.
|
|
48
|
+
return { draft: "", pending: [] };
|
|
49
|
+
}
|
|
43
50
|
},
|
|
44
51
|
write(value) {
|
|
45
52
|
if (storage === undefined)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When something happened, as a picker column: `14:02` for today, `Yesterday`,
|
|
3
|
+
* or `2026-10-24`. Shared by every picker that lists past work.
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatWhen(epochMillis: number, now: number): string;
|
|
6
|
+
/** `text` on one line, whitespace collapsed and cut at a word to `limit`. */
|
|
7
|
+
export declare function oneLine(text: string, limit: number): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
2
|
+
const dayStart = (date) => new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
3
|
+
/**
|
|
4
|
+
* When something happened, as a picker column: `14:02` for today, `Yesterday`,
|
|
5
|
+
* or `2026-10-24`. Shared by every picker that lists past work.
|
|
6
|
+
*/
|
|
7
|
+
export function formatWhen(epochMillis, now) {
|
|
8
|
+
const at = new Date(epochMillis);
|
|
9
|
+
const days = Math.round((dayStart(new Date(now)) - dayStart(at)) / 86_400_000);
|
|
10
|
+
if (days <= 0)
|
|
11
|
+
return `${pad(at.getHours())}:${pad(at.getMinutes())}`;
|
|
12
|
+
if (days === 1)
|
|
13
|
+
return "Yesterday";
|
|
14
|
+
return `${String(at.getFullYear())}-${pad(at.getMonth() + 1)}-${pad(at.getDate())}`;
|
|
15
|
+
}
|
|
16
|
+
/** `text` on one line, whitespace collapsed and cut at a word to `limit`. */
|
|
17
|
+
export function oneLine(text, limit) {
|
|
18
|
+
const line = text.replace(/\s+/gu, " ").trim();
|
|
19
|
+
if (line.length <= limit)
|
|
20
|
+
return line;
|
|
21
|
+
const cut = line.slice(0, Math.max(0, limit - 1));
|
|
22
|
+
const space = cut.lastIndexOf(" ");
|
|
23
|
+
return `${(space > limit / 2 ? cut.slice(0, space) : cut).trimEnd()}…`;
|
|
24
|
+
}
|
|
@@ -7,6 +7,8 @@ export declare function sessionQueryOptions(session: ParakeetSession): import("@
|
|
|
7
7
|
observation: "connecting" | "live";
|
|
8
8
|
/** Last coherent durable Session state, absent before the first snapshot. */
|
|
9
9
|
snapshot: SessionSnapshot | undefined;
|
|
10
|
+
/** Last successful watch connect or delivered frame, not a cached seed. */
|
|
11
|
+
lastReplyAt: number | undefined;
|
|
10
12
|
/** One retryable user prompt, including image cargo, survives page refreshes. */
|
|
11
13
|
latestPrompt: Readonly<{
|
|
12
14
|
/** Owner-issued identity of the original prompt command. */
|
|
@@ -21,6 +23,8 @@ export declare function sessionQueryOptions(session: ParakeetSession): import("@
|
|
|
21
23
|
observation: "connecting" | "live";
|
|
22
24
|
/** Last coherent durable Session state, absent before the first snapshot. */
|
|
23
25
|
snapshot: SessionSnapshot | undefined;
|
|
26
|
+
/** Last successful watch connect or delivered frame, not a cached seed. */
|
|
27
|
+
lastReplyAt: number | undefined;
|
|
24
28
|
/** One retryable user prompt, including image cargo, survives page refreshes. */
|
|
25
29
|
latestPrompt: Readonly<{
|
|
26
30
|
/** Owner-issued identity of the original prompt command. */
|
|
@@ -36,6 +40,8 @@ export declare function sessionQueryOptions(session: ParakeetSession): import("@
|
|
|
36
40
|
observation: "connecting" | "live";
|
|
37
41
|
/** Last coherent durable Session state, absent before the first snapshot. */
|
|
38
42
|
snapshot: SessionSnapshot | undefined;
|
|
43
|
+
/** Last successful watch connect or delivered frame, not a cached seed. */
|
|
44
|
+
lastReplyAt: number | undefined;
|
|
39
45
|
/** One retryable user prompt, including image cargo, survives page refreshes. */
|
|
40
46
|
latestPrompt: Readonly<{
|
|
41
47
|
/** Owner-issued identity of the original prompt command. */
|
|
@@ -51,6 +57,8 @@ export declare function sessionQueryOptions(session: ParakeetSession): import("@
|
|
|
51
57
|
observation: "connecting" | "live";
|
|
52
58
|
/** Last coherent durable Session state, absent before the first snapshot. */
|
|
53
59
|
snapshot: SessionSnapshot | undefined;
|
|
60
|
+
/** Last successful watch connect or delivered frame, not a cached seed. */
|
|
61
|
+
lastReplyAt: number | undefined;
|
|
54
62
|
/** One retryable user prompt, including image cargo, survives page refreshes. */
|
|
55
63
|
latestPrompt: Readonly<{
|
|
56
64
|
/** Owner-issued identity of the original prompt command. */
|
|
@@ -4,6 +4,7 @@ import { reportFailure } from "../lib/failure.js";
|
|
|
4
4
|
const emptyProjection = Object.freeze({
|
|
5
5
|
observation: "connecting",
|
|
6
6
|
snapshot: undefined,
|
|
7
|
+
lastReplyAt: undefined,
|
|
7
8
|
latestPrompt: undefined,
|
|
8
9
|
});
|
|
9
10
|
// A watch starts with a bounded page, but its update frames keep arriving for
|
|
@@ -60,7 +61,7 @@ async function* observeSession(session, signal) {
|
|
|
60
61
|
yield { kind: "snapshot", value: snapshot };
|
|
61
62
|
}
|
|
62
63
|
else {
|
|
63
|
-
yield { kind: "frame", value: frame };
|
|
64
|
+
yield { kind: "frame", value: frame, snapshot };
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
throw new Error("Session watch ended; current activity is unavailable");
|
|
@@ -81,21 +82,21 @@ function reduceObservation(projection, observation) {
|
|
|
81
82
|
return {
|
|
82
83
|
observation: "live",
|
|
83
84
|
snapshot: observation.value,
|
|
85
|
+
lastReplyAt: Date.now(),
|
|
84
86
|
latestPrompt: latestPrompt(observation.value.messages, projection.latestPrompt),
|
|
85
87
|
};
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
observation: "live",
|
|
90
|
-
snapshot: observation.value.baseline,
|
|
91
|
-
latestPrompt: latestPrompt(observation.value.baseline.messages),
|
|
92
|
-
}
|
|
93
|
-
: projection;
|
|
94
|
-
const snapshot = applySessionFrame(projection.snapshot, observation.value);
|
|
88
|
+
// The generator owns frame application and always delivers its baseline
|
|
89
|
+
// first. The cache only reduces already-projected observations.
|
|
90
|
+
const snapshot = observation.snapshot;
|
|
95
91
|
return {
|
|
96
92
|
observation: "live",
|
|
97
93
|
snapshot,
|
|
98
|
-
|
|
94
|
+
lastReplyAt: Date.now(),
|
|
95
|
+
latestPrompt: observation.value.kind === "reset" ||
|
|
96
|
+
(observation.value.kind === "update" &&
|
|
97
|
+
observation.value.messages.some((message) => message.value.role === "user"))
|
|
98
|
+
? latestPrompt(snapshot.messages, projection.latestPrompt)
|
|
99
|
+
: projection.latestPrompt,
|
|
99
100
|
};
|
|
100
101
|
}
|
|
101
102
|
/** One shared TanStack observation keyed by Shiitake's logical Session address. */
|
|
@@ -119,6 +120,7 @@ export function seedSessionQuery(queryClient, session, snapshot) {
|
|
|
119
120
|
queryClient.setQueryData(sessionQueryOptions(session).queryKey, {
|
|
120
121
|
observation: "connecting",
|
|
121
122
|
snapshot,
|
|
123
|
+
lastReplyAt: undefined,
|
|
122
124
|
latestPrompt: latestPrompt(snapshot.messages),
|
|
123
125
|
});
|
|
124
126
|
}
|
|
@@ -46,7 +46,7 @@ export type SessionViewState = Readonly<{
|
|
|
46
46
|
}> | Readonly<{
|
|
47
47
|
/** The latest terminal failed or was interrupted. */
|
|
48
48
|
kind: "error";
|
|
49
|
-
/**
|
|
49
|
+
/** Redacted, bounded terminal diagnostic for presentation. */
|
|
50
50
|
cause: string;
|
|
51
51
|
}>;
|
|
52
52
|
/** The facts a view state is decided from. */
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { boundedDiagnostic } from "@fungi.computer/bird-dog/effect";
|
|
2
|
+
import stripAnsi from "strip-ansi";
|
|
1
3
|
const retryCauseLimit = 160;
|
|
2
|
-
const errorCauseLimit = 200;
|
|
3
4
|
function collapsed(text, limit) {
|
|
4
5
|
return text.replace(/\s+/gu, " ").trim().slice(0, limit);
|
|
5
6
|
}
|
|
7
|
+
function displayDiagnostic(text) {
|
|
8
|
+
// Recognize whole terminal programs before dropping individual controls:
|
|
9
|
+
// otherwise OSC payloads become visible text after their ESC is removed.
|
|
10
|
+
const safe = stripAnsi(text).replace(/[\p{Cc}\u200b\u200e\u200f\u061c\u202a-\u202e\u2066-\u2069\u2060\ufeff]/gu, (character) => (/\s/u.test(character) ? " " : ""));
|
|
11
|
+
return boundedDiagnostic(safe);
|
|
12
|
+
}
|
|
6
13
|
function idleViewState({ activity, terminals, }) {
|
|
7
14
|
let terminal;
|
|
8
15
|
for (const candidate of terminals)
|
|
@@ -15,14 +22,15 @@ function idleViewState({ activity, terminals, }) {
|
|
|
15
22
|
if (activity.lastModelChoiceSequence !== null &&
|
|
16
23
|
activity.lastModelChoiceSequence > terminal.sequence)
|
|
17
24
|
return { kind: "waiting_for_you" };
|
|
18
|
-
if (terminal.outcome === "failed" || terminal.outcome === "interrupted")
|
|
25
|
+
if (terminal.outcome === "failed" || terminal.outcome === "interrupted") {
|
|
26
|
+
const fallback = terminal.outcome === "interrupted"
|
|
27
|
+
? "Run was interrupted"
|
|
28
|
+
: "Run failed without a diagnostic";
|
|
19
29
|
return {
|
|
20
30
|
kind: "error",
|
|
21
|
-
cause:
|
|
22
|
-
(terminal.outcome === "interrupted"
|
|
23
|
-
? "Run was interrupted"
|
|
24
|
-
: "Run failed without a diagnostic"), errorCauseLimit),
|
|
31
|
+
cause: displayDiagnostic(terminal.diagnostic ?? fallback) ?? fallback,
|
|
25
32
|
};
|
|
33
|
+
}
|
|
26
34
|
return { kind: "waiting_for_you" };
|
|
27
35
|
}
|
|
28
36
|
/** Decide a Session's view state from its activity, terminals and progress. */
|
package/dist/headless.d.ts
CHANGED
|
@@ -2,8 +2,9 @@ export { formatModelIdentity, formatModelWithLevel, } from "./core/model-identit
|
|
|
2
2
|
export type { QueuedRecallState, QueuedRecallOutcome, } from "./core/queued-recall.js";
|
|
3
3
|
export type { ParakeetDraftIdentity, ParakeetDraftStorage, } from "./core/draft-storage.js";
|
|
4
4
|
export { formatContextUsage, formatTokenCount } from "./core/format-context.js";
|
|
5
|
+
export { formatWhen, oneLine } from "./core/picker-text.js";
|
|
5
6
|
export { providerChoices } from "./provider-choices.js";
|
|
6
|
-
export { ParakeetProvider, useComposer, useClientContributions, useParakeetInteractions, useExtensionSlots, useParakeetSession, useStatus, useTimeline, type ComposerController, type ParakeetDraftCheckpoint, type ParakeetModel, type ParakeetObservation, type ParakeetReconnectOutcome, type ParakeetStatus, type ParakeetSessionViewState, type ParakeetUsage, type StatusController, type TimelineController, } from "./conversation.js";
|
|
7
|
+
export { ParakeetProvider, useComposer, useClientContributions, useParakeetInteractions, useExtensionSlots, useParakeetSession, useLastSessionReplyAt, useStatus, useTimeline, type ComposerController, type ParakeetDraftCheckpoint, type ParakeetModel, type ParakeetObservation, type ParakeetReconnectOutcome, type ParakeetStatus, type ParakeetSessionViewState, type ParakeetUsage, type StatusController, type TimelineController, } from "./conversation.js";
|
|
7
8
|
export { sessionViewState, queuedSessionLabel, sessionActivityStatus, type SessionActivityStatus, type SessionViewFacts, type SessionViewState as ParakeetOwnerViewState, } from "./core/session-view-state.js";
|
|
8
9
|
export { mergeTerminals } from "./core/session-projection.js";
|
|
9
10
|
export { type SessionActivity, seedSessionQuery, sessionQueryOptions, subscribeSessionActivity, } from "./core/session-query.js";
|
|
@@ -14,7 +15,7 @@ export { contributeSessionCommands, type ParakeetSessionCommandOptions, } from "
|
|
|
14
15
|
export { completeSlash, fuzzyMatch, keyBindings, keyLabel, parseSlashDraft, slashRegistry, type FuzzyMatch, type KeyBindingRow, type SlashCommand, type SlashCommandItem, type SlashCompletion, type SlashParse, type SlashWordItem, } from "./commands/slash.js";
|
|
15
16
|
export { composerHints, fitHints, formatHints, type ComposerHintContext, type ComposerKeys, type KeyHint, } from "./commands/hints.js";
|
|
16
17
|
export { isCancelledOutcome, runSlashDraft, type SlashRuntime, } from "./commands/run.js";
|
|
17
|
-
export { createParakeetInteractions, type ParakeetInteractions, type ParakeetInteractionState, type ParakeetNotice, type ParakeetOpenInteraction, type ParakeetToast, } from "./commands/interactions.js";
|
|
18
|
+
export { createParakeetInteractions, type ParakeetInteractions, type ParakeetAwaiting, type ParakeetInteractionState, type ParakeetNotice, type ParakeetOpenInteraction, type ParakeetToast, } from "./commands/interactions.js";
|
|
18
19
|
export { parakeetBuiltinCommands, type ParakeetBuiltinOptions, } from "./commands/builtins.js";
|
|
19
20
|
export { advertisedCommands, type AdvertisedCatalogError, type AdvertisedCommands, } from "./commands/advertised.js";
|
|
20
21
|
export { useSlashCommands, type ParakeetCommandRuntime, type SlashCommandController, type SlashCommandOptions, } from "./commands/use-slash-commands.js";
|
|
@@ -26,5 +27,9 @@ export { formatToolDuration, toolCallView, toolKindSigils, toolStatusTone, type
|
|
|
26
27
|
export { assistantMessageVisible } from "./core/assistant-visibility.js";
|
|
27
28
|
export { disclosureOpen, initialToolDisclosure, toggleAllDisclosure, toggleDisclosure, toolCallDefaultOpen, toolGroupDefaultOpen, visibleToolCalls, type ToolDisclosureState, } from "./core/tool-groups.js";
|
|
28
29
|
export { useClientActions } from "./client-contributions.js";
|
|
30
|
+
export { awaitingQueue, awaitingTakeover, decisionView, decisionViewText, initialDecisionState, reduceDecision, type AwaitingItem, type AwaitingKind, type AwaitingQueue, type DecisionEffect, type DecisionIntent, type DecisionOutcome, type DecisionPhase, type DecisionPrompt, type DecisionRowView, type DecisionState, type DecisionView, } from "./core/awaiting-answer.js";
|
|
31
|
+
export { useDecision, type DecisionController } from "./use-decision.js";
|
|
32
|
+
export { useApprovalAwaiting } from "./use-approval-awaiting.js";
|
|
33
|
+
export { approvalCardCopy, approvalRefusal, askApproval, type ApprovalAbandoned, type ApprovalAnswered, type ApprovalAnswerInput, type ApprovalAnswerResult, type ApprovalCard, type ApprovalContent, type ApprovalDecision, type ApprovalEntry, type ApprovalFacts, type ApprovalOutcome, type ApprovalRefused, type ApprovalReview, type ApprovalSource, } from "./core/approval-decision.js";
|
|
29
34
|
export { placeClientTimeline, type PlacedClientCell, } from "./core/client-timeline.js";
|
|
30
35
|
export type { ClientContribution } from "./core/client-contributions.js";
|
package/dist/headless.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { formatModelIdentity, formatModelWithLevel, } from "./core/model-identity.js";
|
|
2
2
|
export { formatContextUsage, formatTokenCount } from "./core/format-context.js";
|
|
3
|
+
export { formatWhen, oneLine } from "./core/picker-text.js";
|
|
3
4
|
export { providerChoices } from "./provider-choices.js";
|
|
4
|
-
export { ParakeetProvider, useComposer, useClientContributions, useParakeetInteractions, useExtensionSlots, useParakeetSession, useStatus, useTimeline, } from "./conversation.js";
|
|
5
|
+
export { ParakeetProvider, useComposer, useClientContributions, useParakeetInteractions, useExtensionSlots, useParakeetSession, useLastSessionReplyAt, useStatus, useTimeline, } from "./conversation.js";
|
|
5
6
|
export { sessionViewState, queuedSessionLabel, sessionActivityStatus, } from "./core/session-view-state.js";
|
|
6
7
|
export { mergeTerminals } from "./core/session-projection.js";
|
|
7
8
|
export { seedSessionQuery, sessionQueryOptions, subscribeSessionActivity, } from "./core/session-query.js";
|
|
@@ -21,4 +22,8 @@ export { formatToolDuration, toolCallView, toolKindSigils, toolStatusTone, } fro
|
|
|
21
22
|
export { assistantMessageVisible } from "./core/assistant-visibility.js";
|
|
22
23
|
export { disclosureOpen, initialToolDisclosure, toggleAllDisclosure, toggleDisclosure, toolCallDefaultOpen, toolGroupDefaultOpen, visibleToolCalls, } from "./core/tool-groups.js";
|
|
23
24
|
export { useClientActions } from "./client-contributions.js";
|
|
25
|
+
export { awaitingQueue, awaitingTakeover, decisionView, decisionViewText, initialDecisionState, reduceDecision, } from "./core/awaiting-answer.js";
|
|
26
|
+
export { useDecision } from "./use-decision.js";
|
|
27
|
+
export { useApprovalAwaiting } from "./use-approval-awaiting.js";
|
|
28
|
+
export { approvalCardCopy, approvalRefusal, askApproval, } from "./core/approval-decision.js";
|
|
24
29
|
export { placeClientTimeline, } from "./core/client-timeline.js";
|
package/dist/model-commands.js
CHANGED
|
@@ -8,9 +8,6 @@ const defaultChoice = "default";
|
|
|
8
8
|
const recentModels = new WeakMap();
|
|
9
9
|
/** Private memory ceiling, independent of how many models a provider offers. */
|
|
10
10
|
const maximumRememberedProviders = 256;
|
|
11
|
-
function modelLabel(model) {
|
|
12
|
-
return `${model.providerName} · ${model.name}`;
|
|
13
|
-
}
|
|
14
11
|
/** Each provider's current or last-used model first, then catalog order. */
|
|
15
12
|
function orderModels(models, current, recent) {
|
|
16
13
|
return [...models].sort((left, right) => {
|
|
@@ -33,19 +30,18 @@ async function chooseReasoning(interaction, model, current) {
|
|
|
33
30
|
const answer = await interaction.prompt({
|
|
34
31
|
type: "choice",
|
|
35
32
|
message: `Thinking level for ${model.name}`,
|
|
33
|
+
summary: "How long the model thinks before it answers.",
|
|
34
|
+
current: current ?? defaultChoice,
|
|
36
35
|
options: [
|
|
37
36
|
{ value: defaultChoice, label: "Model default" },
|
|
38
|
-
...model.reasoning.map((level) => ({
|
|
39
|
-
value: level,
|
|
40
|
-
label: level === current ? `${level} (current)` : level,
|
|
41
|
-
})),
|
|
37
|
+
...model.reasoning.map((level) => ({ value: level, label: level })),
|
|
42
38
|
],
|
|
43
39
|
});
|
|
44
40
|
return model.reasoning.find((level) => level === answer);
|
|
45
41
|
}
|
|
46
42
|
/** Leave one Whistle option for inheriting the Agent model. */
|
|
47
43
|
const maximumModelOptions = 255;
|
|
48
|
-
async function chooseProviderModels(interaction, models, account, refreshModels) {
|
|
44
|
+
async function chooseProviderModels(interaction, models, account, refreshModels, currentProviderId) {
|
|
49
45
|
const { owner } = account;
|
|
50
46
|
const [access, registry] = await Promise.all([
|
|
51
47
|
owner.access(interaction.signal),
|
|
@@ -76,7 +72,9 @@ async function chooseProviderModels(interaction, models, account, refreshModels)
|
|
|
76
72
|
}
|
|
77
73
|
const providerId = await interaction.prompt({
|
|
78
74
|
type: "choice",
|
|
79
|
-
message: "
|
|
75
|
+
message: "Choose a provider",
|
|
76
|
+
summary: "Or follow the Agent's own model.",
|
|
77
|
+
current: currentProviderId ?? defaultChoice,
|
|
80
78
|
collapsedGroup: "More providers",
|
|
81
79
|
options: [
|
|
82
80
|
{ value: defaultChoice, label: "Use agent model" },
|
|
@@ -156,21 +154,22 @@ export function sessionModelCommandDefinitions(session, options) {
|
|
|
156
154
|
return session.modelPreference.read();
|
|
157
155
|
const current = await session.modelPreference.read();
|
|
158
156
|
remember(current);
|
|
159
|
-
const providerModels = await chooseProviderModels(interaction, options.models, options.account, options.refreshModels);
|
|
157
|
+
const providerModels = await chooseProviderModels(interaction, options.models, options.account, options.refreshModels, current?.providerId);
|
|
160
158
|
if (providerModels === null) {
|
|
161
159
|
await interaction.run(() => session.modelPreference.set(null));
|
|
162
160
|
return interaction.run(() => session.modelPreference.read());
|
|
163
161
|
}
|
|
164
162
|
const ordered = orderModels(providerModels, current, recent);
|
|
163
|
+
const currentIndex = ordered.findIndex((model) => model.providerId === current?.providerId &&
|
|
164
|
+
model.id === current.modelId);
|
|
165
165
|
const answer = await interaction.prompt({
|
|
166
166
|
type: "choice",
|
|
167
|
-
message: "
|
|
167
|
+
message: "Choose a model",
|
|
168
|
+
summary: `Models from ${ordered[0]?.providerName ?? "this provider"}.`,
|
|
169
|
+
current: currentIndex < 0 ? undefined : String(currentIndex),
|
|
168
170
|
options: ordered.map((model, index) => ({
|
|
169
171
|
value: String(index),
|
|
170
|
-
label: model.
|
|
171
|
-
model.id === current.modelId
|
|
172
|
-
? `${modelLabel(model)} (current)`
|
|
173
|
-
: modelLabel(model),
|
|
172
|
+
label: model.name,
|
|
174
173
|
})),
|
|
175
174
|
});
|
|
176
175
|
const model = ordered[Number(answer)];
|
package/dist/parakeet.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export type ParakeetProps = Readonly<{
|
|
|
31
31
|
composerActionVariant?: "primary" | "outline" | undefined;
|
|
32
32
|
/** Optional caller-owned actions for a completed tool result. */
|
|
33
33
|
renderToolActions?: ((item: ToolItem) => ReactNode) | undefined;
|
|
34
|
+
/** Optional host claim: true hides this item's image parts from the tool body because the host shows them. */
|
|
35
|
+
claimsToolMedia?: ((item: ToolItem) => boolean) | undefined;
|
|
34
36
|
/** Optional caller-owned attachment below a settled assistant reply. */
|
|
35
37
|
renderAssistantAttachment?: ((text: string) => ReactNode) | undefined;
|
|
36
38
|
/** Optional display text; attachments still receive the original reply. */
|
|
@@ -54,6 +56,8 @@ export type ParakeetViewProps = Readonly<{
|
|
|
54
56
|
composerActionVariant?: "primary" | "outline" | undefined;
|
|
55
57
|
/** Optional caller-owned actions for a completed tool result. */
|
|
56
58
|
renderToolActions?: ((item: ToolItem) => ReactNode) | undefined;
|
|
59
|
+
/** Optional host claim: true hides this item's image parts from the tool body because the host shows them. */
|
|
60
|
+
claimsToolMedia?: ((item: ToolItem) => boolean) | undefined;
|
|
57
61
|
/** Optional caller-owned attachment below a settled assistant reply. */
|
|
58
62
|
renderAssistantAttachment?: ((text: string) => ReactNode) | undefined;
|
|
59
63
|
/** Optional display text; attachments still receive the original reply. */
|
|
@@ -72,6 +76,6 @@ export type ParakeetViewProps = Readonly<{
|
|
|
72
76
|
commands?: ParakeetCommandRuntime | undefined;
|
|
73
77
|
}>;
|
|
74
78
|
/** Render the retained conversation surface within one Parakeet provider. */
|
|
75
|
-
export declare function ParakeetView({ disambiguateAuthors, visitor, composerActionVariant, empty, commands, renderToolActions, renderAssistantAttachment, formatAssistantText, onLink, onOpen, }: ParakeetViewProps): ReactElement;
|
|
79
|
+
export declare function ParakeetView({ disambiguateAuthors, visitor, composerActionVariant, empty, commands, renderToolActions, claimsToolMedia, renderAssistantAttachment, formatAssistantText, onLink, onOpen, }: ParakeetViewProps): ReactElement;
|
|
76
80
|
/** Standalone typed composer and exact-message timeline. */
|
|
77
|
-
export declare function Parakeet({ authorOf, disambiguateAuthors, visitor, markdownPolicy, composerActionVariant, empty, session, clients, renderToolActions, renderAssistantAttachment, formatAssistantText, commands, onLink, onOpen, }: ParakeetProps): ReactElement;
|
|
81
|
+
export declare function Parakeet({ authorOf, disambiguateAuthors, visitor, markdownPolicy, composerActionVariant, empty, session, clients, renderToolActions, claimsToolMedia, renderAssistantAttachment, formatAssistantText, commands, onLink, onOpen, }: ParakeetProps): ReactElement;
|
package/dist/parakeet.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createWhistle } from "@fungi.computer/whistle";
|
|
|
6
6
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
7
7
|
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
8
8
|
import { ParakeetProvider, useComposer, useClientContributions, useDismissFailedPrompt, useStatus, useTimeline, } from "./conversation.js";
|
|
9
|
+
import { awaitingTakeover } from "./core/awaiting-answer.js";
|
|
9
10
|
import { initialToolDisclosure, toggleDisclosure } from "./core/tool-groups.js";
|
|
10
11
|
import { authorDisplayNames, } from "./core/author-labels.js";
|
|
11
12
|
import { useTimelineItemKey } from "./core/use-timeline-item-key.js";
|
|
@@ -19,7 +20,7 @@ import { useSlashCommands, } from "./commands/use-slash-commands.js";
|
|
|
19
20
|
import { TimelineItem } from "./components/TimelineItem.js";
|
|
20
21
|
import { MarkdownPolicyContext } from "./components/MarkdownRenderer.js";
|
|
21
22
|
/** Render the retained conversation surface within one Parakeet provider. */
|
|
22
|
-
export function ParakeetView({ disambiguateAuthors, visitor, composerActionVariant, empty, commands, renderToolActions, renderAssistantAttachment, formatAssistantText, onLink, onOpen, }) {
|
|
23
|
+
export function ParakeetView({ disambiguateAuthors, visitor, composerActionVariant, empty, commands, renderToolActions, claimsToolMedia, renderAssistantAttachment, formatAssistantText, onLink, onOpen, }) {
|
|
23
24
|
const textareaRef = useRef(null);
|
|
24
25
|
const [ownCommands] = useState(() => createWhistle());
|
|
25
26
|
const slash = useSlashCommands({
|
|
@@ -31,6 +32,9 @@ export function ParakeetView({ disambiguateAuthors, visitor, composerActionVaria
|
|
|
31
32
|
});
|
|
32
33
|
const clientActions = useClientActions(commands ?? ownCommands);
|
|
33
34
|
const contributions = useClientContributions();
|
|
35
|
+
const { draft } = useComposer();
|
|
36
|
+
// The decision in the composer is not also a footer row.
|
|
37
|
+
const answering = awaitingTakeover(slash.awaiting, draft)?.key;
|
|
34
38
|
const timelineController = useTimeline();
|
|
35
39
|
const placed = useMemo(() => placeClientTimeline(contributions, timelineController.commandAnchors, timelineController.positionAnchors), [
|
|
36
40
|
contributions,
|
|
@@ -43,7 +47,11 @@ export function ParakeetView({ disambiguateAuthors, visitor, composerActionVaria
|
|
|
43
47
|
: null;
|
|
44
48
|
if (anchor instanceof HTMLAnchorElement && onLink?.(anchor.href))
|
|
45
49
|
event.preventDefault();
|
|
46
|
-
}, children: [_jsxs("div", { className: "flex h-full w-full flex-col border-r border-base-300", children: [_jsx(ConversationTimeline, { disambiguateAuthors: visitor === undefined && disambiguateAuthors === true, clientActions: clientActions, onOpen: onOpen, focusAfterJump: textareaRef, empty: empty, renderToolActions: renderToolActions, renderAssistantAttachment: renderAssistantAttachment, formatAssistantText: formatAssistantText }), contributions.some(({ value }) => value.panel.
|
|
50
|
+
}, children: [_jsxs("div", { className: "flex h-full w-full flex-col border-r border-base-300", children: [_jsx(ConversationTimeline, { disambiguateAuthors: visitor === undefined && disambiguateAuthors === true, clientActions: clientActions, onOpen: onOpen, focusAfterJump: textareaRef, empty: empty, renderToolActions: renderToolActions, claimsToolMedia: claimsToolMedia, renderAssistantAttachment: renderAssistantAttachment, formatAssistantText: formatAssistantText }), contributions.some(({ entryId, value }) => value.panel.some((row) => JSON.stringify([entryId, row.id]) !== answering)) ? (_jsx("section", { "aria-label": "Session extension controls", className: "w-full shrink-0 border-t border-base-300 bg-base-100", children: contributions.map(({ entryId, value: contribution }) => {
|
|
51
|
+
const value = {
|
|
52
|
+
...contribution,
|
|
53
|
+
panel: contribution.panel.filter((row) => JSON.stringify([entryId, row.id]) !== answering),
|
|
54
|
+
};
|
|
47
55
|
// An extension's retained-event notice and null-anchor rows
|
|
48
56
|
// join its own footer entry; one with no panel row here keeps
|
|
49
57
|
// none of this, rather than growing a section just to show it.
|
|
@@ -92,7 +100,7 @@ function ConversationInteraction({ slash, }) {
|
|
|
92
100
|
slash.store.close();
|
|
93
101
|
}, children: _jsx(DialogContent, { className: "w-[min(32rem,calc(100vw-2rem))] rounded-box bg-base-100 p-md shadow-lg", keepMounted: false, children: open === undefined ? null : (_jsx(InteractionPanel, { host: open.host, onClose: slash.store.close }, open.key)) }) }));
|
|
94
102
|
}
|
|
95
|
-
function ConversationTimeline({ disambiguateAuthors, clientActions, onOpen, focusAfterJump, empty, renderToolActions, renderAssistantAttachment, formatAssistantText, }) {
|
|
103
|
+
function ConversationTimeline({ disambiguateAuthors, clientActions, onOpen, focusAfterJump, empty, renderToolActions, claimsToolMedia, renderAssistantAttachment, formatAssistantText, }) {
|
|
96
104
|
const dismissFailed = useDismissFailedPrompt();
|
|
97
105
|
const timelineController = useTimeline();
|
|
98
106
|
const cancelQueued = useCallback((commandId) => {
|
|
@@ -209,7 +217,7 @@ function ConversationTimeline({ disambiguateAuthors, clientActions, onOpen, focu
|
|
|
209
217
|
item.author.name,
|
|
210
218
|
},
|
|
211
219
|
}
|
|
212
|
-
: item, onDismissFailed: dismissFailed, onCancelQueued: cancelQueued, cancellingQueued: timelineController.cancellingQueued, onToggleTool: toggleTool, renderToolActions: renderToolActions, renderAssistantAttachment: renderAssistantAttachment, formatAssistantText: formatAssistantText, toolDisclosure: toolDisclosure }), placed.after.get(item.id)?.map(({ entryId, cell }) => (_jsx(ClientRows, { entryId: entryId, rows: cell.rows, actions: clientActions, onOpen: onOpen }, `${entryId}:${cell.id}`)))] }, virtualItem.key));
|
|
220
|
+
: item, onDismissFailed: dismissFailed, onCancelQueued: cancelQueued, cancellingQueued: timelineController.cancellingQueued, onToggleTool: toggleTool, renderToolActions: renderToolActions, claimsToolMedia: claimsToolMedia, renderAssistantAttachment: renderAssistantAttachment, formatAssistantText: formatAssistantText, toolDisclosure: toolDisclosure }), placed.after.get(item.id)?.map(({ entryId, cell }) => (_jsx(ClientRows, { entryId: entryId, rows: cell.rows, actions: clientActions, onOpen: onOpen }, `${entryId}:${cell.id}`)))] }, virtualItem.key));
|
|
213
221
|
}) })) : before.length > 0 ? (before) : hasMoreHistory ? null : ((empty ?? (_jsx("div", { className: "flex h-full items-center justify-center text-center", children: _jsxs("div", { children: [_jsx("p", { className: "mb-1 text-xs text-base-content", children: "Ready" }), _jsx("p", { className: "text-[10px] text-dim", children: "What would you like to build?" })] }) })))) }), count > 0 ? (_jsx(ScrollToBottomBlob, { direction: "forward", unread: unread, focusAfterJump: focusAfterJump, onJump: jumpToBottom, scrollerRef: scrollerRef })) : null] }));
|
|
214
222
|
}
|
|
215
223
|
/** The owner's queued state and independently contributed waiting controls. */
|
|
@@ -242,6 +250,7 @@ function ConversationComposer({ visitor, textareaRef, composerActionVariant, sla
|
|
|
242
250
|
}, [composer.cancel]);
|
|
243
251
|
const composerProps = {
|
|
244
252
|
queuedRecall: visitor === undefined ? composer : undefined,
|
|
253
|
+
onImagesChange: visitor === undefined ? composer.setImages : undefined,
|
|
245
254
|
statusChips: contributions
|
|
246
255
|
.flatMap(({ value }) => value.chips)
|
|
247
256
|
.sort((left, right) => left.order - right.order)
|
|
@@ -270,6 +279,6 @@ function ConversationComposer({ visitor, textareaRef, composerActionVariant, sla
|
|
|
270
279
|
return _jsx(ChatComposer, { ...composerProps });
|
|
271
280
|
}
|
|
272
281
|
/** Standalone typed composer and exact-message timeline. */
|
|
273
|
-
export function Parakeet({ authorOf, disambiguateAuthors, visitor, markdownPolicy = "session", composerActionVariant, empty, session, clients, renderToolActions, renderAssistantAttachment, formatAssistantText, commands, onLink, onOpen, }) {
|
|
274
|
-
return (_jsx(MarkdownPolicyContext.Provider, { value: markdownPolicy, children: _jsx(ParakeetProvider, { session: session, clients: clients, authorOf: authorOf, children: _jsx(ParakeetView, { disambiguateAuthors: disambiguateAuthors, visitor: visitor, composerActionVariant: composerActionVariant, empty: empty, renderToolActions: renderToolActions, renderAssistantAttachment: renderAssistantAttachment, formatAssistantText: formatAssistantText, commands: commands, onLink: onLink, onOpen: onOpen }) }) }));
|
|
282
|
+
export function Parakeet({ authorOf, disambiguateAuthors, visitor, markdownPolicy = "session", composerActionVariant, empty, session, clients, renderToolActions, claimsToolMedia, renderAssistantAttachment, formatAssistantText, commands, onLink, onOpen, }) {
|
|
283
|
+
return (_jsx(MarkdownPolicyContext.Provider, { value: markdownPolicy, children: _jsx(ParakeetProvider, { session: session, clients: clients, authorOf: authorOf, children: _jsx(ParakeetView, { disambiguateAuthors: disambiguateAuthors, visitor: visitor, composerActionVariant: composerActionVariant, empty: empty, renderToolActions: renderToolActions, claimsToolMedia: claimsToolMedia, renderAssistantAttachment: renderAssistantAttachment, formatAssistantText: formatAssistantText, commands: commands, onLink: onLink, onOpen: onOpen }) }) }));
|
|
275
284
|
}
|
package/dist/session-commands.js
CHANGED
|
@@ -2,6 +2,7 @@ import { nanoid } from "@fungi.computer/cairn";
|
|
|
2
2
|
import { ShiitakeError } from "@fungi.computer/shiitake";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { buildDurableTimeline } from "./core/build-timeline.js";
|
|
5
|
+
import { formatWhen, oneLine } from "./core/picker-text.js";
|
|
5
6
|
import { sessionActivityStatus, sessionViewState, } from "./core/session-view-state.js";
|
|
6
7
|
/** A choice prompt holds at most 256 options; the default read already bounds this. */
|
|
7
8
|
const maxForkOptions = 200;
|
|
@@ -132,12 +133,15 @@ export function contributeSessionCommands(commands, session, options) {
|
|
|
132
133
|
if (interaction === undefined)
|
|
133
134
|
throw new Error("Forking a Session needs a person.");
|
|
134
135
|
const ordered = [...candidates].reverse().slice(0, maxForkOptions);
|
|
136
|
+
const now = Date.now();
|
|
135
137
|
const answer = await interaction.prompt({
|
|
136
138
|
type: "choice",
|
|
137
|
-
message: "Fork from message",
|
|
139
|
+
message: "Fork from which message?",
|
|
140
|
+
summary: "Start a new conversation from your message.",
|
|
138
141
|
options: ordered.map((item) => ({
|
|
139
142
|
value: item.id,
|
|
140
|
-
label: item.text
|
|
143
|
+
label: oneLine(item.text, 44) || "(empty message)",
|
|
144
|
+
description: item.at === undefined ? undefined : formatWhen(item.at, now),
|
|
141
145
|
})),
|
|
142
146
|
});
|
|
143
147
|
const forked = await interaction.run(async () => {
|
package/dist/store/types.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export type LocalPromptAdmission = Readonly<{
|
|
|
51
51
|
/** Existing Session delivery intent. */
|
|
52
52
|
delivery: PromptDelivery;
|
|
53
53
|
/** Current local admission result. */
|
|
54
|
-
status: "admitting" | "accepted" | "failed";
|
|
54
|
+
status: "queued-local" | "admitting" | "accepted" | "failed";
|
|
55
55
|
/** User-presentable admission error when status is failed. */
|
|
56
56
|
error?: string;
|
|
57
57
|
}>;
|
|
@@ -73,6 +73,10 @@ export type AssistantMessageItem = Readonly<{
|
|
|
73
73
|
text: string;
|
|
74
74
|
/** Pi's private failed-turn error text; never a terminal or delegation diagnostic. */
|
|
75
75
|
providerError?: string;
|
|
76
|
+
/** Short assistant output retained before a provider failure, not provider prose. */
|
|
77
|
+
errorDetail?: string;
|
|
78
|
+
/** Consecutive failures with the same visible headline and detail. */
|
|
79
|
+
repeatCount?: number;
|
|
76
80
|
/** Assistant reasoning text, when supplied by the model. */
|
|
77
81
|
thinking?: string;
|
|
78
82
|
/** Usage carried by the exact assistant message. */
|