praxis-agent 0.20.9 → 0.20.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/application/top-level-agent-manager.d.ts +2 -2
- package/dist/application/top-level-agent-manager.js +9 -0
- package/dist/compatibility/claude/tool-links.d.ts +1 -0
- package/dist/compatibility/claude/tool-links.js +76 -0
- package/dist/persistence/claude-transcript-store.js +7 -3
- package/package.json +1 -1
|
@@ -5,11 +5,11 @@ export interface TopLevelAgentSummary {
|
|
|
5
5
|
pid?: number;
|
|
6
6
|
id?: string;
|
|
7
7
|
cwd: string;
|
|
8
|
-
kind: 'background' | 'interactive';
|
|
8
|
+
kind: 'background' | 'interactive' | 'bg' | 'daemon' | 'daemon-worker';
|
|
9
9
|
startedAt: number;
|
|
10
10
|
sessionId: string;
|
|
11
11
|
name: string;
|
|
12
|
-
status?: 'active' | 'idle';
|
|
12
|
+
status?: 'active' | 'idle' | 'busy' | 'waiting';
|
|
13
13
|
tempo?: ClaudeJobTempo;
|
|
14
14
|
needs?: string;
|
|
15
15
|
state?: 'working' | 'stopped' | 'failed' | 'done';
|
|
@@ -87,6 +87,15 @@ function nativeClaudeSession(value) {
|
|
|
87
87
|
['active', 'idle'].includes(String(record.status));
|
|
88
88
|
if (interactive)
|
|
89
89
|
return record;
|
|
90
|
+
const daemon = Number.isSafeInteger(record.pid) &&
|
|
91
|
+
typeof record.cwd === 'string' &&
|
|
92
|
+
['bg', 'daemon', 'daemon-worker'].includes(String(record.kind)) &&
|
|
93
|
+
Number.isSafeInteger(record.startedAt) &&
|
|
94
|
+
typeof record.sessionId === 'string' &&
|
|
95
|
+
typeof record.name === 'string' &&
|
|
96
|
+
['busy', 'idle', 'waiting'].includes(String(record.status));
|
|
97
|
+
if (daemon)
|
|
98
|
+
return record;
|
|
90
99
|
if (typeof record.id !== 'string' ||
|
|
91
100
|
typeof record.cwd !== 'string' ||
|
|
92
101
|
record.kind !== 'background' ||
|
|
@@ -5,6 +5,7 @@ export declare function indexClaudeToolLinks(entries: readonly ClaudeTranscriptE
|
|
|
5
5
|
toolNames: Map<string, string>;
|
|
6
6
|
completedToolCalls: Set<string>;
|
|
7
7
|
};
|
|
8
|
+
export declare function recoverClaudeToolResultLinks(entries: readonly ClaudeTranscriptEntry[]): ClaudeTranscriptEntry[];
|
|
8
9
|
export declare function findUnresolvedClaudeToolCalls(entries: readonly ClaudeTranscriptEntry[]): {
|
|
9
10
|
id: string;
|
|
10
11
|
name: string;
|
|
@@ -33,6 +33,82 @@ export function indexClaudeToolLinks(entries) {
|
|
|
33
33
|
}
|
|
34
34
|
return { toolCalls, toolNames, completedToolCalls };
|
|
35
35
|
}
|
|
36
|
+
export function recoverClaudeToolResultLinks(entries) {
|
|
37
|
+
// Index each tool_use id to the uuid of its unique assistant entry. An id
|
|
38
|
+
// declared by more than one assistant tool_use block, or by an assistant
|
|
39
|
+
// entry without a string uuid, is ambiguous and is never recoverable.
|
|
40
|
+
const assistantUuidById = new Map();
|
|
41
|
+
const assistantIdCounts = new Map();
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (entry.type !== 'assistant')
|
|
44
|
+
continue;
|
|
45
|
+
for (const block of getClaudeContentBlocks(entry)) {
|
|
46
|
+
if (block.type !== 'tool_use' || typeof block.id !== 'string')
|
|
47
|
+
continue;
|
|
48
|
+
const count = (assistantIdCounts.get(block.id) ?? 0) + 1;
|
|
49
|
+
assistantIdCounts.set(block.id, count);
|
|
50
|
+
if (count === 1 && typeof entry.uuid === 'string') {
|
|
51
|
+
assistantUuidById.set(block.id, entry.uuid);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
assistantUuidById.delete(block.id);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Count every tool_result reference so a recovered link never duplicates a
|
|
59
|
+
// result that was already completed elsewhere in the transcript.
|
|
60
|
+
const resultIdCounts = new Map();
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (entry.type !== 'user')
|
|
63
|
+
continue;
|
|
64
|
+
for (const block of getClaudeContentBlocks(entry)) {
|
|
65
|
+
if (block.type !== 'tool_result' ||
|
|
66
|
+
typeof block.tool_use_id !== 'string') {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
resultIdCounts.set(block.tool_use_id, (resultIdCounts.get(block.tool_use_id) ?? 0) + 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const recovered = [];
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
if (entry.type === 'user' &&
|
|
75
|
+
typeof entry.sourceToolAssistantUUID !== 'string') {
|
|
76
|
+
const blocks = getClaudeContentBlocks(entry).filter((block) => block.type === 'tool_result');
|
|
77
|
+
const sourceUuids = new Set();
|
|
78
|
+
let linkable = true;
|
|
79
|
+
for (const block of blocks) {
|
|
80
|
+
// Malformed tool_result blocks (non-string tool_use_id) are never
|
|
81
|
+
// recovered and prevent linking the whole entry.
|
|
82
|
+
if (typeof block.tool_use_id !== 'string') {
|
|
83
|
+
linkable = false;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
const sourceUuid = assistantUuidById.get(block.tool_use_id);
|
|
87
|
+
if (sourceUuid === undefined ||
|
|
88
|
+
resultIdCounts.get(block.tool_use_id) !== 1) {
|
|
89
|
+
linkable = false;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
sourceUuids.add(sourceUuid);
|
|
93
|
+
// A single entry has one sourceToolAssistantUUID, so every recoverable
|
|
94
|
+
// block must resolve to the same assistant entry.
|
|
95
|
+
if (sourceUuids.size > 1) {
|
|
96
|
+
linkable = false;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (linkable && sourceUuids.size === 1) {
|
|
101
|
+
recovered.push({
|
|
102
|
+
...entry,
|
|
103
|
+
sourceToolAssistantUUID: [...sourceUuids][0],
|
|
104
|
+
});
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
recovered.push(entry);
|
|
109
|
+
}
|
|
110
|
+
return recovered;
|
|
111
|
+
}
|
|
36
112
|
export function findUnresolvedClaudeToolCalls(entries) {
|
|
37
113
|
const active = selectClaudeActiveTranscript(entries);
|
|
38
114
|
const { completedToolCalls } = indexClaudeToolLinks(active);
|
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { mkdir, open, readFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
4
|
import { TextDecoder } from 'node:util';
|
|
5
|
-
import { getClaudeContentBlocks, indexClaudeToolLinks, } from '../compatibility/claude/tool-links.js';
|
|
5
|
+
import { getClaudeContentBlocks, indexClaudeToolLinks, recoverClaudeToolResultLinks, } from '../compatibility/claude/tool-links.js';
|
|
6
6
|
import { ExclusiveFileLease } from '../platform/exclusive-file-lease.js';
|
|
7
7
|
export class ClaudeTranscriptParseError extends Error {
|
|
8
8
|
lineNumber;
|
|
@@ -200,10 +200,14 @@ export class ClaudeTranscriptStore {
|
|
|
200
200
|
}
|
|
201
201
|
async load() {
|
|
202
202
|
const { entries, tail } = this.parseSource(await this.readSource(), false);
|
|
203
|
-
return { entries, tail };
|
|
203
|
+
return { entries: recoverClaudeToolResultLinks(entries), tail };
|
|
204
204
|
}
|
|
205
205
|
async loadReadOnly() {
|
|
206
|
-
|
|
206
|
+
const recovery = this.parseSource(await this.readSource(), true);
|
|
207
|
+
return {
|
|
208
|
+
...recovery,
|
|
209
|
+
entries: recoverClaudeToolResultLinks(recovery.entries),
|
|
210
|
+
};
|
|
207
211
|
}
|
|
208
212
|
async exportReadOnly() {
|
|
209
213
|
return readFile(this.sessionFile);
|