@clinebot/core 0.0.10 → 0.0.11
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/index.node.d.ts +0 -1
- package/dist/index.node.js +107 -107
- package/dist/session/utils/helpers.d.ts +1 -1
- package/dist/session/utils/types.d.ts +1 -0
- package/package.json +4 -4
- package/src/session/default-session-manager.test.ts +131 -0
- package/src/session/default-session-manager.ts +5 -1
- package/src/session/utils/helpers.ts +13 -3
- package/src/session/utils/types.ts +1 -0
|
@@ -7,5 +7,5 @@ export declare function extractWorkspaceMetadataFromSystemPrompt(systemPrompt: s
|
|
|
7
7
|
export declare function hasRuntimeHooks(hooks: AgentConfig["hooks"]): boolean;
|
|
8
8
|
export declare function mergeAgentExtensions(explicitExtensions: AgentConfig["extensions"] | undefined, loadedExtensions: AgentConfig["extensions"] | undefined): AgentConfig["extensions"];
|
|
9
9
|
export declare function serializeAgentEvent(event: AgentEvent): string;
|
|
10
|
-
export declare function withLatestAssistantTurnMetadata(messages: LlmsProviders.Message[], result: AgentResult): StoredMessageWithMetadata[];
|
|
10
|
+
export declare function withLatestAssistantTurnMetadata(messages: LlmsProviders.Message[], result: AgentResult, previousMessages?: LlmsProviders.MessageWithMetadata[]): StoredMessageWithMetadata[];
|
|
11
11
|
export declare function toSessionRecord(row: SessionRowShape): SessionRecord;
|
|
@@ -17,6 +17,7 @@ export type ActiveSession = {
|
|
|
17
17
|
started: boolean;
|
|
18
18
|
aborting: boolean;
|
|
19
19
|
interactive: boolean;
|
|
20
|
+
persistedMessages?: LlmsProviders.MessageWithMetadata[];
|
|
20
21
|
activeTeamRunIds: Set<string>;
|
|
21
22
|
pendingTeamRunUpdates: TeamRunUpdate[];
|
|
22
23
|
teamRunWaiters: Array<() => void>;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clinebot/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"main": "./dist/index.node.js",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@clinebot/agents": "0.0.
|
|
7
|
-
"@clinebot/llms": "0.0.
|
|
8
|
-
"@clinebot/shared": "0.0.
|
|
6
|
+
"@clinebot/agents": "0.0.11",
|
|
7
|
+
"@clinebot/llms": "0.0.11",
|
|
8
|
+
"@clinebot/shared": "0.0.11",
|
|
9
9
|
"@opentelemetry/api": "^1.9.0",
|
|
10
10
|
"@opentelemetry/api-logs": "^0.56.0",
|
|
11
11
|
"@opentelemetry/exporter-logs-otlp-http": "^0.56.0",
|
|
@@ -309,6 +309,137 @@ describe("DefaultSessionManager", () => {
|
|
|
309
309
|
});
|
|
310
310
|
});
|
|
311
311
|
|
|
312
|
+
it("preserves per-turn metadata on prior assistant messages across turns", async () => {
|
|
313
|
+
const sessionId = "sess-meta-multi";
|
|
314
|
+
const manifest = createManifest(sessionId);
|
|
315
|
+
const persistSessionMessages = vi.fn();
|
|
316
|
+
const runtimeBuilder = {
|
|
317
|
+
build: vi.fn().mockReturnValue({
|
|
318
|
+
tools: [],
|
|
319
|
+
shutdown: vi.fn(),
|
|
320
|
+
}),
|
|
321
|
+
};
|
|
322
|
+
const firstTurnMessages = [
|
|
323
|
+
{
|
|
324
|
+
role: "user" as const,
|
|
325
|
+
content: [{ type: "text" as const, text: "hello" }],
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
role: "assistant" as const,
|
|
329
|
+
content: [{ type: "text" as const, text: "world" }],
|
|
330
|
+
},
|
|
331
|
+
];
|
|
332
|
+
const secondTurnMessages = [
|
|
333
|
+
...firstTurnMessages,
|
|
334
|
+
{
|
|
335
|
+
role: "user" as const,
|
|
336
|
+
content: [{ type: "text" as const, text: "again" }],
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
role: "assistant" as const,
|
|
340
|
+
content: [{ type: "text" as const, text: "still here" }],
|
|
341
|
+
},
|
|
342
|
+
];
|
|
343
|
+
const run = vi.fn().mockResolvedValue(
|
|
344
|
+
createResult({
|
|
345
|
+
usage: {
|
|
346
|
+
inputTokens: 33,
|
|
347
|
+
outputTokens: 12,
|
|
348
|
+
cacheReadTokens: 4,
|
|
349
|
+
cacheWriteTokens: 1,
|
|
350
|
+
totalCost: 0.42,
|
|
351
|
+
},
|
|
352
|
+
model: {
|
|
353
|
+
id: "claude-sonnet-4-6",
|
|
354
|
+
provider: "anthropic",
|
|
355
|
+
},
|
|
356
|
+
endedAt: new Date("2026-01-01T00:00:02.000Z"),
|
|
357
|
+
messages: firstTurnMessages,
|
|
358
|
+
}),
|
|
359
|
+
);
|
|
360
|
+
const continueFn = vi.fn().mockResolvedValue(
|
|
361
|
+
createResult({
|
|
362
|
+
usage: {
|
|
363
|
+
inputTokens: 10,
|
|
364
|
+
outputTokens: 5,
|
|
365
|
+
cacheReadTokens: 2,
|
|
366
|
+
cacheWriteTokens: 0,
|
|
367
|
+
totalCost: 0.12,
|
|
368
|
+
},
|
|
369
|
+
model: {
|
|
370
|
+
id: "claude-sonnet-4-6",
|
|
371
|
+
provider: "anthropic",
|
|
372
|
+
},
|
|
373
|
+
endedAt: new Date("2026-01-01T00:00:03.000Z"),
|
|
374
|
+
messages: secondTurnMessages,
|
|
375
|
+
}),
|
|
376
|
+
);
|
|
377
|
+
const agent = {
|
|
378
|
+
run,
|
|
379
|
+
continue: continueFn,
|
|
380
|
+
abort: vi.fn(),
|
|
381
|
+
shutdown: vi.fn().mockResolvedValue(undefined),
|
|
382
|
+
restore: vi.fn(),
|
|
383
|
+
getMessages: vi.fn().mockReturnValue([]),
|
|
384
|
+
messages: [],
|
|
385
|
+
};
|
|
386
|
+
const sessionService = {
|
|
387
|
+
ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
|
|
388
|
+
createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
|
|
389
|
+
manifestPath: "/tmp/manifest-meta-multi.json",
|
|
390
|
+
transcriptPath: "/tmp/transcript-meta-multi.log",
|
|
391
|
+
hookPath: "/tmp/hook-meta-multi.log",
|
|
392
|
+
messagesPath: "/tmp/messages-meta-multi.json",
|
|
393
|
+
manifest,
|
|
394
|
+
}),
|
|
395
|
+
persistSessionMessages,
|
|
396
|
+
updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
|
|
397
|
+
writeSessionManifest: vi.fn(),
|
|
398
|
+
listSessions: vi.fn().mockResolvedValue([]),
|
|
399
|
+
deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
|
|
400
|
+
};
|
|
401
|
+
const manager = new DefaultSessionManager({
|
|
402
|
+
distinctId,
|
|
403
|
+
sessionService: sessionService as never,
|
|
404
|
+
runtimeBuilder,
|
|
405
|
+
createAgent: () => agent as never,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
await manager.start({
|
|
409
|
+
config: createConfig({
|
|
410
|
+
sessionId,
|
|
411
|
+
providerId: "anthropic",
|
|
412
|
+
modelId: "claude-sonnet-4-6",
|
|
413
|
+
}),
|
|
414
|
+
interactive: true,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
await manager.send({ sessionId, prompt: "hello" });
|
|
418
|
+
await manager.send({ sessionId, prompt: "again" });
|
|
419
|
+
|
|
420
|
+
const persisted = persistSessionMessages.mock.calls[1]?.[1];
|
|
421
|
+
expect(persisted?.[1]).toMatchObject({
|
|
422
|
+
role: "assistant",
|
|
423
|
+
metrics: {
|
|
424
|
+
inputTokens: 33,
|
|
425
|
+
outputTokens: 12,
|
|
426
|
+
cacheReadTokens: 4,
|
|
427
|
+
cacheWriteTokens: 1,
|
|
428
|
+
cost: 0.42,
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
expect(persisted?.[3]).toMatchObject({
|
|
432
|
+
role: "assistant",
|
|
433
|
+
metrics: {
|
|
434
|
+
inputTokens: 10,
|
|
435
|
+
outputTokens: 5,
|
|
436
|
+
cacheReadTokens: 2,
|
|
437
|
+
cacheWriteTokens: 0,
|
|
438
|
+
cost: 0.12,
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
312
443
|
it("persists rendered messages when a turn fails", async () => {
|
|
313
444
|
const sessionId = "sess-failed-turn";
|
|
314
445
|
const manifest = createManifest(sessionId);
|
|
@@ -303,6 +303,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
303
303
|
started: false,
|
|
304
304
|
aborting: false,
|
|
305
305
|
interactive: input.interactive === true,
|
|
306
|
+
persistedMessages: input.initialMessages,
|
|
306
307
|
activeTeamRunIds: new Set<string>(),
|
|
307
308
|
pendingTeamRunUpdates: [],
|
|
308
309
|
teamRunWaiters: [],
|
|
@@ -548,7 +549,8 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
548
549
|
): Promise<AgentResult> {
|
|
549
550
|
const shouldContinue =
|
|
550
551
|
session.started || session.agent.getMessages().length > 0;
|
|
551
|
-
const baselineMessages =
|
|
552
|
+
const baselineMessages =
|
|
553
|
+
session.persistedMessages ?? session.agent.getMessages();
|
|
552
554
|
const usageBaseline =
|
|
553
555
|
this.usageBySession.get(session.sessionId) ??
|
|
554
556
|
createInitialAccumulatedUsage();
|
|
@@ -582,7 +584,9 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
582
584
|
const persistedMessages = withLatestAssistantTurnMetadata(
|
|
583
585
|
result.messages,
|
|
584
586
|
result,
|
|
587
|
+
baselineMessages,
|
|
585
588
|
);
|
|
589
|
+
session.persistedMessages = persistedMessages;
|
|
586
590
|
this.usageBySession.set(
|
|
587
591
|
session.sessionId,
|
|
588
592
|
accumulateUsageTotals(usageBaseline, result.usage),
|
|
@@ -62,10 +62,20 @@ export function serializeAgentEvent(event: AgentEvent): string {
|
|
|
62
62
|
export function withLatestAssistantTurnMetadata(
|
|
63
63
|
messages: LlmsProviders.Message[],
|
|
64
64
|
result: AgentResult,
|
|
65
|
+
previousMessages: LlmsProviders.MessageWithMetadata[] = [],
|
|
65
66
|
): StoredMessageWithMetadata[] {
|
|
66
|
-
const next = messages.map((message) =>
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const next = messages.map((message, index) => {
|
|
68
|
+
const previous = previousMessages[index];
|
|
69
|
+
const sameMessage =
|
|
70
|
+
previous?.role === message.role &&
|
|
71
|
+
JSON.stringify(previous.content) === JSON.stringify(message.content);
|
|
72
|
+
return sameMessage
|
|
73
|
+
? ({
|
|
74
|
+
...previous,
|
|
75
|
+
...message,
|
|
76
|
+
} as StoredMessageWithMetadata)
|
|
77
|
+
: ({ ...message } as StoredMessageWithMetadata);
|
|
78
|
+
});
|
|
69
79
|
const assistantIndex = [...next]
|
|
70
80
|
.reverse()
|
|
71
81
|
.findIndex((message) => message.role === "assistant");
|
|
@@ -18,6 +18,7 @@ export type ActiveSession = {
|
|
|
18
18
|
started: boolean;
|
|
19
19
|
aborting: boolean;
|
|
20
20
|
interactive: boolean;
|
|
21
|
+
persistedMessages?: LlmsProviders.MessageWithMetadata[];
|
|
21
22
|
activeTeamRunIds: Set<string>;
|
|
22
23
|
pendingTeamRunUpdates: TeamRunUpdate[];
|
|
23
24
|
teamRunWaiters: Array<() => void>;
|