@jini-ai/chat 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/core/ag-ui/entry.d.ts +33 -0
- package/dist/core/ag-ui/entry.js +80 -0
- package/dist/core/agentic/chat-capabilities.d.ts +2 -4
- package/dist/core/agentic/index.d.ts +4 -17
- package/dist/core/agentic/index.js +4 -17
- package/dist/core/assistant-client/a2ui-action-poster.d.ts +46 -0
- package/dist/core/assistant-client/a2ui-action-poster.js +113 -0
- package/dist/core/assistant-client/ag-ui-transport.d.ts +68 -0
- package/dist/core/assistant-client/ag-ui-transport.js +147 -0
- package/dist/core/assistant-client/attachment-liveness.d.ts +37 -0
- package/dist/core/assistant-client/attachment-liveness.js +93 -0
- package/dist/core/assistant-client/durable-run-subscription.d.ts +39 -0
- package/dist/core/assistant-client/durable-run-subscription.js +96 -0
- package/dist/core/assistant-client/entry.d.ts +13 -0
- package/dist/core/assistant-client/entry.js +7 -0
- package/dist/core/assistant-client/messages.d.ts +73 -0
- package/dist/core/assistant-client/messages.js +68 -0
- package/dist/core/assistant-client/transport.d.ts +164 -0
- package/dist/core/assistant-client/transport.js +734 -0
- package/dist/core/browser/page-actions.d.ts +121 -0
- package/dist/core/browser/page-actions.js +113 -0
- package/dist/core/composer-history.d.ts +54 -0
- package/dist/core/composer-history.js +57 -0
- package/dist/core/durable-projection.d.ts +28 -0
- package/dist/core/durable-projection.js +71 -0
- package/dist/core/durable-runs/continuation.d.ts +18 -0
- package/dist/core/durable-runs/continuation.js +61 -0
- package/dist/core/durable-runs/ledger-contracts.d.ts +53 -0
- package/dist/core/durable-runs/ledger-contracts.js +2 -0
- package/dist/core/durable-runs/policies.d.ts +6 -0
- package/dist/core/durable-runs/policies.js +6 -0
- package/dist/core/durable-runs/ports.d.ts +123 -0
- package/dist/core/durable-runs/ports.js +2 -0
- package/dist/core/durable-runs/run-contracts.d.ts +17 -0
- package/dist/core/durable-runs/run-contracts.js +2 -0
- package/dist/core/durable-runs/run-lock.d.ts +4 -0
- package/dist/core/durable-runs/run-lock.js +4 -0
- package/dist/core/index.d.ts +9 -19
- package/dist/core/index.js +8 -19
- package/dist/core/messages.d.ts +2 -0
- package/dist/core/question-form/format.d.ts +0 -9
- package/dist/core/question-form/format.js +16 -2
- package/dist/core/question-form/normalize.js +1 -0
- package/dist/core/question-form/secret-answers.d.ts +38 -0
- package/dist/core/question-form/secret-answers.js +45 -0
- package/dist/core/question-form/types.d.ts +2 -0
- package/dist/core/question-form.d.ts +1 -0
- package/dist/core/question-form.js +1 -0
- package/dist/core/run-events/entry.d.ts +208 -16
- package/dist/core/run-events/entry.js +273 -39
- package/dist/core/transcript.d.ts +3 -2
- package/dist/core/transcript.js +19 -3
- package/dist/core/transport.d.ts +4 -0
- package/dist/core/user-text-redaction.d.ts +56 -0
- package/dist/core/user-text-redaction.js +89 -0
- package/dist/embed-widget/browser/page-actions.d.ts +1 -120
- package/dist/embed-widget/browser/page-actions.js +2 -112
- package/dist/embed-widget/react/embed.d.ts +1 -1
- package/dist/embed-widget/react/embed.js +1 -1
- package/dist/react/artifact-types.d.ts +9 -15
- package/dist/react/components/Composer.js +6 -0
- package/dist/react/components/MessageRow.js +24 -11
- package/dist/react/components/QuestionForm.d.ts +2 -1
- package/dist/react/components/QuestionForm.js +18 -12
- package/dist/react/components/QuestionsPanel.d.ts +3 -2
- package/dist/react/components/QuestionsPanel.js +2 -2
- package/dist/react/features/chat-pane/agent-tools.d.ts +1 -1
- package/dist/react/features/chat-pane/agent-tools.js +0 -1
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +5 -2
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +9 -4
- package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +2 -1
- package/dist/react/features/chat-pane/create-typed-answer-poster.js +33 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +3 -2
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +7 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +38 -23
- package/dist/react/features/chat-pane/model-options.d.ts +10 -0
- package/dist/react/features/chat-pane/model-options.js +51 -0
- package/dist/react/features/chat-pane/rules.d.ts +3 -2
- package/dist/react/features/chat-pane/rules.js +12 -4
- package/dist/react/features/chat-pane/types.d.ts +30 -1
- package/dist/react/hooks/assistant-chats-contracts.d.ts +164 -0
- package/dist/react/hooks/assistant-chats-contracts.js +2 -0
- package/dist/react/hooks/assistant-chats.d.ts +7 -0
- package/dist/react/hooks/assistant-chats.js +960 -0
- package/dist/react/hooks/attachment-preview-cache.d.ts +1 -1
- package/dist/react/hooks/attachment-preview-cache.js +1 -1
- package/dist/react/hooks/composer-draft-cache.d.ts +2 -2
- package/dist/react/hooks/composer-draft-cache.js +2 -2
- package/dist/react/hooks/composer-history-storage.d.ts +8 -0
- package/dist/react/hooks/composer-history-storage.js +40 -0
- package/dist/react/hooks/fab-position.d.ts +90 -0
- package/dist/react/hooks/fab-position.js +271 -0
- package/dist/react/hooks/useComposer.d.ts +10 -1
- package/dist/react/hooks/useComposer.js +14 -2
- package/dist/react/hooks/useComposerHistory.hooks.d.ts +15 -0
- package/dist/react/hooks/useComposerHistory.hooks.js +90 -0
- package/dist/react/hooks/useComposerHistoryKeys.hooks.d.ts +9 -0
- package/dist/react/hooks/useComposerHistoryKeys.hooks.js +90 -0
- package/dist/react/hooks/useConversation.d.ts +4 -1
- package/dist/react/hooks/useConversation.js +20 -14
- package/dist/react/hooks/useRunStream.js +12 -2
- package/dist/react/hooks/useUserTextGuard.d.ts +11 -0
- package/dist/react/hooks/useUserTextGuard.js +19 -0
- package/dist/react/index.d.ts +14 -21
- package/dist/react/index.js +8 -0
- package/dist/server/durable-runs/accept.d.ts +25 -0
- package/dist/server/durable-runs/accept.js +21 -0
- package/dist/server/durable-runs/entry.d.ts +5 -0
- package/dist/server/durable-runs/entry.js +4 -0
- package/dist/server/durable-runs/recover.d.ts +5 -0
- package/dist/server/durable-runs/recover.js +147 -0
- package/dist/server/run-finalizer/ports.d.ts +2 -16
- package/dist/store/legacy/sqlite/messages.d.ts +24 -18
- package/dist/store/legacy/sqlite/messages.js +12 -2
- package/dist/store/pglite/index.d.ts +2 -1
- package/dist/store/postgres/index.d.ts +2 -1
- package/dist/store/runtime/chat-expiry-sweep.d.ts +40 -0
- package/dist/store/runtime/chat-expiry-sweep.js +70 -0
- package/dist/store/runtime/chat-history-store.d.ts +20 -0
- package/dist/store/runtime/chat-history-store.js +50 -0
- package/dist/store/runtime/durable-run-store.d.ts +21 -0
- package/dist/store/runtime/durable-run-store.js +342 -0
- package/dist/store/runtime/entry.d.ts +7 -0
- package/dist/store/runtime/entry.js +6 -0
- package/dist/store/runtime/kernel.d.ts +29 -0
- package/dist/store/runtime/kernel.js +2 -0
- package/dist/store/runtime/run-guarded-chat-store.d.ts +10 -0
- package/dist/store/runtime/run-guarded-chat-store.js +42 -0
- package/dist/store/runtime/run-ledger.d.ts +15 -0
- package/dist/store/runtime/run-ledger.js +206 -0
- package/dist/store/sql/boundary.d.ts +6 -0
- package/dist/store/sql/boundary.js +7 -1
- package/dist/store/sql/store.d.ts +3 -2
- package/dist/store/sql/store.js +9 -2
- package/dist/store/sqlite/index.d.ts +3 -2
- package/package.json +75 -10
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** Resolved target supplied by the host; the client never derives a path from a title. */
|
|
2
|
+
export interface ResolvedPageTarget {
|
|
3
|
+
readonly slug: string;
|
|
4
|
+
readonly title: string;
|
|
5
|
+
readonly path: string;
|
|
6
|
+
}
|
|
7
|
+
export type PageAction = {
|
|
8
|
+
readonly type: 'navigate';
|
|
9
|
+
readonly target: ResolvedPageTarget;
|
|
10
|
+
readonly auto: boolean;
|
|
11
|
+
} | {
|
|
12
|
+
readonly type: 'scroll_to' | 'highlight';
|
|
13
|
+
readonly target: ResolvedPageTarget;
|
|
14
|
+
};
|
|
15
|
+
export type NavigateAction = Extract<PageAction, {
|
|
16
|
+
type: 'navigate';
|
|
17
|
+
}>;
|
|
18
|
+
export type NonNavigateAction = Exclude<PageAction, {
|
|
19
|
+
type: 'navigate';
|
|
20
|
+
}>;
|
|
21
|
+
export interface DirectiveCarryingEvent {
|
|
22
|
+
readonly kind: string;
|
|
23
|
+
readonly name?: string;
|
|
24
|
+
readonly data?: unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface SplitPageActions {
|
|
27
|
+
readonly navigate: NavigateAction | null;
|
|
28
|
+
readonly other: NonNavigateAction | null;
|
|
29
|
+
}
|
|
30
|
+
/** Narrows an untrusted, bare queued action. URL authorization remains a separate host policy. O(1). */
|
|
31
|
+
export declare function isQueuedPageAction(args: {
|
|
32
|
+
value: unknown;
|
|
33
|
+
}): args is {
|
|
34
|
+
value: PageAction;
|
|
35
|
+
};
|
|
36
|
+
/** Narrows an untrusted directive envelope, dropping unknown kinds. O(1). */
|
|
37
|
+
export declare function isPageActionDirective(args: {
|
|
38
|
+
value: unknown;
|
|
39
|
+
}): args is {
|
|
40
|
+
value: {
|
|
41
|
+
kind: 'page_action';
|
|
42
|
+
action: PageAction;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
/** Preserves valid action order while ignoring unrelated events. O(events) time and output space. */
|
|
46
|
+
export declare function extractPageActions({ events, directiveEventName }: {
|
|
47
|
+
events: readonly DirectiveCarryingEvent[] | undefined;
|
|
48
|
+
directiveEventName: string;
|
|
49
|
+
}): PageAction[];
|
|
50
|
+
/** Returns only the first navigation and first page treatment. O(actions), O(1) extra space. */
|
|
51
|
+
export declare function splitPageActions({ actions }: {
|
|
52
|
+
actions: readonly PageAction[];
|
|
53
|
+
}): SplitPageActions;
|
|
54
|
+
export interface TargetSelectionPolicy {
|
|
55
|
+
readonly candidateSelector: string;
|
|
56
|
+
readonly excludedAncestorSelector: string;
|
|
57
|
+
}
|
|
58
|
+
/** Exact title match first, case-insensitive fallback; never searches excluded UI. O(candidates). */
|
|
59
|
+
export declare function findTargetElement({ title, root, selection }: {
|
|
60
|
+
title: string;
|
|
61
|
+
root: Pick<ParentNode, 'querySelectorAll'>;
|
|
62
|
+
selection: TargetSelectionPolicy;
|
|
63
|
+
}): Element | null;
|
|
64
|
+
export interface PageActionTimerPort {
|
|
65
|
+
schedule(args: {
|
|
66
|
+
callback: () => void;
|
|
67
|
+
delayMs: number;
|
|
68
|
+
}): unknown;
|
|
69
|
+
cancel(args: {
|
|
70
|
+
handle: unknown;
|
|
71
|
+
}): void;
|
|
72
|
+
}
|
|
73
|
+
export interface PageActionPort {
|
|
74
|
+
allows(args: {
|
|
75
|
+
action: PageAction;
|
|
76
|
+
}): boolean;
|
|
77
|
+
navigate(args: {
|
|
78
|
+
action: NavigateAction;
|
|
79
|
+
}): boolean;
|
|
80
|
+
execute(args: {
|
|
81
|
+
action: NonNavigateAction;
|
|
82
|
+
}): boolean;
|
|
83
|
+
clearHighlight(args: Record<string, never>): void;
|
|
84
|
+
}
|
|
85
|
+
export interface BrowserPageActionController extends PageActionPort {
|
|
86
|
+
applyHighlight(args: {
|
|
87
|
+
element: Element;
|
|
88
|
+
}): void;
|
|
89
|
+
scrollToElement(args: {
|
|
90
|
+
element: Element;
|
|
91
|
+
}): void;
|
|
92
|
+
}
|
|
93
|
+
export interface BrowserPageActionsArgs {
|
|
94
|
+
readonly root: Pick<ParentNode, 'querySelectorAll'>;
|
|
95
|
+
readonly selection: TargetSelectionPolicy;
|
|
96
|
+
readonly allowPath: (args: {
|
|
97
|
+
path: string;
|
|
98
|
+
}) => boolean;
|
|
99
|
+
readonly navigate: (args: {
|
|
100
|
+
path: string;
|
|
101
|
+
}) => void;
|
|
102
|
+
readonly reducedMotion: (args: Record<string, never>) => boolean;
|
|
103
|
+
readonly scroll: (args: {
|
|
104
|
+
element: Element;
|
|
105
|
+
options: ScrollIntoViewOptions;
|
|
106
|
+
}) => void;
|
|
107
|
+
readonly timers: PageActionTimerPort;
|
|
108
|
+
readonly highlightClass: string;
|
|
109
|
+
readonly fadeAnimationName: string;
|
|
110
|
+
readonly cleanupFallbackMs: number;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Creates page effects with injected DOM, navigation, URL policy, scroll and timers.
|
|
114
|
+
* At most one highlight belongs to this instance; cleanup and navigation remove it.
|
|
115
|
+
* URL policy is checked before queueing, navigation or page effects. Stale timers and descendant
|
|
116
|
+
* animation events cannot remove the current highlight. Navigation paths are resolved by the host,
|
|
117
|
+
* never guessed from a title; each controller owns its mutable highlight state.
|
|
118
|
+
* @complexity Execute O(candidates); other operations O(1), with one timer/listener retained.
|
|
119
|
+
*/
|
|
120
|
+
export declare function createBrowserPageActions(args: BrowserPageActionsArgs): BrowserPageActionController;
|
|
121
|
+
//# sourceMappingURL=page-actions.d.ts.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
function record(value) {
|
|
2
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function target(value) {
|
|
5
|
+
return record(value) && typeof value.slug === 'string' && typeof value.title === 'string' && typeof value.path === 'string';
|
|
6
|
+
}
|
|
7
|
+
/** Narrows an untrusted, bare queued action. URL authorization remains a separate host policy. O(1). */
|
|
8
|
+
export function isQueuedPageAction(args) {
|
|
9
|
+
const { value } = args;
|
|
10
|
+
if (!record(value) || !target(value.target))
|
|
11
|
+
return false;
|
|
12
|
+
return value.type === 'navigate' ? typeof value.auto === 'boolean' : value.type === 'scroll_to' || value.type === 'highlight';
|
|
13
|
+
}
|
|
14
|
+
/** Narrows an untrusted directive envelope, dropping unknown kinds. O(1). */
|
|
15
|
+
export function isPageActionDirective(args) {
|
|
16
|
+
const { value } = args;
|
|
17
|
+
return record(value) && value.kind === 'page_action' && isQueuedPageAction({ value: value.action });
|
|
18
|
+
}
|
|
19
|
+
/** Preserves valid action order while ignoring unrelated events. O(events) time and output space. */
|
|
20
|
+
export function extractPageActions({ events, directiveEventName }) {
|
|
21
|
+
const actions = [];
|
|
22
|
+
for (const event of events ?? []) {
|
|
23
|
+
const envelope = { value: event.data };
|
|
24
|
+
if (event.kind === 'ext' && event.name === directiveEventName && isPageActionDirective(envelope)) {
|
|
25
|
+
actions.push(envelope.value.action);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return actions;
|
|
29
|
+
}
|
|
30
|
+
/** Returns only the first navigation and first page treatment. O(actions), O(1) extra space. */
|
|
31
|
+
export function splitPageActions({ actions }) {
|
|
32
|
+
return {
|
|
33
|
+
navigate: actions.find((a) => a.type === 'navigate') ?? null,
|
|
34
|
+
other: actions.find((a) => a.type !== 'navigate') ?? null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Exact title match first, case-insensitive fallback; never searches excluded UI. O(candidates). */
|
|
38
|
+
export function findTargetElement({ title, root, selection }) {
|
|
39
|
+
const needle = title.trim();
|
|
40
|
+
if (!needle)
|
|
41
|
+
return null;
|
|
42
|
+
const candidates = Array.from(root.querySelectorAll(selection.candidateSelector))
|
|
43
|
+
.filter(el => !el.closest(selection.excludedAncestorSelector));
|
|
44
|
+
return candidates.find(el => (el.textContent ?? '').trim() === needle)
|
|
45
|
+
?? candidates.find(el => (el.textContent ?? '').trim().toLowerCase() === needle.toLowerCase()) ?? null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates page effects with injected DOM, navigation, URL policy, scroll and timers.
|
|
49
|
+
* At most one highlight belongs to this instance; cleanup and navigation remove it.
|
|
50
|
+
* URL policy is checked before queueing, navigation or page effects. Stale timers and descendant
|
|
51
|
+
* animation events cannot remove the current highlight. Navigation paths are resolved by the host,
|
|
52
|
+
* never guessed from a title; each controller owns its mutable highlight state.
|
|
53
|
+
* @complexity Execute O(candidates); other operations O(1), with one timer/listener retained.
|
|
54
|
+
*/
|
|
55
|
+
export function createBrowserPageActions(args) {
|
|
56
|
+
if (!Number.isFinite(args.cleanupFallbackMs) || args.cleanupFallbackMs < 0)
|
|
57
|
+
throw new Error('Invalid highlight deadline');
|
|
58
|
+
let current = null;
|
|
59
|
+
const clearHighlight = (_args) => {
|
|
60
|
+
if (!current)
|
|
61
|
+
return;
|
|
62
|
+
const old = current;
|
|
63
|
+
current = null;
|
|
64
|
+
args.timers.cancel({ handle: old.handle });
|
|
65
|
+
old.element.removeEventListener('animationend', old.onEnd);
|
|
66
|
+
old.element.classList.remove(args.highlightClass);
|
|
67
|
+
};
|
|
68
|
+
function highlight(element) {
|
|
69
|
+
if (current?.element === element)
|
|
70
|
+
return;
|
|
71
|
+
clearHighlight({});
|
|
72
|
+
const onEnd = event => {
|
|
73
|
+
if (event.target === element && event.animationName === args.fadeAnimationName)
|
|
74
|
+
clearHighlight({});
|
|
75
|
+
};
|
|
76
|
+
element.classList.add(args.highlightClass);
|
|
77
|
+
element.addEventListener('animationend', onEnd);
|
|
78
|
+
// Identity check makes even a late callback from a canceled timer harmless.
|
|
79
|
+
const handle = args.timers.schedule({ delayMs: args.cleanupFallbackMs, callback: () => {
|
|
80
|
+
if (current?.element === element && current.onEnd === onEnd)
|
|
81
|
+
clearHighlight({});
|
|
82
|
+
} });
|
|
83
|
+
current = { element, handle, onEnd };
|
|
84
|
+
}
|
|
85
|
+
const scrollToElement = ({ element }) => {
|
|
86
|
+
args.scroll({ element, options: { behavior: args.reducedMotion({}) ? 'auto' : 'smooth', block: 'center' } });
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
applyHighlight({ element }) { highlight(element); },
|
|
90
|
+
scrollToElement,
|
|
91
|
+
allows({ action }) { return args.allowPath({ path: action.target.path }); },
|
|
92
|
+
navigate({ action }) {
|
|
93
|
+
if (!args.allowPath({ path: action.target.path }))
|
|
94
|
+
return false;
|
|
95
|
+
clearHighlight({});
|
|
96
|
+
args.navigate({ path: action.target.path });
|
|
97
|
+
return true;
|
|
98
|
+
},
|
|
99
|
+
execute({ action }) {
|
|
100
|
+
if (!args.allowPath({ path: action.target.path }))
|
|
101
|
+
return false;
|
|
102
|
+
const element = findTargetElement({ title: action.target.title, root: args.root, selection: args.selection });
|
|
103
|
+
if (!element)
|
|
104
|
+
return false;
|
|
105
|
+
scrollToElement({ element });
|
|
106
|
+
if (action.type === 'highlight')
|
|
107
|
+
highlight(element);
|
|
108
|
+
return true;
|
|
109
|
+
},
|
|
110
|
+
clearHighlight,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=page-actions.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Text-only prompt recall. Browsing owns a snapshot so incoming turns cannot move its cursor. */
|
|
2
|
+
export interface ComposerHistoryState {
|
|
3
|
+
entries: readonly string[];
|
|
4
|
+
index: number | null;
|
|
5
|
+
draft: string | null;
|
|
6
|
+
}
|
|
7
|
+
export interface ComposerHistoryStoragePort {
|
|
8
|
+
read: (required: {
|
|
9
|
+
scope: string;
|
|
10
|
+
}, optional: {}) => Promise<readonly string[]>;
|
|
11
|
+
write: (required: {
|
|
12
|
+
scope: string;
|
|
13
|
+
entries: readonly string[];
|
|
14
|
+
}, optional: {}) => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export interface ComposerHistoryKeyInput {
|
|
17
|
+
key: string;
|
|
18
|
+
text: string;
|
|
19
|
+
selectionStart: number;
|
|
20
|
+
selectionEnd: number;
|
|
21
|
+
shiftKey?: boolean;
|
|
22
|
+
ctrlKey?: boolean;
|
|
23
|
+
altKey?: boolean;
|
|
24
|
+
metaKey?: boolean;
|
|
25
|
+
isComposing?: boolean;
|
|
26
|
+
popupOpen?: boolean;
|
|
27
|
+
/** A DOM host can refine logical newline boundaries with measured soft-wrap boundaries. */
|
|
28
|
+
firstLine?: boolean;
|
|
29
|
+
lastLine?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function normalizeComposerHistory({ entries }: {
|
|
32
|
+
entries: readonly string[];
|
|
33
|
+
}, { cap }?: {
|
|
34
|
+
cap?: number;
|
|
35
|
+
}): string[];
|
|
36
|
+
export declare function mergeComposerHistory({ recent, current }: {
|
|
37
|
+
recent: readonly string[];
|
|
38
|
+
current: readonly string[];
|
|
39
|
+
}, { cap }?: {
|
|
40
|
+
cap?: number;
|
|
41
|
+
}): string[];
|
|
42
|
+
export declare function createComposerHistoryState(_required: {}, _optional?: {}): ComposerHistoryState;
|
|
43
|
+
export declare function composerHistoryKeyAction(input: ComposerHistoryKeyInput, _optional?: {}): 'previous' | 'next' | 'restore' | null;
|
|
44
|
+
export declare function transitionComposerHistory({ state, action, text, entries }: {
|
|
45
|
+
state: ComposerHistoryState;
|
|
46
|
+
action: 'previous' | 'next' | 'restore' | 'edit' | 'reset';
|
|
47
|
+
text: string;
|
|
48
|
+
entries: readonly string[];
|
|
49
|
+
}, _optional?: {}): {
|
|
50
|
+
state: ComposerHistoryState;
|
|
51
|
+
text: string;
|
|
52
|
+
handled: boolean;
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=composer-history.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export function normalizeComposerHistory({ entries }, { cap = 100 } = {}) {
|
|
2
|
+
const result = [];
|
|
3
|
+
for (const text of entries) {
|
|
4
|
+
// Blank/attachment-only turns are not useful prompts; meaningful whitespace stays exact.
|
|
5
|
+
if (text.trim() && text !== result[result.length - 1])
|
|
6
|
+
result.push(text);
|
|
7
|
+
}
|
|
8
|
+
return result.slice(-Math.max(1, Math.floor(cap)));
|
|
9
|
+
}
|
|
10
|
+
export function mergeComposerHistory({ recent, current }, { cap = 100 } = {}) {
|
|
11
|
+
const left = normalizeComposerHistory({ entries: recent }, { cap });
|
|
12
|
+
const right = normalizeComposerHistory({ entries: current }, { cap });
|
|
13
|
+
// A mounted conversation is usually already the recent list's suffix. Do not append its
|
|
14
|
+
// entire transcript again on every streamed render; preserve nonconsecutive repetitions.
|
|
15
|
+
let overlap = Math.min(left.length, right.length);
|
|
16
|
+
while (overlap > 0 && !right.slice(0, overlap).every((text, i) => text === left[left.length - overlap + i]))
|
|
17
|
+
overlap -= 1;
|
|
18
|
+
return normalizeComposerHistory({ entries: [...left, ...right.slice(overlap)] }, { cap });
|
|
19
|
+
}
|
|
20
|
+
export function createComposerHistoryState(_required, _optional = {}) {
|
|
21
|
+
return { entries: [], index: null, draft: null };
|
|
22
|
+
}
|
|
23
|
+
export function composerHistoryKeyAction(input, _optional = {}) {
|
|
24
|
+
if (input.shiftKey || input.ctrlKey || input.altKey || input.metaKey || input.isComposing || input.popupOpen)
|
|
25
|
+
return null;
|
|
26
|
+
if (input.key === 'Escape')
|
|
27
|
+
return 'restore';
|
|
28
|
+
const start = Math.min(input.selectionStart, input.selectionEnd);
|
|
29
|
+
const end = Math.max(input.selectionStart, input.selectionEnd);
|
|
30
|
+
if (input.text.slice(start, end).includes('\n'))
|
|
31
|
+
return null;
|
|
32
|
+
const firstLine = !input.text.slice(0, end).includes('\n') && input.firstLine !== false;
|
|
33
|
+
const lastLine = !input.text.slice(start).includes('\n') && input.lastLine !== false;
|
|
34
|
+
if (input.key === 'ArrowUp' && firstLine)
|
|
35
|
+
return 'previous';
|
|
36
|
+
if (input.key === 'ArrowDown' && lastLine)
|
|
37
|
+
return 'next';
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
export function transitionComposerHistory({ state, action, text, entries }, _optional = {}) {
|
|
41
|
+
if (action === 'edit' || action === 'reset') {
|
|
42
|
+
return { state: createComposerHistoryState({}), text, handled: false };
|
|
43
|
+
}
|
|
44
|
+
if (state.index === null) {
|
|
45
|
+
if (action !== 'previous' || entries.length === 0)
|
|
46
|
+
return { state, text, handled: false };
|
|
47
|
+
const snapshot = [...entries];
|
|
48
|
+
const index = snapshot.length - 1;
|
|
49
|
+
return { state: { entries: snapshot, index, draft: text }, text: snapshot[index], handled: true };
|
|
50
|
+
}
|
|
51
|
+
if (action === 'restore' || (action === 'next' && state.index === state.entries.length - 1)) {
|
|
52
|
+
return { state: createComposerHistoryState({}), text: state.draft ?? '', handled: true };
|
|
53
|
+
}
|
|
54
|
+
const index = action === 'previous' ? Math.max(0, state.index - 1) : state.index + 1;
|
|
55
|
+
return { state: { ...state, index }, text: state.entries[index], handled: true };
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=composer-history.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AgentEvent } from './events.js';
|
|
2
|
+
import type { ChatMessage } from './messages.js';
|
|
3
|
+
/** Saved checkpoints can coalesce deltas and a completion may replay only a prefix. Keep the
|
|
4
|
+
* saved projection, merge identifiable events, and append only text beyond its saved prefix. */
|
|
5
|
+
export declare function mergeRunEvents({ saved, incoming }: {
|
|
6
|
+
saved?: readonly AgentEvent[] | undefined;
|
|
7
|
+
incoming: readonly AgentEvent[];
|
|
8
|
+
}, _optional?: {}): AgentEvent[];
|
|
9
|
+
/** A recovered attempt stays visibly in progress until new text or tool work supersedes its marker. */
|
|
10
|
+
export declare function continuingRunNotice({ events, active }: {
|
|
11
|
+
events: readonly AgentEvent[] | undefined;
|
|
12
|
+
active: boolean;
|
|
13
|
+
}, _optional?: {}): string | null;
|
|
14
|
+
export declare function preserveAssistantContent({ saved, incoming }: {
|
|
15
|
+
saved: string;
|
|
16
|
+
incoming: string;
|
|
17
|
+
}, _optional?: {}): string;
|
|
18
|
+
/** Terminal failure belongs to the message. Legacy resend instructions are display-only;
|
|
19
|
+
* changing this copy must never execute or reopen an old terminal run. */
|
|
20
|
+
export declare function terminalMessageNotice({ message }: {
|
|
21
|
+
message: ChatMessage;
|
|
22
|
+
}, _optional?: {}): string | null;
|
|
23
|
+
/** The first answer has no earlier segment to divide, even if startup was retried. Only saved
|
|
24
|
+
* visible text can justify a Continued divider; the recovery marker still records the attempt. */
|
|
25
|
+
export declare function recoveredRunEvents({ saved }: {
|
|
26
|
+
saved: readonly AgentEvent[];
|
|
27
|
+
}, _optional?: {}): AgentEvent[];
|
|
28
|
+
//# sourceMappingURL=durable-projection.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
function identity(event) {
|
|
2
|
+
const wire = event;
|
|
3
|
+
if (wire.eventId !== undefined)
|
|
4
|
+
return `event:${wire.eventId}`;
|
|
5
|
+
if (wire.cursor !== undefined)
|
|
6
|
+
return `cursor:${wire.cursor}`;
|
|
7
|
+
if (event.kind === 'tool_use')
|
|
8
|
+
return `use:${event.id}`;
|
|
9
|
+
if (event.kind === 'tool_result')
|
|
10
|
+
return `result:${event.toolUseId}`;
|
|
11
|
+
return JSON.stringify(event);
|
|
12
|
+
}
|
|
13
|
+
function textOf(events) {
|
|
14
|
+
return events.filter((event) => event.kind === 'text').map((event) => event.text).join('');
|
|
15
|
+
}
|
|
16
|
+
/** Saved checkpoints can coalesce deltas and a completion may replay only a prefix. Keep the
|
|
17
|
+
* saved projection, merge identifiable events, and append only text beyond its saved prefix. */
|
|
18
|
+
export function mergeRunEvents({ saved, incoming }, _optional = {}) {
|
|
19
|
+
const previous = saved ?? [];
|
|
20
|
+
const result = [...previous];
|
|
21
|
+
const keys = new Set(previous.map(identity));
|
|
22
|
+
const before = textOf(previous);
|
|
23
|
+
const after = textOf(incoming);
|
|
24
|
+
let skip = after.startsWith(before) ? before.length : after.length;
|
|
25
|
+
for (const event of incoming) {
|
|
26
|
+
if (event.kind === 'text') {
|
|
27
|
+
const text = event.text.slice(skip);
|
|
28
|
+
skip = Math.max(0, skip - event.text.length);
|
|
29
|
+
if (text)
|
|
30
|
+
result.push({ ...event, text });
|
|
31
|
+
}
|
|
32
|
+
else if (!keys.has(identity(event))) {
|
|
33
|
+
keys.add(identity(event));
|
|
34
|
+
result.push(event);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
/** A recovered attempt stays visibly in progress until new text or tool work supersedes its marker. */
|
|
40
|
+
export function continuingRunNotice({ events, active }, _optional = {}) {
|
|
41
|
+
if (!active)
|
|
42
|
+
return null;
|
|
43
|
+
const list = events ?? [];
|
|
44
|
+
const marker = list.findLastIndex((event) => event.kind === 'status' && event.code === 'run_recovering');
|
|
45
|
+
if (marker < 0)
|
|
46
|
+
return null;
|
|
47
|
+
const progressed = list.slice(marker + 1).some((event) => event.kind === 'text' || event.kind === 'tool_use' || event.kind === 'tool_result');
|
|
48
|
+
return progressed ? null : 'Continuing…';
|
|
49
|
+
}
|
|
50
|
+
export function preserveAssistantContent({ saved, incoming }, _optional = {}) {
|
|
51
|
+
return incoming.length >= saved.length ? incoming : saved;
|
|
52
|
+
}
|
|
53
|
+
/** Terminal failure belongs to the message. Legacy resend instructions are display-only;
|
|
54
|
+
* changing this copy must never execute or reopen an old terminal run. */
|
|
55
|
+
export function terminalMessageNotice({ message }, _optional = {}) {
|
|
56
|
+
if (message.runStatus !== 'failed' && message.runStatus !== 'canceled')
|
|
57
|
+
return null;
|
|
58
|
+
const notice = message.events?.filter((event) => event.kind === 'status' &&
|
|
59
|
+
(event.code === 'run_terminal' || /failed|stopped|restarted|could not|not logged in|cancell?ed/i.test(event.label))).at(-1);
|
|
60
|
+
if (notice?.kind === 'status')
|
|
61
|
+
return [notice.label, notice.detail].filter(Boolean).join(' ').replace(/Send your message again to retry\.?/gi, '').trim();
|
|
62
|
+
return message.runStatus === 'failed' ? 'This turn failed.' : 'Stopped.';
|
|
63
|
+
}
|
|
64
|
+
/** The first answer has no earlier segment to divide, even if startup was retried. Only saved
|
|
65
|
+
* visible text can justify a Continued divider; the recovery marker still records the attempt. */
|
|
66
|
+
export function recoveredRunEvents({ saved }, _optional = {}) {
|
|
67
|
+
const hasAnswer = saved.some(event => event.kind === 'text' && event.text.trim().length > 0);
|
|
68
|
+
return [...saved, ...(hasAnswer ? [{ kind: 'text', text: '\n\n---\n\nContinued\n\n' }] : []),
|
|
69
|
+
{ kind: 'status', code: 'run_recovering', label: 'Continuing…' }];
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=durable-projection.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AgentEvent } from "../index.js";
|
|
2
|
+
import type { AcceptedRunRequest, DurableRun } from "./ports.js";
|
|
3
|
+
export declare const CONTINUATION_DIVIDER = "\n\n---\n\nContinued\n\n";
|
|
4
|
+
export declare const UNKNOWN_MUTATION_ERROR = "This operation may already be done. Its earlier outcome is unknown; verify the current state before repeating it.";
|
|
5
|
+
/** Object-key order and a new tool-use id must not turn the same mutation into a new operation. */
|
|
6
|
+
export declare function canonicalToolInput({ value }: {
|
|
7
|
+
value: unknown;
|
|
8
|
+
}, _optional?: {}): string;
|
|
9
|
+
export declare function hasUnknownToolCall({ events, toolId, input }: {
|
|
10
|
+
events: readonly AgentEvent[];
|
|
11
|
+
toolId: string;
|
|
12
|
+
input: unknown;
|
|
13
|
+
}, _optional?: {}): boolean;
|
|
14
|
+
export declare function continuationRequest({ run, native }: {
|
|
15
|
+
run: DurableRun;
|
|
16
|
+
native: boolean;
|
|
17
|
+
}, _optional?: {}): AcceptedRunRequest;
|
|
18
|
+
//# sourceMappingURL=continuation.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const CONTINUATION_DIVIDER = "\n\n---\n\nContinued\n\n";
|
|
2
|
+
export const UNKNOWN_MUTATION_ERROR = "This operation may already be done. Its earlier outcome is unknown; verify the current state before repeating it.";
|
|
3
|
+
/** Object-key order and a new tool-use id must not turn the same mutation into a new operation. */
|
|
4
|
+
export function canonicalToolInput({ value }, _optional = {}) {
|
|
5
|
+
if (Array.isArray(value))
|
|
6
|
+
return `[${value.map((item) => canonicalToolInput({ value: item }, {})).join(",")}]`;
|
|
7
|
+
if (value !== null && typeof value === "object") {
|
|
8
|
+
const object = value;
|
|
9
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalToolInput({ value: object[key] }, {})}`).join(",")}}`;
|
|
10
|
+
}
|
|
11
|
+
return JSON.stringify(value) ?? "null";
|
|
12
|
+
}
|
|
13
|
+
function delegatedCall(event) {
|
|
14
|
+
const input = event.input;
|
|
15
|
+
if (input && typeof input.toolId === "string" && /execute.*delegated_tool/.test(event.name))
|
|
16
|
+
return { toolId: input.toolId, input: input.input };
|
|
17
|
+
return { toolId: event.name, input: event.input };
|
|
18
|
+
}
|
|
19
|
+
export function hasUnknownToolCall({ events, toolId, input }, _optional = {}) {
|
|
20
|
+
const completed = new Set(events.filter((event) => event.kind === "tool_result").map((event) => event.toolUseId));
|
|
21
|
+
const canonical = canonicalToolInput({ value: input }, {});
|
|
22
|
+
return events.some((event) => {
|
|
23
|
+
if (event.kind !== "tool_use" || completed.has(event.id))
|
|
24
|
+
return false;
|
|
25
|
+
const call = delegatedCall(event);
|
|
26
|
+
return call.toolId === toolId && canonicalToolInput({ value: call.input }, {}) === canonical;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function toolHistory(events) {
|
|
30
|
+
const results = new Map(events.filter((event) => event.kind === "tool_result").map((event) => [event.toolUseId, event]));
|
|
31
|
+
return events.filter((event) => event.kind === "tool_use").map((event) => {
|
|
32
|
+
const result = results.get(event.id);
|
|
33
|
+
return JSON.stringify({ call: delegatedCall(event), result: result ?? "OUTCOME UNKNOWN: verify state before repeating" });
|
|
34
|
+
}).join("\n");
|
|
35
|
+
}
|
|
36
|
+
export function continuationRequest({ run, native }, _optional = {}) {
|
|
37
|
+
const original = JSON.parse(run.request.contextRef);
|
|
38
|
+
// Native sessions may have disappeared on deploy. Reconstruction includes the saved answer
|
|
39
|
+
// and tool results for every runtime; completed side effects must never be silently repeated.
|
|
40
|
+
const transcript = run.transcript.map((message) => `${message.role}: ${message.content}`).join("\n\n");
|
|
41
|
+
const prompt = [
|
|
42
|
+
"Continue the interrupted answer from the saved work below. Preserve completed work. Do not repeat completed mutations.",
|
|
43
|
+
"Calls without a result have an unknown outcome: inspect current state before any retry. Expired plans or approvals must be re-planned and re-approved; never reuse them.",
|
|
44
|
+
"A pending question may be asked again when its process was lost. The interrupted segment remains visible; append the continuation.",
|
|
45
|
+
`Accepted task:\n${String(original.prompt ?? "")}`,
|
|
46
|
+
native ? "Native session is confirmed; continue its saved task." : `Saved conversation:\n${transcript}`,
|
|
47
|
+
`Saved partial answer:\n${run.message.content}`,
|
|
48
|
+
`Saved tool calls and results:\n${toolHistory(run.message.events ?? [])}`,
|
|
49
|
+
].join("\n\n");
|
|
50
|
+
return {
|
|
51
|
+
...run.request,
|
|
52
|
+
contextRef: JSON.stringify({ ...original, prompt, conversationId: run.conversationId,
|
|
53
|
+
assistantMessageId: run.messageId, principalId: run.principalId,
|
|
54
|
+
recoveryMode: native ? "native" : "reconstruction", recoverySessionId: native ? run.sessionId : undefined,
|
|
55
|
+
// Old attachment claims have been consumed. Saved transcript/tool results carry their
|
|
56
|
+
// observations; attempting to claim the same ids again would fail before execution.
|
|
57
|
+
attachmentIds: [],
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=continuation.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ChatMessage } from "../index.js";
|
|
2
|
+
import type { DurableRunStore } from "./ports.js";
|
|
3
|
+
import type { RunRef, RunProgress, RunSettlement } from "./run-contracts.js";
|
|
4
|
+
export type { RunRef, RunProgress, RunSettlement } from "./run-contracts.js";
|
|
5
|
+
/** {@link ChatRunLedger.unlessSettled}'s result: the write's value, or that the run had settled. */
|
|
6
|
+
export type UnlessSettled<T> = {
|
|
7
|
+
readonly written: true;
|
|
8
|
+
readonly value: T;
|
|
9
|
+
} | {
|
|
10
|
+
readonly written: false;
|
|
11
|
+
};
|
|
12
|
+
/** Internal boot recovery, never an HTTP history read. The owner comes from storage, not a client. */
|
|
13
|
+
export interface InterruptedChatTurn {
|
|
14
|
+
readonly principalId: string | null;
|
|
15
|
+
readonly conversationId: string;
|
|
16
|
+
readonly message: ChatMessage;
|
|
17
|
+
}
|
|
18
|
+
export interface ChatRunRecoveryOptions {
|
|
19
|
+
/** True retains the row: a daemon run was adopted with bounded resolution of uncertainty.
|
|
20
|
+
* Called outside storage transactions so a daemon request cannot hold the database lock. */
|
|
21
|
+
readonly recover?: (input: InterruptedChatTurn) => Promise<boolean>;
|
|
22
|
+
}
|
|
23
|
+
export interface ChatRunLedger {
|
|
24
|
+
readonly durable?: DurableRunStore;
|
|
25
|
+
/**
|
|
26
|
+
* Runs `write` only while `run`'s row holds no terminal status (an unknown row, another run id, or
|
|
27
|
+
* a row still `queued`/`running` all count as unsettled), with no settle able to land in between.
|
|
28
|
+
* `write` must reach the chat database through this ledger's kernel (the same connection, on
|
|
29
|
+
* SQLite) so it joins the transaction.
|
|
30
|
+
*/
|
|
31
|
+
unlessSettled<T>(required: {
|
|
32
|
+
run: RunRef;
|
|
33
|
+
write: () => Promise<T>;
|
|
34
|
+
}, optional?: {}): Promise<UnlessSettled<T>>;
|
|
35
|
+
/**
|
|
36
|
+
* Writes the run's final content, events and status — only if the row still belongs to that run
|
|
37
|
+
* and is not yet terminal. Resolves `true` when this call was the one that settled it.
|
|
38
|
+
*/
|
|
39
|
+
settle(settlement: RunSettlement, optional?: {}): Promise<boolean>;
|
|
40
|
+
/**
|
|
41
|
+
* Saves what a still-running run has produced so far (content and events; the status stays), so
|
|
42
|
+
* a process that dies mid-run leaves its partial answer for {@link reconcileInterrupted} to keep.
|
|
43
|
+
* A no-op once the row is terminal or belongs to another run. Resolves `true` when it wrote.
|
|
44
|
+
*/
|
|
45
|
+
checkpoint(progress: RunProgress, optional?: {}): Promise<boolean>;
|
|
46
|
+
/** Serving-only discovery of unfinished rows. The injected recovery coordinator owns adopted
|
|
47
|
+
* runs; rows it cannot adopt are canceled, preserving saved work. Returns handled rows.
|
|
48
|
+
* Export apps must not invoke discovery while a serving daemon is still alive. */
|
|
49
|
+
reconcileInterrupted(required?: {
|
|
50
|
+
now?: number;
|
|
51
|
+
}, optional?: ChatRunRecoveryOptions): Promise<number>;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=ledger-contracts.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const RECOVERY_LIMIT = 3;
|
|
2
|
+
export declare const RECOVERY_WINDOW_MS: number;
|
|
3
|
+
export declare const RUN_STALENESS_MS: number;
|
|
4
|
+
export declare const SAVED_WORK_NOTICE = "The assistant stopped after making no progress. Saved work is above.";
|
|
5
|
+
export declare const EXHAUSTED_NOTICE = "The assistant could not continue this answer. Saved work is above.";
|
|
6
|
+
//# sourceMappingURL=policies.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const RECOVERY_LIMIT = 3;
|
|
2
|
+
export const RECOVERY_WINDOW_MS = 5 * 60_000;
|
|
3
|
+
export const RUN_STALENESS_MS = 30 * 60_000;
|
|
4
|
+
export const SAVED_WORK_NOTICE = "The assistant stopped after making no progress. Saved work is above.";
|
|
5
|
+
export const EXHAUSTED_NOTICE = "The assistant could not continue this answer. Saved work is above.";
|
|
6
|
+
//# sourceMappingURL=policies.js.map
|