@adecore/agents 0.21.0 → 0.21.2
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/async.js +3 -8
- package/dist/chat/chat-core.d.ts +7 -3
- package/dist/chat/chat-core.js +19 -9
- package/dist/chat/chat-log.js +2 -2
- package/dist/chat/chat-session.d.ts +13 -0
- package/dist/chat/chat-session.js +75 -5
- package/dist/chat/chat-store.d.ts +3 -2
- package/dist/chat/chat-store.js +44 -16
- package/dist/chat/chat-title.js +4 -8
- package/dist/chat/claude-backend.d.ts +1 -0
- package/dist/chat/claude-backend.js +8 -6
- package/dist/chat/claude-title.js +1 -1
- package/dist/chat/codex-backend.d.ts +2 -2
- package/dist/chat/codex-backend.js +5 -8
- package/dist/chat/codex-protocol.js +1 -1
- package/dist/chat/codex-thread.js +4 -5
- package/dist/chat/composer-preferences.js +2 -3
- package/dist/chat/limit-resume.js +2 -7
- package/dist/chat/readable-text.d.ts +2 -0
- package/dist/chat/readable-text.js +16 -0
- package/dist/chat/ui-queries.d.ts +12 -1
- package/dist/chat/ui-queries.js +107 -60
- package/dist/chat/visual-store.js +8 -6
- package/dist/context/refusal.js +3 -7
- package/dist/context/verb.js +1 -5
- package/dist/host/agent-host.js +2 -2
- package/dist/host/handlers.js +1 -2
- package/dist/host/wiring.js +1 -1
- package/dist/lineage.js +2 -3
- package/dist/messages/deliver-message.js +6 -11
- package/dist/messages/deliver-notice.js +8 -12
- package/dist/messages/notice-store.js +3 -5
- package/dist/outbox/outbox-worker.js +5 -2
- package/dist/providers/accounts/service.d.ts +2 -1
- package/dist/providers/accounts/service.js +11 -13
- package/dist/providers/claude.js +0 -1
- package/dist/providers/detect.js +2 -10
- package/dist/record-directory.js +0 -1
- package/dist/tasks/task-coordinator.d.ts +1 -0
- package/dist/tasks/task-coordinator.js +40 -36
- package/dist/tasks/task-store.js +1 -1
- package/dist/tasks/task-work.js +2 -2
- package/dist/tasks/waiting-child.js +5 -3
- package/dist/tasks/wake-parent.js +15 -10
- package/dist/tasks/wiring.js +32 -37
- package/dist/usage/aggregate.js +62 -46
- package/dist/usage/exchange.js +1 -1
- package/dist/usage/limits/monitor.d.ts +2 -2
- package/dist/usage/limits/monitor.js +2 -2
- package/dist/usage/limits/probe.js +1 -1
- package/dist/usage/pricing.js +1 -1
- package/dist/usage/roots.js +1 -2
- package/dist/usage/scanner.js +7 -9
- package/dist/usage/usage-service.d.ts +1 -2
- package/dist/usage/usage-service.js +3 -6
- package/package.json +3 -3
- package/src/async.ts +3 -9
- package/src/chat/chat-core.ts +29 -9
- package/src/chat/chat-log.ts +2 -2
- package/src/chat/chat-session.ts +85 -5
- package/src/chat/chat-store.ts +54 -16
- package/src/chat/chat-title.ts +4 -8
- package/src/chat/claude-backend.ts +9 -6
- package/src/chat/claude-title.ts +1 -1
- package/src/chat/claude-transcript.ts +1 -1
- package/src/chat/codex-backend.ts +5 -8
- package/src/chat/codex-protocol.ts +1 -1
- package/src/chat/codex-thread.ts +6 -5
- package/src/chat/composer-preferences.ts +2 -3
- package/src/chat/limit-resume.ts +2 -7
- package/src/chat/readable-text.ts +18 -0
- package/src/chat/thread.ts +1 -1
- package/src/chat/ui-queries.ts +120 -64
- package/src/chat/visual-store.ts +9 -6
- package/src/chat/wake-chat.ts +4 -1
- package/src/context/refusal.ts +3 -7
- package/src/context/verb.ts +1 -5
- package/src/host/agent-host.ts +2 -2
- package/src/host/handlers.ts +1 -2
- package/src/host/wiring.ts +1 -1
- package/src/lineage.ts +2 -3
- package/src/messages/deliver-message.ts +6 -11
- package/src/messages/deliver-notice.ts +8 -12
- package/src/messages/notice-store.ts +3 -5
- package/src/outbox/outbox-worker.ts +5 -2
- package/src/providers/accounts/service.ts +12 -13
- package/src/providers/accounts/variables.ts +1 -0
- package/src/providers/claude.ts +0 -1
- package/src/providers/detect.ts +2 -11
- package/src/record-directory.ts +0 -1
- package/src/skills.ts +0 -1
- package/src/tasks/task-coordinator.ts +41 -35
- package/src/tasks/task-store.ts +1 -1
- package/src/tasks/task-work.ts +2 -2
- package/src/tasks/waiting-child.ts +6 -3
- package/src/tasks/wake-parent.ts +21 -16
- package/src/tasks/wiring.ts +33 -41
- package/src/usage/aggregate.ts +80 -50
- package/src/usage/exchange.ts +1 -1
- package/src/usage/limits/monitor.ts +4 -4
- package/src/usage/limits/probe.ts +2 -2
- package/src/usage/pricing.ts +1 -1
- package/src/usage/roots.ts +1 -2
- package/src/usage/scanner.ts +8 -10
- package/src/usage/usage-service.ts +5 -9
package/dist/async.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
/* The two waits the host does: a pause it can be woken from, and a deadline on somebody else's promise. */
|
|
2
1
|
/*
|
|
3
|
-
* Resolves after `ms`, or at once when the signal is raised. An abort
|
|
4
|
-
*
|
|
5
|
-
* rejection would only be caught and thrown away.
|
|
2
|
+
* Resolves after `ms`, or at once when the signal is raised. An abort resolves instead of rejecting:
|
|
3
|
+
* every caller is a loop that checks the signal itself on its next turn.
|
|
6
4
|
*/
|
|
7
5
|
export function wait(ms, signal) {
|
|
8
6
|
return new Promise((resolve) => {
|
|
@@ -13,10 +11,7 @@ export function wait(ms, signal) {
|
|
|
13
11
|
}, { once: true });
|
|
14
12
|
});
|
|
15
13
|
}
|
|
16
|
-
/*
|
|
17
|
-
* The promise, or `message` when it takes longer than `ms`. The timer is cleared either way, so a
|
|
18
|
-
* call that answers at once does not hold the process for the rest of its deadline.
|
|
19
|
-
*/
|
|
14
|
+
/* The timer is cleared either way, so a call that answers at once does not hold the process until its deadline. */
|
|
20
15
|
export function withTimeout(promise, ms, message) {
|
|
21
16
|
return new Promise((resolve, reject) => {
|
|
22
17
|
const timer = setTimeout(() => reject(new Error(message)), ms);
|
package/dist/chat/chat-core.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type AgentKind, type ChatAttachment, type ChatAttachResult, type ChatAttachmentUpload, type ChatBookmark, type ChatCheckpointDiff, type ChatConfigurePayload, type ChatCreatePayload, type ChatEvent, type ChatHistoryResult, type ChatInfo, type ChatItem, type ChatQueuedMessage, type ChatUiChoicePayload, type ChatUiQueryPayload, type ChatUiLinkPayload, type ChatSkill, type ChatSubagentPayload, type ChatSubagentResult, type ChatTurnItem, type ChatVisual, type ModelSelection, type RuntimeMode } from '@adecore/agent-contracts';
|
|
2
|
-
import { type ChatUiHost } from './ui-queries.ts';
|
|
3
2
|
import { ClientSinks } from '../client-sinks.ts';
|
|
4
3
|
import type { AgentEvent, AgentSink } from '../events.ts';
|
|
5
4
|
import type { OutboxOutcome } from '../outbox/outbox-worker.ts';
|
|
@@ -8,6 +7,7 @@ import type { ProviderRegistry } from '../providers/registry.ts';
|
|
|
8
7
|
import { SkillIndex } from '../skills.ts';
|
|
9
8
|
import type { LimitsUpdate } from '../usage/limits/normalize.ts';
|
|
10
9
|
import type { AttachmentStore } from './attachment-store.ts';
|
|
10
|
+
import { type BackgroundWork } from './background-work.ts';
|
|
11
11
|
import type { BookmarkStore } from './bookmark-store.ts';
|
|
12
12
|
import type { SpawnChatProcess } from './chat-process.ts';
|
|
13
13
|
import { ChatSession, type ChatReferences, type ChatSendExtras, type PromptNotes, type ResumeWords, type TurnCheckpoints } from './chat-session.ts';
|
|
@@ -17,8 +17,8 @@ import type { CodexProcessSpec } from './codex-thread.ts';
|
|
|
17
17
|
import type { CodexClientInfo } from './codex-transport.ts';
|
|
18
18
|
import { ComposerPreferences } from './composer-preferences.ts';
|
|
19
19
|
import { DeltaCoalescer } from './delta-coalescer.ts';
|
|
20
|
-
import { type BackgroundWork } from './background-work.ts';
|
|
21
20
|
import { SubagentReader, type SubagentReaderOptions } from './subagent-reader.ts';
|
|
21
|
+
import { type ChatUiHost } from './ui-queries.ts';
|
|
22
22
|
import type { VisualInput, VisualStore } from './visual-store.ts';
|
|
23
23
|
export interface InterruptedRun {
|
|
24
24
|
chatId: string;
|
|
@@ -27,6 +27,7 @@ export interface InterruptedRun {
|
|
|
27
27
|
}
|
|
28
28
|
export interface ChatCoreOptions {
|
|
29
29
|
intelligentUi?: ChatUiHost;
|
|
30
|
+
uiFenceLanguage?: string;
|
|
30
31
|
providers: ProviderRegistry;
|
|
31
32
|
store?: ChatStore;
|
|
32
33
|
checkpoints?: TurnCheckpoints;
|
|
@@ -190,6 +191,7 @@ export declare class ChatCore {
|
|
|
190
191
|
readonly claudeProjectsDir: string;
|
|
191
192
|
readonly composerPreferences: ComposerPreferences;
|
|
192
193
|
private readonly uiQueries;
|
|
194
|
+
private readonly uiFenceLanguage;
|
|
193
195
|
private readonly checkpoints;
|
|
194
196
|
private readonly instructions;
|
|
195
197
|
private readonly skillIndex;
|
|
@@ -290,6 +292,7 @@ export declare class ChatCore {
|
|
|
290
292
|
conflicted?: boolean | undefined;
|
|
291
293
|
relativePath?: string | undefined;
|
|
292
294
|
label?: string | undefined;
|
|
295
|
+
code?: string | undefined;
|
|
293
296
|
reason?: string | undefined;
|
|
294
297
|
}>;
|
|
295
298
|
queryUi(payload: ChatUiQueryPayload, clientId: string): Promise<{
|
|
@@ -297,9 +300,10 @@ export declare class ChatCore {
|
|
|
297
300
|
value?: unknown;
|
|
298
301
|
readId?: string | undefined;
|
|
299
302
|
readAt: number;
|
|
303
|
+
code?: string | undefined;
|
|
300
304
|
reason?: string | undefined;
|
|
301
305
|
}>;
|
|
302
|
-
choose(payload: ChatUiChoicePayload): Promise<{
|
|
306
|
+
choose(payload: ChatUiChoicePayload, clientId: string): Promise<{
|
|
303
307
|
queued: boolean;
|
|
304
308
|
turnId: string;
|
|
305
309
|
}>;
|
package/dist/chat/chat-core.js
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { ChatAttachmentUploadsSchema, generatedImageAttachment } from '@adecore/agent-contracts';
|
|
4
|
-
import { ChatUiQueries } from './ui-queries.js';
|
|
5
4
|
import { ClientSinks } from '../client-sinks.js';
|
|
6
5
|
import { errorText } from '../error-text.js';
|
|
7
6
|
import { AccountError, definedEnv, isDefaultAccountOf, launchEnv, storedAccount } from '../providers/accounts/launch.js';
|
|
8
7
|
import { SkillIndex } from '../skills.js';
|
|
9
8
|
import { usageRoots } from '../usage/roots.js';
|
|
9
|
+
import { commandLabel, isBackgroundWork, runsInBackground } from './background-work.js';
|
|
10
10
|
import { ChatLog, COMPACT_ABOVE_BYTES } from './chat-log.js';
|
|
11
11
|
import { ChatSession } from './chat-session.js';
|
|
12
12
|
import { ComposerPreferences } from './composer-preferences.js';
|
|
13
13
|
import { DeltaCoalescer } from './delta-coalescer.js';
|
|
14
14
|
import { ChatError } from './errors.js';
|
|
15
|
-
import {
|
|
15
|
+
import { readableAssistantText } from './readable-text.js';
|
|
16
16
|
import { SubagentReader } from './subagent-reader.js';
|
|
17
|
-
|
|
17
|
+
import { ChatUiQueries } from './ui-queries.js';
|
|
18
18
|
function withoutHidden(info) {
|
|
19
19
|
const { hidden: _hidden, ...rest } = info;
|
|
20
20
|
return rest;
|
|
21
21
|
}
|
|
22
|
+
/* A message a person or the agent wrote in the chat's own thread; a subagent's words belong to its row. */
|
|
22
23
|
function isBookmarkable(item) {
|
|
23
24
|
return item.kind === 'user' || (item.kind === 'assistant' && (item.parentToolUseId ?? null) === null);
|
|
24
25
|
}
|
|
25
26
|
/* The start of a message on one line; a message of only attachments is its file names. */
|
|
26
27
|
function excerptOf(item) {
|
|
27
28
|
const files = item.kind === 'user' ? (item.attachments ?? []).map((attachment) => attachment.name).join(', ') : '';
|
|
28
|
-
|
|
29
|
+
const text = item.kind === 'assistant' ? readableAssistantText(item) : item.text;
|
|
30
|
+
return (text.trim() === '' ? files : text).replace(/\s+/g, ' ').trim();
|
|
29
31
|
}
|
|
30
32
|
// One process worked on the turn and one more may take it up after a restart; a loop of resumes could redo a command forever.
|
|
31
33
|
const MAX_ATTEMPTS = 2;
|
|
@@ -61,6 +63,7 @@ export class ChatCore {
|
|
|
61
63
|
claudeProjectsDir;
|
|
62
64
|
composerPreferences = new ComposerPreferences();
|
|
63
65
|
uiQueries;
|
|
66
|
+
uiFenceLanguage;
|
|
64
67
|
checkpoints;
|
|
65
68
|
instructions;
|
|
66
69
|
skillIndex;
|
|
@@ -74,7 +77,7 @@ export class ChatCore {
|
|
|
74
77
|
// How big each record was the last time it went to disk, and the writes waiting for a big one.
|
|
75
78
|
sizes = new Map();
|
|
76
79
|
waiting = new Map();
|
|
77
|
-
// What the last record held that no event carries (preambles, the host's extras), as its JSON.
|
|
80
|
+
// What the last record held that no event carries (preambles, the host's extras, captured UI access), as its JSON.
|
|
78
81
|
unlogged = new Map();
|
|
79
82
|
// The write in flight per chat, so the next one queues behind it instead of racing it.
|
|
80
83
|
writes = new Map();
|
|
@@ -87,6 +90,7 @@ export class ChatCore {
|
|
|
87
90
|
limitResume;
|
|
88
91
|
constructor(options) {
|
|
89
92
|
this.uiQueries = options.intelligentUi ? new ChatUiQueries(options.intelligentUi) : null;
|
|
93
|
+
this.uiFenceLanguage = options.uiFenceLanguage;
|
|
90
94
|
this.providers = options.providers;
|
|
91
95
|
this.store = options.store ?? null;
|
|
92
96
|
this.bookmarks = options.bookmarks ?? null;
|
|
@@ -272,9 +276,11 @@ export class ChatCore {
|
|
|
272
276
|
const references = this.referencesFor(payload.chatId);
|
|
273
277
|
const session = new ChatSession({
|
|
274
278
|
info,
|
|
279
|
+
...(this.uiFenceLanguage ? { uiFenceLanguage: this.uiFenceLanguage } : {}),
|
|
275
280
|
...(this.uiQueries ? { ui: { schemas: this.uiQueries.schemas, observe: (item, final) => this.uiQueries.observe(session, item, final) } } : {}),
|
|
276
281
|
items: stored?.items ?? [],
|
|
277
282
|
preambles: stored?.preambles ?? [],
|
|
283
|
+
uiAccess: stored?.uiAccess ?? {},
|
|
278
284
|
provider,
|
|
279
285
|
command: this.commands[kind] ?? provider.command,
|
|
280
286
|
...(this.spawn ? { spawn: this.spawn } : {}),
|
|
@@ -573,7 +579,10 @@ export class ChatCore {
|
|
|
573
579
|
}
|
|
574
580
|
return this.uiQueries.query(this.require(payload.chatId), payload);
|
|
575
581
|
}
|
|
576
|
-
async choose(payload) {
|
|
582
|
+
async choose(payload, clientId) {
|
|
583
|
+
if (!this.attached.get(payload.chatId)?.has(clientId)) {
|
|
584
|
+
throw new ChatError('refused-query', 'Attach to the chat before answering its UI.');
|
|
585
|
+
}
|
|
577
586
|
const session = this.require(payload.chatId);
|
|
578
587
|
const queries = await this.uiQueries?.choiceValues(session, payload);
|
|
579
588
|
const result = session.choose(payload, queries);
|
|
@@ -837,7 +846,7 @@ export class ChatCore {
|
|
|
837
846
|
const log = this.logs.get(chatId);
|
|
838
847
|
// Not folded here: an older write still in flight may land after this one, and the log is what covers for it.
|
|
839
848
|
try {
|
|
840
|
-
this.store.writeSync(chatId, info, items, { seq: log?.seq ?? 0, resetSeq: log?.resetSeq ?? 0 }, session.preambles, this.recordExtras(chatId));
|
|
849
|
+
this.store.writeSync(chatId, info, items, { seq: log?.seq ?? 0, resetSeq: log?.resetSeq ?? 0 }, session.preambles, this.recordExtras(chatId), session.uiAccessRecord);
|
|
841
850
|
}
|
|
842
851
|
catch (e) {
|
|
843
852
|
console.error(`Chat record for ${chatId} failed:`, errorText(e));
|
|
@@ -970,7 +979,8 @@ export class ChatCore {
|
|
|
970
979
|
// A delta held back is already in the thread, so it gets its seq before the snapshot says where it ends.
|
|
971
980
|
this.coalescers.get(chatId)?.flush();
|
|
972
981
|
const extras = this.recordExtras(chatId);
|
|
973
|
-
const
|
|
982
|
+
const uiAccess = session.uiAccessRecord;
|
|
983
|
+
const unlogged = JSON.stringify([session.preambles, extras, uiAccess]);
|
|
974
984
|
const folds = fold || log.size > COMPACT_ABOVE_BYTES || !log.onDisk;
|
|
975
985
|
// The log already holds every change to the thread, so the whole record is rewritten only to fold
|
|
976
986
|
// the log, on a chat's first write, or for what no event carries.
|
|
@@ -979,7 +989,7 @@ export class ChatCore {
|
|
|
979
989
|
}
|
|
980
990
|
const { info, items } = session.thread.snapshot();
|
|
981
991
|
const at = { seq: log.seq, resetSeq: log.resetSeq };
|
|
982
|
-
this.sizes.set(chatId, await this.store.write(chatId, info, items, at, session.preambles, extras));
|
|
992
|
+
this.sizes.set(chatId, await this.store.write(chatId, info, items, at, session.preambles, extras, uiAccess));
|
|
983
993
|
this.unlogged.set(chatId, unlogged);
|
|
984
994
|
// A chat killed while the write was out has no log left to fold.
|
|
985
995
|
if (this.logs.get(chatId) === log && folds) {
|
package/dist/chat/chat-log.js
CHANGED
|
@@ -31,8 +31,8 @@ export function parseLog(raw) {
|
|
|
31
31
|
return lines;
|
|
32
32
|
}
|
|
33
33
|
/*
|
|
34
|
-
*
|
|
35
|
-
* one order. Recent lines stay in memory
|
|
34
|
+
* Numbers every broadcast event and appends it synchronously, so disk, memory and snapshot boundaries
|
|
35
|
+
* keep one order. Recent lines stay in memory to answer an attach with `since` at once.
|
|
36
36
|
*/
|
|
37
37
|
export class ChatLog {
|
|
38
38
|
path;
|
|
@@ -32,6 +32,7 @@ export interface ChatSessionOptions {
|
|
|
32
32
|
clock?: OutboxClock;
|
|
33
33
|
items?: ChatItem[];
|
|
34
34
|
preambles?: string[];
|
|
35
|
+
uiAccess?: Readonly<Record<string, unknown>>;
|
|
35
36
|
provider: ChatProvider;
|
|
36
37
|
command: string[];
|
|
37
38
|
env(account: string | undefined): Record<string, string>;
|
|
@@ -43,6 +44,7 @@ export interface ChatSessionOptions {
|
|
|
43
44
|
promptNotes?: PromptNotes;
|
|
44
45
|
references?: ChatReferences;
|
|
45
46
|
checkpoints?: TurnCheckpoints;
|
|
47
|
+
uiFenceLanguage?: string;
|
|
46
48
|
ui?: {
|
|
47
49
|
schemas: UiCompileOptions['querySchemas'];
|
|
48
50
|
observe(item: ChatAssistantItem, final: boolean): void;
|
|
@@ -105,6 +107,11 @@ export declare class ChatSession {
|
|
|
105
107
|
private readonly steering;
|
|
106
108
|
private readonly uiStreams;
|
|
107
109
|
private readonly uiCompiled;
|
|
110
|
+
private readonly capturedUiAccess;
|
|
111
|
+
private readonly uiScanned;
|
|
112
|
+
private readonly uiReferencing;
|
|
113
|
+
private readonly uiAttachments;
|
|
114
|
+
private readonly uiFenceLanguage;
|
|
108
115
|
constructor(options: ChatSessionOptions);
|
|
109
116
|
get uiPreviews(): ChatEvent[];
|
|
110
117
|
previewItems(items: ChatItem[]): ChatItem[];
|
|
@@ -124,6 +131,9 @@ export declare class ChatSession {
|
|
|
124
131
|
queued: boolean;
|
|
125
132
|
turnId: string;
|
|
126
133
|
};
|
|
134
|
+
uiAccess(itemId: string): unknown;
|
|
135
|
+
setUiAccess(itemId: string, access: unknown): void;
|
|
136
|
+
get uiAccessRecord(): Record<string, unknown>;
|
|
127
137
|
updateUiQueries(itemId: string, uiQueries: ChatUiQueryState): void;
|
|
128
138
|
choose(payload: ChatUiChoicePayload, queries?: Readonly<Record<string, unknown>>): {
|
|
129
139
|
queued: boolean;
|
|
@@ -226,7 +236,10 @@ export declare class ChatSession {
|
|
|
226
236
|
private settleFromTranscripts;
|
|
227
237
|
private settleRows;
|
|
228
238
|
private emit;
|
|
239
|
+
private unscannedUiText;
|
|
240
|
+
private forgetUiScan;
|
|
229
241
|
private latestUiAttachment;
|
|
242
|
+
private findUiAttachment;
|
|
230
243
|
private clearUi;
|
|
231
244
|
private delegatingChange;
|
|
232
245
|
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { GeneratedImageInputSchema, notResumedNote } from '@adecore/agent-contracts';
|
|
3
|
-
import { resolveUiChoice, UiFailure } from '@adecore/intelligent-ui';
|
|
3
|
+
import { resolveUiChoice, UI_FENCE_LANGUAGE, UiFailure, uiHasFence, uiMayReferenceHost } from '@adecore/intelligent-ui';
|
|
4
4
|
import { UiStream } from '@adecore/intelligent-ui/stream';
|
|
5
5
|
import { commandLabel, isBackgroundWork, reportsOnBackgroundWork, runningInBackground } from './background-work.js';
|
|
6
6
|
import { ChatError } from './errors.js';
|
|
7
7
|
import { limitedTurn, limitResumeAt, limitResumeWake } from './limit-resume.js';
|
|
8
8
|
import { isMainAgentOutput, ThreadProjector } from './projector.js';
|
|
9
|
+
import { readableAssistantText } from './readable-text.js';
|
|
9
10
|
import { ChatThread } from './thread.js';
|
|
10
11
|
import { errorText } from '../error-text.js';
|
|
11
12
|
import { systemClock } from '../outbox/outbox-worker.js';
|
|
12
13
|
// Claude Code names a session about six seconds after its first prompt, and a first turn can run for minutes.
|
|
13
14
|
const TITLE_RECHECK_MS = 10_000;
|
|
15
|
+
// Longer than any fence line or the name of a tag a scan looks for, so a match never falls between two deltas.
|
|
16
|
+
const UI_SCAN_OVERLAP = 64;
|
|
14
17
|
/* One per limited turn, so a second fork of the same turn writes no second note. */
|
|
15
18
|
function continuedNoteId(turnId) {
|
|
16
19
|
return `continued-${turnId}`;
|
|
@@ -74,9 +77,17 @@ export class ChatSession {
|
|
|
74
77
|
steering = new Set();
|
|
75
78
|
uiStreams = new Map();
|
|
76
79
|
uiCompiled = new Map();
|
|
80
|
+
capturedUiAccess;
|
|
81
|
+
// How much of a streaming reply's text the fence and host scans already covered, so a delta scans only its tail.
|
|
82
|
+
uiScanned = new Map();
|
|
83
|
+
uiReferencing = new Set();
|
|
84
|
+
uiAttachments = new Map();
|
|
85
|
+
uiFenceLanguage;
|
|
77
86
|
constructor(options) {
|
|
78
87
|
this.options = options;
|
|
88
|
+
this.uiFenceLanguage = options.uiFenceLanguage ?? UI_FENCE_LANGUAGE;
|
|
79
89
|
this.pendingPreambles = options.preambles ?? [];
|
|
90
|
+
this.capturedUiAccess = new Map(Object.entries(options.uiAccess ?? {}));
|
|
80
91
|
this.thread = new ChatThread(options.info, options.items);
|
|
81
92
|
this.projector = new ThreadProjector(this.thread, { providerName: options.provider.name });
|
|
82
93
|
}
|
|
@@ -182,6 +193,21 @@ export class ChatSession {
|
|
|
182
193
|
this.dispatch(text, extras, turnId);
|
|
183
194
|
return { queued: false, turnId };
|
|
184
195
|
}
|
|
196
|
+
/* What a UI host captured for a reply; null where the capture was denied or is still out, undefined where none started. */
|
|
197
|
+
uiAccess(itemId) {
|
|
198
|
+
return this.capturedUiAccess.get(itemId);
|
|
199
|
+
}
|
|
200
|
+
setUiAccess(itemId, access) {
|
|
201
|
+
if (this.frozen) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
this.capturedUiAccess.set(itemId, access);
|
|
205
|
+
this.options.persistSoon();
|
|
206
|
+
}
|
|
207
|
+
/* The captured access of the replies still in the thread, for the record. */
|
|
208
|
+
get uiAccessRecord() {
|
|
209
|
+
return Object.fromEntries([...this.capturedUiAccess].filter(([itemId]) => this.thread.get(itemId) !== undefined));
|
|
210
|
+
}
|
|
185
211
|
updateUiQueries(itemId, uiQueries) {
|
|
186
212
|
const item = this.thread.get(itemId);
|
|
187
213
|
if (this.frozen || item?.kind !== 'assistant' || item.parentToolUseId) {
|
|
@@ -1598,7 +1624,7 @@ export class ChatSession {
|
|
|
1598
1624
|
this.naming = true;
|
|
1599
1625
|
const turnId = done[0].id;
|
|
1600
1626
|
const answer = items
|
|
1601
|
-
.map((item) => (item.kind === 'assistant' && item.turnId === turnId ? item
|
|
1627
|
+
.map((item) => (item.kind === 'assistant' && item.turnId === turnId ? readableAssistantText(item) : ''))
|
|
1602
1628
|
.filter((text) => text !== '')
|
|
1603
1629
|
.join('\n\n');
|
|
1604
1630
|
const agentSessionId = this.thread.info.agentSessionId;
|
|
@@ -1652,19 +1678,28 @@ export class ChatSession {
|
|
|
1652
1678
|
if (event.type === 'reset') {
|
|
1653
1679
|
this.clearUi();
|
|
1654
1680
|
}
|
|
1681
|
+
if (event.type === 'item' && event.item.kind === 'tool') {
|
|
1682
|
+
this.uiAttachments.delete(event.item.turnId);
|
|
1683
|
+
}
|
|
1655
1684
|
const item = event.type === 'item' ? event.item : event.type === 'delta' ? this.thread.get(event.itemId) : null;
|
|
1656
1685
|
if (item?.kind !== 'assistant' || item.parentToolUseId) {
|
|
1657
1686
|
this.options.emit(event);
|
|
1658
1687
|
continue;
|
|
1659
1688
|
}
|
|
1689
|
+
const tail = this.unscannedUiText(item, event.type === 'item');
|
|
1690
|
+
if (uiMayReferenceHost(tail)) {
|
|
1691
|
+
this.uiReferencing.add(item.id);
|
|
1692
|
+
}
|
|
1660
1693
|
let stream = this.uiStreams.get(item.id);
|
|
1661
|
-
if (!stream && !
|
|
1694
|
+
if (!stream && !uiHasFence(tail, this.uiFenceLanguage)) {
|
|
1695
|
+
this.forgetUiScan(item);
|
|
1662
1696
|
this.options.emit(event);
|
|
1663
1697
|
continue;
|
|
1664
1698
|
}
|
|
1665
1699
|
if (!stream) {
|
|
1666
1700
|
stream = new UiStream({
|
|
1667
1701
|
querySchemas: this.options.ui?.schemas,
|
|
1702
|
+
fenceLanguage: this.uiFenceLanguage,
|
|
1668
1703
|
id: item.id,
|
|
1669
1704
|
clock: this.options.clock ?? systemClock,
|
|
1670
1705
|
emit: (preview) => {
|
|
@@ -1674,7 +1709,10 @@ export class ChatSession {
|
|
|
1674
1709
|
});
|
|
1675
1710
|
this.uiStreams.set(item.id, stream);
|
|
1676
1711
|
}
|
|
1677
|
-
this.
|
|
1712
|
+
if (this.uiReferencing.has(item.id)) {
|
|
1713
|
+
this.options.ui?.observe(item, false);
|
|
1714
|
+
}
|
|
1715
|
+
this.forgetUiScan(item);
|
|
1678
1716
|
const latestAttachment = this.latestUiAttachment(item.turnId);
|
|
1679
1717
|
if (event.type === 'item' && !item.streaming) {
|
|
1680
1718
|
const ui = stream
|
|
@@ -1684,7 +1722,7 @@ export class ChatSession {
|
|
|
1684
1722
|
this.uiCompiled.delete(item.id);
|
|
1685
1723
|
const diagnostics = [...new Set(ui.flatMap((block) => block.diagnostics.map((diagnostic) => diagnostic.message)))].slice(0, 6);
|
|
1686
1724
|
if (diagnostics.length > 0) {
|
|
1687
|
-
const repair = `Your last
|
|
1725
|
+
const repair = `Your last ${this.uiFenceLanguage} block had these diagnostics: ${diagnostics.join(' ')} Use the catalog's supported props and components.`.slice(0, 1200);
|
|
1688
1726
|
if (!this.pendingPreambles.includes(repair)) {
|
|
1689
1727
|
this.pendingPreambles = [...this.pendingPreambles, repair];
|
|
1690
1728
|
}
|
|
@@ -1701,7 +1739,36 @@ export class ChatSession {
|
|
|
1701
1739
|
}
|
|
1702
1740
|
}
|
|
1703
1741
|
}
|
|
1742
|
+
/*
|
|
1743
|
+
* The text a scan has not covered yet, from the start of the line the last scan ended in, since a fence
|
|
1744
|
+
* is one line; a long line is cut short, behind a character no fence or tag can follow.
|
|
1745
|
+
*/
|
|
1746
|
+
unscannedUiText(item, whole) {
|
|
1747
|
+
const scanned = whole ? 0 : (this.uiScanned.get(item.id) ?? 0);
|
|
1748
|
+
this.uiScanned.set(item.id, item.text.length);
|
|
1749
|
+
if (scanned === 0 || scanned > item.text.length) {
|
|
1750
|
+
return item.text;
|
|
1751
|
+
}
|
|
1752
|
+
const line = item.text.lastIndexOf('\n', scanned - 1) + 1;
|
|
1753
|
+
const from = Math.max(line, scanned - UI_SCAN_OVERLAP);
|
|
1754
|
+
return from === line ? item.text.slice(from) : `\0${item.text.slice(from)}`;
|
|
1755
|
+
}
|
|
1756
|
+
forgetUiScan(item) {
|
|
1757
|
+
if (!item.streaming) {
|
|
1758
|
+
this.uiScanned.delete(item.id);
|
|
1759
|
+
this.uiReferencing.delete(item.id);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
/* Looked up once per turn, and again only after one of its tool calls changed. */
|
|
1704
1763
|
latestUiAttachment(turnId) {
|
|
1764
|
+
if (this.uiAttachments.has(turnId)) {
|
|
1765
|
+
return this.uiAttachments.get(turnId);
|
|
1766
|
+
}
|
|
1767
|
+
const attachment = this.findUiAttachment(turnId);
|
|
1768
|
+
this.uiAttachments.set(turnId, attachment);
|
|
1769
|
+
return attachment;
|
|
1770
|
+
}
|
|
1771
|
+
findUiAttachment(turnId) {
|
|
1705
1772
|
for (const item of this.thread.list().toReversed()) {
|
|
1706
1773
|
if (item.kind !== 'tool' || item.turnId !== turnId || item.name !== 'ImageGeneration' || item.state !== 'done') {
|
|
1707
1774
|
continue;
|
|
@@ -1719,6 +1786,9 @@ export class ChatSession {
|
|
|
1719
1786
|
}
|
|
1720
1787
|
this.uiStreams.clear();
|
|
1721
1788
|
this.uiCompiled.clear();
|
|
1789
|
+
this.uiScanned.clear();
|
|
1790
|
+
this.uiReferencing.clear();
|
|
1791
|
+
this.uiAttachments.clear();
|
|
1722
1792
|
}
|
|
1723
1793
|
/* Said on the info as well as by the rows, since a client with the thread closed sees only the info. */
|
|
1724
1794
|
delegatingChange(events) {
|
|
@@ -8,6 +8,7 @@ export interface ChatRecord {
|
|
|
8
8
|
seq: number;
|
|
9
9
|
resetSeq: number;
|
|
10
10
|
preambles: string[];
|
|
11
|
+
uiAccess: Record<string, unknown>;
|
|
11
12
|
extras: ChatRecordExtras;
|
|
12
13
|
lines: ChatLogLine[];
|
|
13
14
|
}
|
|
@@ -28,8 +29,8 @@ export declare class ChatStore {
|
|
|
28
29
|
list(): Promise<string[]>;
|
|
29
30
|
has(chatId: string): Promise<boolean>;
|
|
30
31
|
discardLog(chatId: string): Promise<number>;
|
|
31
|
-
write(chatId: string, info: ChatInfo, items: ChatItem[], at?: ChatSeq, preambles?: readonly string[], extras?: ChatRecordExtras): Promise<number>;
|
|
32
|
-
writeSync(chatId: string, info: ChatInfo, items: ChatItem[], at?: ChatSeq, preambles?: readonly string[], extras?: ChatRecordExtras): void;
|
|
32
|
+
write(chatId: string, info: ChatInfo, items: ChatItem[], at?: ChatSeq, preambles?: readonly string[], extras?: ChatRecordExtras, uiAccess?: Readonly<Record<string, unknown>>): Promise<number>;
|
|
33
|
+
writeSync(chatId: string, info: ChatInfo, items: ChatItem[], at?: ChatSeq, preambles?: readonly string[], extras?: ChatRecordExtras, uiAccess?: Readonly<Record<string, unknown>>): void;
|
|
33
34
|
read(chatId: string): Promise<ChatRecord | null>;
|
|
34
35
|
unreadable(chatId: string): Promise<boolean>;
|
|
35
36
|
private readSnapshot;
|
package/dist/chat/chat-store.js
CHANGED
|
@@ -4,12 +4,12 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { ChatInfoSchema, ChatItemSchema } from '@adecore/agent-contracts';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { isNotFound, writeAtomic, writeAtomicSync } from '../fs.js';
|
|
7
|
-
import {
|
|
7
|
+
import { recordFileName } from '../record-directory.js';
|
|
8
8
|
import { migrateInlineAttachments } from './attachment-store.js';
|
|
9
|
+
import { isBookmarkFileName } from './bookmark-store.js';
|
|
9
10
|
import { parseLog } from './chat-log.js';
|
|
10
11
|
import { ChatThread } from './thread.js';
|
|
11
12
|
import { isVisualFileName } from './visual-store.js';
|
|
12
|
-
import { recordFileName } from '../record-directory.js';
|
|
13
13
|
// zod strips what it does not know inside the info and the items, so a file written by an older build
|
|
14
14
|
// (with `interactionMode`, say) still parses and loses only the dropped field. A record without `seq`
|
|
15
15
|
// predates the log. Keys beside these are the host's own, and come back to it as they were written.
|
|
@@ -19,20 +19,40 @@ const RecordSchema = z.looseObject({
|
|
|
19
19
|
seq: z.number().int().nonnegative().optional(),
|
|
20
20
|
resetSeq: z.number().int().nonnegative().optional(),
|
|
21
21
|
// Never on the wire: what goes in front of the next real prompt, once (a fork's note for its agent).
|
|
22
|
-
preambles: z.array(z.string()).optional()
|
|
22
|
+
preambles: z.array(z.string()).optional(),
|
|
23
|
+
// Never on the wire: the access a UI host captured for each reply that reads it, by item id.
|
|
24
|
+
uiAccess: z.record(z.string(), z.unknown()).optional()
|
|
23
25
|
});
|
|
24
|
-
const OWN_KEYS = new Set(['info', 'items', 'seq', 'resetSeq', 'preambles']);
|
|
25
|
-
function
|
|
26
|
+
const OWN_KEYS = new Set(['info', 'items', 'seq', 'resetSeq', 'preambles', 'uiAccess']);
|
|
27
|
+
function withoutOwnKeys(record) {
|
|
26
28
|
return Object.fromEntries(Object.entries(record).filter(([key]) => !OWN_KEYS.has(key)));
|
|
27
29
|
}
|
|
28
30
|
function logName(chatId) {
|
|
29
31
|
return `${encodeURIComponent(chatId)}.log`;
|
|
30
32
|
}
|
|
31
|
-
function recordBody(info, items, at, preambles, extras) {
|
|
32
|
-
return JSON.stringify({
|
|
33
|
+
function recordBody(info, items, at, preambles, extras, uiAccess) {
|
|
34
|
+
return JSON.stringify({
|
|
35
|
+
info,
|
|
36
|
+
items,
|
|
37
|
+
...at,
|
|
38
|
+
...(preambles.length === 0 ? {} : { preambles }),
|
|
39
|
+
...(Object.keys(uiAccess).length === 0 ? {} : { uiAccess }),
|
|
40
|
+
...withoutOwnKeys(extras)
|
|
41
|
+
});
|
|
33
42
|
}
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
/* A record from before `uiAccess` kept the access inside each reply's `uiQueries`, which the item schema now drops. */
|
|
44
|
+
function liftUiAccess(record) {
|
|
45
|
+
if (typeof record !== 'object' || record === null || 'uiAccess' in record || !Array.isArray(record.items)) {
|
|
46
|
+
return record;
|
|
47
|
+
}
|
|
48
|
+
const uiAccess = {};
|
|
49
|
+
for (const item of record.items) {
|
|
50
|
+
const queries = item?.uiQueries;
|
|
51
|
+
if (typeof queries === 'object' && queries !== null && 'access' in queries) {
|
|
52
|
+
uiAccess[String(item.id)] = queries.access;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return Object.keys(uiAccess).length === 0 ? record : { ...record, uiAccess };
|
|
36
56
|
}
|
|
37
57
|
/*
|
|
38
58
|
* One JSON file per chat under `<home>/chats`, written while a turn runs and on shutdown, with
|
|
@@ -80,8 +100,8 @@ export class ChatStore {
|
|
|
80
100
|
return lines.at(-1)?.seq ?? 0;
|
|
81
101
|
}
|
|
82
102
|
/* Answers how many bytes the record took, which is what tells a caller whether writing it is cheap. */
|
|
83
|
-
async write(chatId, info, items, at = { seq: 0, resetSeq: 0 }, preambles = [], extras = {}) {
|
|
84
|
-
const body = recordBody(info, items, at, preambles, extras);
|
|
103
|
+
async write(chatId, info, items, at = { seq: 0, resetSeq: 0 }, preambles = [], extras = {}, uiAccess = {}) {
|
|
104
|
+
const body = recordBody(info, items, at, preambles, extras, uiAccess);
|
|
85
105
|
await mkdir(this.dir, { recursive: true, mode: 0o700 });
|
|
86
106
|
await writeAtomic(join(this.dir, recordFileName(chatId)), body);
|
|
87
107
|
return body.length;
|
|
@@ -90,9 +110,9 @@ export class ChatStore {
|
|
|
90
110
|
* The same write without a turn of the event loop. A `bun --watch` reload restarts the module
|
|
91
111
|
* before an awaited write comes back, so a shutdown has to put the threads down synchronously.
|
|
92
112
|
*/
|
|
93
|
-
writeSync(chatId, info, items, at = { seq: 0, resetSeq: 0 }, preambles = [], extras = {}) {
|
|
113
|
+
writeSync(chatId, info, items, at = { seq: 0, resetSeq: 0 }, preambles = [], extras = {}, uiAccess = {}) {
|
|
94
114
|
mkdirSync(this.dir, { recursive: true, mode: 0o700 });
|
|
95
|
-
writeAtomicSync(join(this.dir, recordFileName(chatId)), recordBody(info, items, at, preambles, extras));
|
|
115
|
+
writeAtomicSync(join(this.dir, recordFileName(chatId)), recordBody(info, items, at, preambles, extras, uiAccess));
|
|
96
116
|
}
|
|
97
117
|
/* Null for a chat with nothing on disk to make it of, and for one whose record does not parse (see `unreadable`). */
|
|
98
118
|
async read(chatId) {
|
|
@@ -117,7 +137,15 @@ export class ChatStore {
|
|
|
117
137
|
resetSeq = line.seq;
|
|
118
138
|
}
|
|
119
139
|
}
|
|
120
|
-
return {
|
|
140
|
+
return {
|
|
141
|
+
...thread.snapshot(),
|
|
142
|
+
seq,
|
|
143
|
+
resetSeq,
|
|
144
|
+
preambles: snapshot.preambles ?? [],
|
|
145
|
+
uiAccess: snapshot.uiAccess ?? {},
|
|
146
|
+
extras: withoutOwnKeys(snapshot),
|
|
147
|
+
lines
|
|
148
|
+
};
|
|
121
149
|
}
|
|
122
150
|
/*
|
|
123
151
|
* Whether a record is on disk that this host cannot read: one a newer version wrote, one cut short,
|
|
@@ -146,7 +174,7 @@ export class ChatStore {
|
|
|
146
174
|
await writeAtomic(join(this.dir, recordFileName(chatId)), JSON.stringify(migrated));
|
|
147
175
|
}
|
|
148
176
|
}
|
|
149
|
-
const parsed = RecordSchema.safeParse(record);
|
|
177
|
+
const parsed = RecordSchema.safeParse(liftUiAccess(record));
|
|
150
178
|
return parsed.success ? parsed.data : UNREADABLE;
|
|
151
179
|
}
|
|
152
180
|
async delete(chatId) {
|
|
@@ -199,5 +227,5 @@ function fromLogAlone(lines) {
|
|
|
199
227
|
resetSeq = line.seq;
|
|
200
228
|
}
|
|
201
229
|
}
|
|
202
|
-
return { ...thread.snapshot(), seq: 0, resetSeq, preambles: [], extras: {}, lines };
|
|
230
|
+
return { ...thread.snapshot(), seq: 0, resetSeq, preambles: [], uiAccess: {}, extras: {}, lines };
|
|
203
231
|
}
|
package/dist/chat/chat-title.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { cleanTitle } from '../title-file.js';
|
|
2
1
|
import { runProcess } from '../run-process.js';
|
|
2
|
+
import { cleanTitle } from '../title-file.js';
|
|
3
3
|
// Enough of a conversation to name it; the rest of a long prompt or answer says nothing a title needs.
|
|
4
4
|
const MAX_PROMPT_CHARS = 2000;
|
|
5
5
|
const MAX_ANSWER_CHARS = 1500;
|
|
@@ -8,10 +8,7 @@ const TIMEOUT_MS = 45_000;
|
|
|
8
8
|
function cap(text, limit) {
|
|
9
9
|
return text.length <= limit ? text : `${text.slice(0, limit)}...`;
|
|
10
10
|
}
|
|
11
|
-
/*
|
|
12
|
-
* What the CLI is asked. JSON again, as for a commit message: a model left to write freely opens
|
|
13
|
-
* with a sentence, and that sentence would become the name of the node.
|
|
14
|
-
*/
|
|
11
|
+
/* JSON, since a model left to write freely opens with a sentence, and that sentence would become the name. */
|
|
15
12
|
export function buildTitlePrompt(prompt, answer) {
|
|
16
13
|
return [
|
|
17
14
|
'Write a short title for the conversation below, as a person would name it in a list of chats.',
|
|
@@ -30,9 +27,8 @@ export function buildTitlePrompt(prompt, answer) {
|
|
|
30
27
|
].join('\n');
|
|
31
28
|
}
|
|
32
29
|
/*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* is better than a stray line of it.
|
|
30
|
+
* Strict on purpose: a run that holds no `{"title": "..."}` object printed something other than a
|
|
31
|
+
* title, and a derived name is better than a stray line of it.
|
|
36
32
|
*/
|
|
37
33
|
export function parseTitle(output) {
|
|
38
34
|
const start = [...output.matchAll(/\{\s*"title"/g)].at(-1)?.index;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { errorText } from '../error-text.js';
|
|
2
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { errorText } from '../error-text.js';
|
|
3
3
|
import { claudeArgs, claudeEnv, promptPrefix } from '../providers/claude.js';
|
|
4
4
|
import { ChatChild } from './chat-process.js';
|
|
5
5
|
import { ClaudeProtocol } from './claude-protocol.js';
|
|
@@ -59,12 +59,10 @@ export class ClaudeBackend {
|
|
|
59
59
|
return Promise.resolve();
|
|
60
60
|
}
|
|
61
61
|
sendTurn(input) {
|
|
62
|
-
// The prefix is what the CLI must see first (ultrathink), then the note about the links, then what was typed.
|
|
63
|
-
const prefix = `${promptPrefix(this.launch.selection)}${input.preamble === null ? '' : `${input.preamble}\n\n`}`;
|
|
64
62
|
const promptId = input.promptId ?? randomUUID();
|
|
65
63
|
this.active = true;
|
|
66
64
|
this.protocol.beginPrompt(promptId);
|
|
67
|
-
this.write(
|
|
65
|
+
this.write(this.userMessage(input, promptId));
|
|
68
66
|
}
|
|
69
67
|
steerTurn(input) {
|
|
70
68
|
if (!this.child || !this.active || this.stdinClosed) {
|
|
@@ -75,8 +73,7 @@ export class ClaudeBackend {
|
|
|
75
73
|
this.steers.set(promptId, { resolve, reject });
|
|
76
74
|
this.protocol.beginSteer(promptId);
|
|
77
75
|
try {
|
|
78
|
-
|
|
79
|
-
this.write(buildUserMessage({ text: input.text, attachments: input.attachments, prefix, skills: input.skills, promptId }));
|
|
76
|
+
this.write(this.userMessage(input, promptId));
|
|
80
77
|
}
|
|
81
78
|
catch (error) {
|
|
82
79
|
this.steers.delete(promptId);
|
|
@@ -126,6 +123,11 @@ export class ClaudeBackend {
|
|
|
126
123
|
this.child = null;
|
|
127
124
|
return child?.end() ?? Promise.resolve();
|
|
128
125
|
}
|
|
126
|
+
userMessage(input, promptId) {
|
|
127
|
+
// What the CLI must see first (ultrathink), then the preamble, then what was typed.
|
|
128
|
+
const prefix = `${promptPrefix(this.launch.selection)}${input.preamble === null ? '' : `${input.preamble}\n\n`}`;
|
|
129
|
+
return buildUserMessage({ text: input.text, attachments: input.attachments, prefix, skills: input.skills, promptId });
|
|
130
|
+
}
|
|
129
131
|
async readLines(child) {
|
|
130
132
|
const reader = child.process.stdout.getReader();
|
|
131
133
|
const decoder = new TextDecoder();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readdir, stat } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { usageRoots } from '../usage/roots.js';
|
|
5
4
|
import { cleanTitle, readLines } from '../title-file.js';
|
|
5
|
+
import { usageRoots } from '../usage/roots.js';
|
|
6
6
|
// The two records carry these words; a line without them is never parsed.
|
|
7
7
|
const MARKERS = ['"ai-title"', '"custom-title"'];
|
|
8
8
|
/*
|