@gaia-ai/addon-claude 0.11.5 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.d.ts +11 -3
- package/dist/src/index.js +82 -2
- package/dist/src/preset.d.ts +2 -1
- package/dist/src/preset.js +30 -4
- package/package.json +3 -3
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentFootprint, AgentPlugin, GaiaAgent } from '@gaia-ai/conductor/contract';
|
|
1
|
+
import type { AgentFootprint, AgentPlugin, AgentRunIdentity, GaiaAgent } from '@gaia-ai/conductor/contract';
|
|
2
2
|
export { parseClaudeTranscript } from './footprint.js';
|
|
3
3
|
/**
|
|
4
4
|
* Encode an absolute cwd the way Claude Code names its per-project transcript
|
|
@@ -13,8 +13,16 @@ export declare class ClaudeAgent implements GaiaAgent {
|
|
|
13
13
|
private readonly home;
|
|
14
14
|
readonly id = "claude";
|
|
15
15
|
constructor(options: ClaudeAgentOptions, home?: string);
|
|
16
|
-
launchCommand(prompt: string): string;
|
|
17
|
-
|
|
16
|
+
launchCommand(prompt: string, run?: AgentRunIdentity): string;
|
|
17
|
+
/**
|
|
18
|
+
* `claude --session-id <uuid>` refuses to start when that session already
|
|
19
|
+
* exists (GAIA-473 review round 2), so the conductor checks this before
|
|
20
|
+
* pinning the run's own UUID — a re-dispatch of the same run would
|
|
21
|
+
* otherwise mint the very id its first attempt already wrote and fail to
|
|
22
|
+
* launch at all, silently (`claude` exits 0 on that error).
|
|
23
|
+
*/
|
|
24
|
+
sessionExists(worktreePath: string, sessionId: string): Promise<boolean>;
|
|
25
|
+
getRunLog(worktreePath: string, run?: AgentRunIdentity): Promise<string>;
|
|
18
26
|
parseFootprint(log: string): AgentFootprint;
|
|
19
27
|
}
|
|
20
28
|
/** Config-facing factory: `claudeAgent({ model: 'claude-opus-4-8' })`. */
|
package/dist/src/index.js
CHANGED
|
@@ -19,7 +19,7 @@ export class ClaudeAgent {
|
|
|
19
19
|
this.options = options;
|
|
20
20
|
this.home = home;
|
|
21
21
|
}
|
|
22
|
-
launchCommand(prompt) {
|
|
22
|
+
launchCommand(prompt, run) {
|
|
23
23
|
if (prompt === '') {
|
|
24
24
|
return 'claude';
|
|
25
25
|
}
|
|
@@ -28,10 +28,85 @@ export class ClaudeAgent {
|
|
|
28
28
|
parts.push('--model', this.options.model);
|
|
29
29
|
}
|
|
30
30
|
parts.push('--dangerously-skip-permissions');
|
|
31
|
+
// Pin the transcript's own session id to the run's UUID (GAIA-473), so
|
|
32
|
+
// `getRunLog` can read that ONE file directly instead of scanning the
|
|
33
|
+
// worktree-keyed directory by mtime — the scan is what let a
|
|
34
|
+
// resumed/re-dispatched/reused worktree finalise a foreign transcript.
|
|
35
|
+
if (run?.sessionId) {
|
|
36
|
+
parts.push('--session-id', run.sessionId);
|
|
37
|
+
}
|
|
31
38
|
return parts.join(' ');
|
|
32
39
|
}
|
|
33
|
-
|
|
40
|
+
/**
|
|
41
|
+
* `claude --session-id <uuid>` refuses to start when that session already
|
|
42
|
+
* exists (GAIA-473 review round 2), so the conductor checks this before
|
|
43
|
+
* pinning the run's own UUID — a re-dispatch of the same run would
|
|
44
|
+
* otherwise mint the very id its first attempt already wrote and fail to
|
|
45
|
+
* launch at all, silently (`claude` exits 0 on that error).
|
|
46
|
+
*/
|
|
47
|
+
async sessionExists(worktreePath, sessionId) {
|
|
34
48
|
const dir = join(this.home, '.claude', 'projects', encodeClaudeProjectDir(worktreePath));
|
|
49
|
+
try {
|
|
50
|
+
await stat(join(dir, `${sessionId}.jsonl`));
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async getRunLog(worktreePath, run) {
|
|
58
|
+
const dir = join(this.home, '.claude', 'projects', encodeClaudeProjectDir(worktreePath));
|
|
59
|
+
const startedAtMs = run?.startedAt !== undefined ? new Date(run.startedAt).getTime() : null;
|
|
60
|
+
// GAIA-473: when the run's own session id is known (pinned at launch via
|
|
61
|
+
// `--session-id`), read that file directly — no directory scan, so a
|
|
62
|
+
// foreign transcript (resumed run, re-dispatch, reused worktree) can
|
|
63
|
+
// never be selected. AC-5: when the pinned file is missing (a run
|
|
64
|
+
// dispatched before this change, or the agent never wrote it), fall
|
|
65
|
+
// through to the scan below — bounded by `run.startedAt` where known —
|
|
66
|
+
// instead of returning '' and turning a wrong log into no log at all.
|
|
67
|
+
//
|
|
68
|
+
// Round 4 (review round 3, Critical 1): the pinned file can also be
|
|
69
|
+
// *stale* rather than missing — a re-dispatch omits `--session-id` (see
|
|
70
|
+
// `sessionExists`) precisely because attempt 1's file already owns this
|
|
71
|
+
// name, so a direct read here would silently keep returning attempt 1's
|
|
72
|
+
// transcript forever. `startedAt` is re-stamped by the conductor on
|
|
73
|
+
// every dispatch (`markRunning`), so a pinned file older than this
|
|
74
|
+
// attempt's own start can only be a leftover from a prior attempt —
|
|
75
|
+
// reject it and fall through to the scan, which is bounded by the same
|
|
76
|
+
// timestamp and will find the fresh, unpinned file this attempt wrote.
|
|
77
|
+
// GAIA-473 review round 4 (Critical 2): when the run's deterministic
|
|
78
|
+
// attempt family is known, select the HIGHEST-numbered attempt whose
|
|
79
|
+
// file actually exists — an IDENTITY, not the `startedAt` window below,
|
|
80
|
+
// which a sibling session newer than the bound could still beat. Each
|
|
81
|
+
// family member names a distinct attempt, so no staleness check is
|
|
82
|
+
// needed: whichever member exists IS that attempt's own transcript.
|
|
83
|
+
if (run?.attemptFamily?.length) {
|
|
84
|
+
for (let i = run.attemptFamily.length - 1; i >= 0; i--) {
|
|
85
|
+
try {
|
|
86
|
+
return await readFile(join(dir, `${run.attemptFamily[i]}.jsonl`), 'utf8');
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// try the next, lower-numbered attempt.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// AC-5: none of the family's files exist yet — a run dispatched before
|
|
93
|
+
// this change, or the agent never wrote its pinned file — so fall
|
|
94
|
+
// through to the scan below instead of returning ''.
|
|
95
|
+
}
|
|
96
|
+
else if (run?.sessionId) {
|
|
97
|
+
try {
|
|
98
|
+
const path = join(dir, `${run.sessionId}.jsonl`);
|
|
99
|
+
const isStale = startedAtMs !== null &&
|
|
100
|
+
Number.isFinite(startedAtMs) &&
|
|
101
|
+
(await stat(path)).mtimeMs < startedAtMs;
|
|
102
|
+
if (!isStale) {
|
|
103
|
+
return await readFile(path, 'utf8');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// fall through to the scan.
|
|
108
|
+
}
|
|
109
|
+
}
|
|
35
110
|
let entries;
|
|
36
111
|
try {
|
|
37
112
|
entries = await readdir(dir);
|
|
@@ -47,6 +122,11 @@ export class ClaudeAgent {
|
|
|
47
122
|
const full = join(dir, name);
|
|
48
123
|
try {
|
|
49
124
|
const s = await stat(full);
|
|
125
|
+
if (startedAtMs !== null &&
|
|
126
|
+
Number.isFinite(startedAtMs) &&
|
|
127
|
+
s.mtimeMs < startedAtMs) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
50
130
|
if (!latest || s.mtimeMs > latest.mtimeMs) {
|
|
51
131
|
latest = { path: full, mtimeMs: s.mtimeMs };
|
|
52
132
|
}
|
package/dist/src/preset.d.ts
CHANGED
package/dist/src/preset.js
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
import claudeAgent from './index.js';
|
|
2
|
-
export const agents = (acc, opts) =>
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
export const agents = (acc, opts) => {
|
|
3
|
+
if (opts?.model) {
|
|
4
|
+
return [
|
|
5
|
+
...acc,
|
|
6
|
+
{
|
|
7
|
+
agent: claudeAgent(opts),
|
|
8
|
+
model: opts.model,
|
|
9
|
+
description: 'Custom Claude variant; suitability depends on the configured model.',
|
|
10
|
+
},
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
return [
|
|
14
|
+
...acc,
|
|
15
|
+
{
|
|
16
|
+
alias: 'opus',
|
|
17
|
+
description: 'Strong reasoning for ambiguous design, diagnosis and demanding implementation.',
|
|
18
|
+
model: 'claude-opus-5',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
alias: 'sonnet',
|
|
22
|
+
description: 'Efficient implementation of well-specified changes and routine maintenance.',
|
|
23
|
+
model: 'claude-sonnet-5',
|
|
24
|
+
},
|
|
25
|
+
].map((candidate) => 'agent' in candidate
|
|
26
|
+
? candidate
|
|
27
|
+
: {
|
|
28
|
+
...candidate,
|
|
29
|
+
agent: claudeAgent({ ...opts, model: candidate.model }),
|
|
30
|
+
});
|
|
31
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaia-ai/addon-claude",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "GAIA agent plugin for the Claude CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"directory": "gaia-cli/addons/claude"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@gaia-ai/conductor": "^0.
|
|
24
|
-
"@gaia-ai/core": "^0.
|
|
23
|
+
"@gaia-ai/conductor": "^0.13.0",
|
|
24
|
+
"@gaia-ai/core": "^0.13.0"
|
|
25
25
|
}
|
|
26
26
|
}
|