@gobing-ai/ts-ai-runner 0.4.18 → 0.4.19
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/README.md +51 -0
- package/dist/agents/shims.d.ts +22 -0
- package/dist/agents/shims.d.ts.map +1 -1
- package/dist/agents/shims.js +76 -9
- package/package.json +4 -4
- package/src/agents/shims.ts +93 -9
package/README.md
CHANGED
|
@@ -291,6 +291,57 @@ const gitBlock = getGitContext('/workspace/spur');
|
|
|
291
291
|
// "Git context:\nbranch: feat/team-mode\ndirty: 3 files"
|
|
292
292
|
```
|
|
293
293
|
|
|
294
|
+
## Session Affinity (run-scoped sessions)
|
|
295
|
+
|
|
296
|
+
`PromptOptions` carries two additive fields for pinning or isolating sessions:
|
|
297
|
+
|
|
298
|
+
- `sessionId?: string` — resume a specific prior session when the agent supports resume-by-id.
|
|
299
|
+
- `sessionDir?: string` — isolate session storage into a caller-owned directory (durable).
|
|
300
|
+
|
|
301
|
+
When either is set, the shim selects the **run-scoped session path** and **never** emits an unscoped
|
|
302
|
+
global continue/last-session flag. This prevents a pipeline hop from attaching to the host's
|
|
303
|
+
interactive session (ADR-047).
|
|
304
|
+
|
|
305
|
+
**Precedence (R5):**
|
|
306
|
+
|
|
307
|
+
1. `sessionId` or `sessionDir` set → pin/isolate path; bare `continue` is ignored for global-last.
|
|
308
|
+
2. else `continue === true` → legacy resume-last (only where the agent allows it).
|
|
309
|
+
3. else → fresh open.
|
|
310
|
+
|
|
311
|
+
**Capability query (R2):** consult `getAgentSessionCapability(agent)` rather than inventing per-agent
|
|
312
|
+
argv. It reports `supportsResumeById` and `supportsSessionDir`.
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
import { getAgentSessionCapability, getAgentShim } from '@gobing-ai/ts-ai-runner';
|
|
316
|
+
|
|
317
|
+
const cap = getAgentSessionCapability('omp'); // { supportsResumeById: true, supportsSessionDir: true }
|
|
318
|
+
const { args } = getAgentShim('omp').getPromptCommand({
|
|
319
|
+
input: '',
|
|
320
|
+
sessionDir: '.spur/run/r1/agent-sessions/omp',
|
|
321
|
+
sessionId: 'abc123',
|
|
322
|
+
});
|
|
323
|
+
// args: ['-p', '', '--session-dir', '.spur/run/r1/...', '-r', 'abc123', '--mode', 'text']
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Shim / capability matrix:**
|
|
327
|
+
|
|
328
|
+
| Agent | `supportsResumeById` | `supportsSessionDir` | sessionDir flag | sessionId resume flag | Legacy continue |
|
|
329
|
+
| ----- | -------------------- | -------------------- | --------------- | --------------------- | --------------- |
|
|
330
|
+
| omp | ✓ | ✓ | `--session-dir <dir>` | `-r <id>` | `-c` |
|
|
331
|
+
| pi | ✓ | ✓ | `--session-dir <dir>` | `-r <id>` | `-c` |
|
|
332
|
+
| claude | ✓ | ✗ (ignored) | — | `--resume <id>` | `--continue` |
|
|
333
|
+
| codex | ✗ | ✗ (ignored) | — | degrade → fresh `exec` | `exec resume --last` |
|
|
334
|
+
| agy | ✓ | ✗ (ignored) | — | `--conversation <id>` | `--continue` |
|
|
335
|
+
| grok | ✓ | ✗ (ignored) | — | `--resume <id>` | `-c` |
|
|
336
|
+
|
|
337
|
+
**Degrade rule:** when `sessionDir`/`sessionId` is set and the agent lacks resume-by-id or a
|
|
338
|
+
session-store flag, the shim opens **fresh in isolation** (or plain fresh) — never bare global
|
|
339
|
+
continue/last. codex, which has no one-shot resume-by-id, always degrades to a fresh `exec`.
|
|
340
|
+
|
|
341
|
+
**Durable vs ephemeral (R4):** for omp/pi, setting `sessionDir` implies a durable session — the shim
|
|
342
|
+
omits `--no-session` so a session file is written and discoverable. The legacy fresh path (no session
|
|
343
|
+
fields, `continue` false) keeps `--no-session`.
|
|
344
|
+
|
|
294
345
|
## Observability
|
|
295
346
|
|
|
296
347
|
`AiRunner` and `TeamOrchestrator` emit typed events when an `EventBus<AgentEvents>` is provided:
|
package/dist/agents/shims.d.ts
CHANGED
|
@@ -15,6 +15,19 @@ export interface PromptOptions {
|
|
|
15
15
|
input?: string;
|
|
16
16
|
/** Continue the previous session if the agent supports it. */
|
|
17
17
|
continue?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Pin a specific session to resume, when the agent supports resume-by-id
|
|
20
|
+
* (see {@link getAgentSessionCapability}). Setting `sessionId` or `sessionDir`
|
|
21
|
+
* selects the run-scoped session path and suppresses any unscoped global
|
|
22
|
+
* continue/last-session flag (ADR-047 precedence R5).
|
|
23
|
+
*/
|
|
24
|
+
sessionId?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Isolate session storage into this directory (where the agent supports it).
|
|
27
|
+
* Implies a durable session: agents with a session-store flag (omp/pi) must not
|
|
28
|
+
* emit `--no-session` when this is set, so the session file is discoverable.
|
|
29
|
+
*/
|
|
30
|
+
sessionDir?: string;
|
|
18
31
|
/** Model identifier passed through to the agent CLI. */
|
|
19
32
|
model?: string;
|
|
20
33
|
/** Output mode passed through to the agent CLI. */
|
|
@@ -64,6 +77,15 @@ export interface AgentShim {
|
|
|
64
77
|
}
|
|
65
78
|
/** All bundled agent shims keyed by canonical agent name. */
|
|
66
79
|
export declare const AGENT_SHIMS: Readonly<Record<AgentName, AgentShim>>;
|
|
80
|
+
/** Session-affinity capability for one coding agent (ADR-047). */
|
|
81
|
+
export interface AgentSessionCapability {
|
|
82
|
+
/** Can resume a specific prior session by id (e.g. `-r <id>` / `--resume <id>`). */
|
|
83
|
+
readonly supportsResumeById: boolean;
|
|
84
|
+
/** Can isolate session storage into a caller-supplied directory. */
|
|
85
|
+
readonly supportsSessionDir: boolean;
|
|
86
|
+
}
|
|
87
|
+
/** Query a bundled agent's session-affinity capability by canonical name. */
|
|
88
|
+
export declare function getAgentSessionCapability(agent: AgentName): AgentSessionCapability;
|
|
67
89
|
/** Tier-1 auto-selection priority. Deprecated ids are excluded. */
|
|
68
90
|
export declare const TIER1_PRIORITY: readonly AgentName[];
|
|
69
91
|
/** Display order for doctor and list commands. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shims.d.ts","sourceRoot":"","sources":["../../src/agents/shims.ts"],"names":[],"mappings":"AAEA,gEAAgE;AAChE,MAAM,MAAM,SAAS,GACf,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,IAAI,GACJ,UAAU,GACV,iBAAiB,GACjB,UAAU,GACV,QAAQ,GACR,KAAK,GACL,MAAM,CAAC;AAEb,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzC,mEAAmE;AACnE,MAAM,WAAW,WAAW;IACxB,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC1B,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjE;AAED,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC7B,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC;CACnC;AAED,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACtB,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,+BAA+B;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,wFAAwF;IACxF,QAAQ,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IACvC,oCAAoC;IACpC,cAAc,IAAI,WAAW,CAAC;IAC9B,yCAAyC;IACzC,iBAAiB,IAAI,WAAW,CAAC;IACjC,yCAAyC;IACzC,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,WAAW,CAAC;IACtD,8DAA8D;IAC9D,cAAc,IAAI,WAAW,GAAG,IAAI,CAAC;CACxC;
|
|
1
|
+
{"version":3,"file":"shims.d.ts","sourceRoot":"","sources":["../../src/agents/shims.ts"],"names":[],"mappings":"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"}
|
package/dist/agents/shims.js
CHANGED
|
@@ -7,8 +7,16 @@ const claudeShim = {
|
|
|
7
7
|
getVersionCommand: () => ({ command: 'claude', args: ['--version'] }),
|
|
8
8
|
getPromptCommand: (options) => {
|
|
9
9
|
const args = ['-p', options.input ?? ''];
|
|
10
|
-
|
|
10
|
+
const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
|
|
11
|
+
if (hasSession) {
|
|
12
|
+
// Session/pin path — never emit --continue. Claude has no session-dir
|
|
13
|
+
// flag; sessionDir is ignored (best-effort isolate).
|
|
14
|
+
if (options.sessionId !== undefined)
|
|
15
|
+
args.push('--resume', options.sessionId);
|
|
16
|
+
}
|
|
17
|
+
else if (options.continue === true) {
|
|
11
18
|
args.push('--continue');
|
|
19
|
+
}
|
|
12
20
|
if (options.model !== undefined)
|
|
13
21
|
args.push('--model', options.model);
|
|
14
22
|
args.push('--output-format', options.mode ?? 'text');
|
|
@@ -23,10 +31,13 @@ const codexShim = {
|
|
|
23
31
|
getHelpCommand: () => ({ command: 'codex', args: ['--help'] }),
|
|
24
32
|
getVersionCommand: () => ({ command: 'codex', args: ['--version'] }),
|
|
25
33
|
getPromptCommand: (options) => {
|
|
26
|
-
|
|
34
|
+
const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
|
|
35
|
+
// Session/pin path degrades to a fresh `exec` — codex has no reliable
|
|
36
|
+
// one-shot resume-by-id, so never `exec resume --last` (no global last).
|
|
37
|
+
if (options.continue === true && !hasSession && options.input !== undefined) {
|
|
27
38
|
throw new Error('Codex resume mode does not accept a new prompt');
|
|
28
39
|
}
|
|
29
|
-
const args = options.continue === true ? ['exec', 'resume', '--last'] : ['exec', options.input ?? ''];
|
|
40
|
+
const args = options.continue === true && !hasSession ? ['exec', 'resume', '--last'] : ['exec', options.input ?? ''];
|
|
30
41
|
if (options.model !== undefined)
|
|
31
42
|
args.push('-m', options.model);
|
|
32
43
|
if ((options.mode ?? 'text') === 'json')
|
|
@@ -61,11 +72,17 @@ const piShim = {
|
|
|
61
72
|
getVersionCommand: () => ({ command: 'pi', args: ['--version'] }),
|
|
62
73
|
getPromptCommand: (options) => {
|
|
63
74
|
const args = [];
|
|
64
|
-
|
|
75
|
+
const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
|
|
76
|
+
// Session/pin path: durable — never ephemeral --no-session, never global -c.
|
|
77
|
+
if (!hasSession && options.continue !== true)
|
|
65
78
|
args.push('--no-session');
|
|
66
79
|
args.push('-p', options.input ?? '');
|
|
67
|
-
if (options.continue === true)
|
|
80
|
+
if (!hasSession && options.continue === true)
|
|
68
81
|
args.push('-c');
|
|
82
|
+
if (options.sessionDir !== undefined)
|
|
83
|
+
args.push('--session-dir', options.sessionDir);
|
|
84
|
+
if (options.sessionId !== undefined)
|
|
85
|
+
args.push('-r', options.sessionId);
|
|
69
86
|
if (options.model !== undefined)
|
|
70
87
|
args.push('--model', options.model);
|
|
71
88
|
args.push('--mode', options.mode ?? 'text');
|
|
@@ -105,8 +122,16 @@ const antigravityCliShim = {
|
|
|
105
122
|
getVersionCommand: () => ({ command: 'agy', args: ['--version'] }),
|
|
106
123
|
getPromptCommand: (options) => {
|
|
107
124
|
const args = ['-p', options.input ?? ''];
|
|
108
|
-
|
|
125
|
+
const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
|
|
126
|
+
if (hasSession) {
|
|
127
|
+
// Session/pin path — never emit --continue. agy has no session-dir flag;
|
|
128
|
+
// sessionDir is ignored (best-effort isolate).
|
|
129
|
+
if (options.sessionId !== undefined)
|
|
130
|
+
args.push('--conversation', options.sessionId);
|
|
131
|
+
}
|
|
132
|
+
else if (options.continue === true) {
|
|
109
133
|
args.push('--continue');
|
|
134
|
+
}
|
|
110
135
|
if (options.model !== undefined)
|
|
111
136
|
args.push('--model', options.model);
|
|
112
137
|
return { command: 'agy', args };
|
|
@@ -154,11 +179,17 @@ const ompShim = {
|
|
|
154
179
|
getVersionCommand: () => ({ command: 'omp', args: ['--version'] }),
|
|
155
180
|
getPromptCommand: (options) => {
|
|
156
181
|
const args = [];
|
|
157
|
-
|
|
182
|
+
const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
|
|
183
|
+
// Session/pin path: durable — never ephemeral --no-session, never global -c.
|
|
184
|
+
if (!hasSession && options.continue !== true)
|
|
158
185
|
args.push('--no-session');
|
|
159
186
|
args.push('-p', options.input ?? '');
|
|
160
|
-
if (options.continue === true)
|
|
187
|
+
if (!hasSession && options.continue === true)
|
|
161
188
|
args.push('-c');
|
|
189
|
+
if (options.sessionDir !== undefined)
|
|
190
|
+
args.push('--session-dir', options.sessionDir);
|
|
191
|
+
if (options.sessionId !== undefined)
|
|
192
|
+
args.push('-r', options.sessionId);
|
|
162
193
|
if (options.model !== undefined)
|
|
163
194
|
args.push('--model', options.model);
|
|
164
195
|
args.push('--mode', options.mode ?? 'text');
|
|
@@ -180,8 +211,16 @@ const grokShim = {
|
|
|
180
211
|
getVersionCommand: () => ({ command: 'grok', args: ['--version'] }),
|
|
181
212
|
getPromptCommand: (options) => {
|
|
182
213
|
const args = ['-p', options.input ?? ''];
|
|
183
|
-
|
|
214
|
+
const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
|
|
215
|
+
if (hasSession) {
|
|
216
|
+
// Session/pin path — never emit -c. grok has no session-dir flag;
|
|
217
|
+
// sessionDir is ignored (best-effort isolate).
|
|
218
|
+
if (options.sessionId !== undefined)
|
|
219
|
+
args.push('--resume', options.sessionId);
|
|
220
|
+
}
|
|
221
|
+
else if (options.continue === true) {
|
|
184
222
|
args.push('-c');
|
|
223
|
+
}
|
|
185
224
|
if (options.model !== undefined)
|
|
186
225
|
args.push('-m', options.model);
|
|
187
226
|
// Grok has no `text` format; map ai-runner OutputMode `text` → `plain`.
|
|
@@ -204,6 +243,34 @@ export const AGENT_SHIMS = {
|
|
|
204
243
|
omp: ompShim,
|
|
205
244
|
grok: grokShim,
|
|
206
245
|
};
|
|
246
|
+
/**
|
|
247
|
+
* Session-affinity capability metadata per agent. Callers must consult this
|
|
248
|
+
* instead of inventing per-agent argv: when `sessionDir`/`sessionId` are set,
|
|
249
|
+
* `supportsResumeById` decides resume-by-id vs fresh-degrade, and
|
|
250
|
+
* `supportsSessionDir` decides whether `sessionDir` is honored (ADR-047 R2).
|
|
251
|
+
*
|
|
252
|
+
* Agents not in the six-agent affinity matrix default to no resume-by-id and no
|
|
253
|
+
* session-dir — they get the isolated-fresh / no-resume degrade.
|
|
254
|
+
*/
|
|
255
|
+
const AGENT_SESSION_CAPABILITY = {
|
|
256
|
+
omp: { supportsResumeById: true, supportsSessionDir: true },
|
|
257
|
+
pi: { supportsResumeById: true, supportsSessionDir: true },
|
|
258
|
+
claude: { supportsResumeById: true, supportsSessionDir: false },
|
|
259
|
+
// codex resume is interactive-only (`exec resume` picker); no one-shot
|
|
260
|
+
// resume-by-id and no session-dir — degrades to fresh exec.
|
|
261
|
+
codex: { supportsResumeById: false, supportsSessionDir: false },
|
|
262
|
+
'antigravity-cli': { supportsResumeById: true, supportsSessionDir: false },
|
|
263
|
+
grok: { supportsResumeById: true, supportsSessionDir: false },
|
|
264
|
+
// Non-matrix agents: conservative default (isolated-fresh / no-resume).
|
|
265
|
+
gemini: { supportsResumeById: false, supportsSessionDir: false },
|
|
266
|
+
opencode: { supportsResumeById: false, supportsSessionDir: false },
|
|
267
|
+
openclaw: { supportsResumeById: false, supportsSessionDir: false },
|
|
268
|
+
hermes: { supportsResumeById: false, supportsSessionDir: false },
|
|
269
|
+
};
|
|
270
|
+
/** Query a bundled agent's session-affinity capability by canonical name. */
|
|
271
|
+
export function getAgentSessionCapability(agent) {
|
|
272
|
+
return AGENT_SESSION_CAPABILITY[agent];
|
|
273
|
+
}
|
|
207
274
|
/** Tier-1 auto-selection priority. Deprecated ids are excluded. */
|
|
208
275
|
export const TIER1_PRIORITY = [
|
|
209
276
|
'pi',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-ai-runner",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.19",
|
|
4
4
|
"description": "@gobing-ai/ts-ai-runner — Coding-agent shims, detection, doctor checks, and prompt execution.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-ai-runner-v<version> && git push --tags' && exit 1"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@gobing-ai/ts-infra": "^0.4.
|
|
51
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
50
|
+
"@gobing-ai/ts-infra": "^0.4.19",
|
|
51
|
+
"@gobing-ai/ts-runtime": "^0.4.19"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/bun": "1.3.14",
|
|
55
|
-
"@gobing-ai/ts-db": "^0.4.
|
|
55
|
+
"@gobing-ai/ts-db": "^0.4.19"
|
|
56
56
|
},
|
|
57
57
|
"publishConfig": {
|
|
58
58
|
"access": "public"
|
package/src/agents/shims.ts
CHANGED
|
@@ -30,6 +30,19 @@ export interface PromptOptions {
|
|
|
30
30
|
input?: string;
|
|
31
31
|
/** Continue the previous session if the agent supports it. */
|
|
32
32
|
continue?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Pin a specific session to resume, when the agent supports resume-by-id
|
|
35
|
+
* (see {@link getAgentSessionCapability}). Setting `sessionId` or `sessionDir`
|
|
36
|
+
* selects the run-scoped session path and suppresses any unscoped global
|
|
37
|
+
* continue/last-session flag (ADR-047 precedence R5).
|
|
38
|
+
*/
|
|
39
|
+
sessionId?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Isolate session storage into this directory (where the agent supports it).
|
|
42
|
+
* Implies a durable session: agents with a session-store flag (omp/pi) must not
|
|
43
|
+
* emit `--no-session` when this is set, so the session file is discoverable.
|
|
44
|
+
*/
|
|
45
|
+
sessionDir?: string;
|
|
33
46
|
/** Model identifier passed through to the agent CLI. */
|
|
34
47
|
model?: string;
|
|
35
48
|
/** Output mode passed through to the agent CLI. */
|
|
@@ -84,7 +97,14 @@ const claudeShim: AgentShim = {
|
|
|
84
97
|
getVersionCommand: () => ({ command: 'claude', args: ['--version'] }),
|
|
85
98
|
getPromptCommand: (options) => {
|
|
86
99
|
const args = ['-p', options.input ?? ''];
|
|
87
|
-
|
|
100
|
+
const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
|
|
101
|
+
if (hasSession) {
|
|
102
|
+
// Session/pin path — never emit --continue. Claude has no session-dir
|
|
103
|
+
// flag; sessionDir is ignored (best-effort isolate).
|
|
104
|
+
if (options.sessionId !== undefined) args.push('--resume', options.sessionId);
|
|
105
|
+
} else if (options.continue === true) {
|
|
106
|
+
args.push('--continue');
|
|
107
|
+
}
|
|
88
108
|
if (options.model !== undefined) args.push('--model', options.model);
|
|
89
109
|
args.push('--output-format', options.mode ?? 'text');
|
|
90
110
|
return { command: 'claude', args };
|
|
@@ -99,10 +119,14 @@ const codexShim: AgentShim = {
|
|
|
99
119
|
getHelpCommand: () => ({ command: 'codex', args: ['--help'] }),
|
|
100
120
|
getVersionCommand: () => ({ command: 'codex', args: ['--version'] }),
|
|
101
121
|
getPromptCommand: (options) => {
|
|
102
|
-
|
|
122
|
+
const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
|
|
123
|
+
// Session/pin path degrades to a fresh `exec` — codex has no reliable
|
|
124
|
+
// one-shot resume-by-id, so never `exec resume --last` (no global last).
|
|
125
|
+
if (options.continue === true && !hasSession && options.input !== undefined) {
|
|
103
126
|
throw new Error('Codex resume mode does not accept a new prompt');
|
|
104
127
|
}
|
|
105
|
-
const args =
|
|
128
|
+
const args =
|
|
129
|
+
options.continue === true && !hasSession ? ['exec', 'resume', '--last'] : ['exec', options.input ?? ''];
|
|
106
130
|
if (options.model !== undefined) args.push('-m', options.model);
|
|
107
131
|
if ((options.mode ?? 'text') === 'json') args.push('--json');
|
|
108
132
|
return { command: 'codex', args };
|
|
@@ -135,9 +159,13 @@ const piShim: AgentShim = {
|
|
|
135
159
|
getVersionCommand: () => ({ command: 'pi', args: ['--version'] }),
|
|
136
160
|
getPromptCommand: (options) => {
|
|
137
161
|
const args: string[] = [];
|
|
138
|
-
|
|
162
|
+
const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
|
|
163
|
+
// Session/pin path: durable — never ephemeral --no-session, never global -c.
|
|
164
|
+
if (!hasSession && options.continue !== true) args.push('--no-session');
|
|
139
165
|
args.push('-p', options.input ?? '');
|
|
140
|
-
if (options.continue === true) args.push('-c');
|
|
166
|
+
if (!hasSession && options.continue === true) args.push('-c');
|
|
167
|
+
if (options.sessionDir !== undefined) args.push('--session-dir', options.sessionDir);
|
|
168
|
+
if (options.sessionId !== undefined) args.push('-r', options.sessionId);
|
|
141
169
|
if (options.model !== undefined) args.push('--model', options.model);
|
|
142
170
|
args.push('--mode', options.mode ?? 'text');
|
|
143
171
|
return { command: 'pi', args };
|
|
@@ -175,7 +203,14 @@ const antigravityCliShim: AgentShim = {
|
|
|
175
203
|
getVersionCommand: () => ({ command: 'agy', args: ['--version'] }),
|
|
176
204
|
getPromptCommand: (options) => {
|
|
177
205
|
const args = ['-p', options.input ?? ''];
|
|
178
|
-
|
|
206
|
+
const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
|
|
207
|
+
if (hasSession) {
|
|
208
|
+
// Session/pin path — never emit --continue. agy has no session-dir flag;
|
|
209
|
+
// sessionDir is ignored (best-effort isolate).
|
|
210
|
+
if (options.sessionId !== undefined) args.push('--conversation', options.sessionId);
|
|
211
|
+
} else if (options.continue === true) {
|
|
212
|
+
args.push('--continue');
|
|
213
|
+
}
|
|
179
214
|
if (options.model !== undefined) args.push('--model', options.model);
|
|
180
215
|
return { command: 'agy', args };
|
|
181
216
|
},
|
|
@@ -223,9 +258,13 @@ const ompShim: AgentShim = {
|
|
|
223
258
|
getVersionCommand: () => ({ command: 'omp', args: ['--version'] }),
|
|
224
259
|
getPromptCommand: (options) => {
|
|
225
260
|
const args: string[] = [];
|
|
226
|
-
|
|
261
|
+
const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
|
|
262
|
+
// Session/pin path: durable — never ephemeral --no-session, never global -c.
|
|
263
|
+
if (!hasSession && options.continue !== true) args.push('--no-session');
|
|
227
264
|
args.push('-p', options.input ?? '');
|
|
228
|
-
if (options.continue === true) args.push('-c');
|
|
265
|
+
if (!hasSession && options.continue === true) args.push('-c');
|
|
266
|
+
if (options.sessionDir !== undefined) args.push('--session-dir', options.sessionDir);
|
|
267
|
+
if (options.sessionId !== undefined) args.push('-r', options.sessionId);
|
|
229
268
|
if (options.model !== undefined) args.push('--model', options.model);
|
|
230
269
|
args.push('--mode', options.mode ?? 'text');
|
|
231
270
|
return { command: 'omp', args };
|
|
@@ -247,7 +286,14 @@ const grokShim: AgentShim = {
|
|
|
247
286
|
getVersionCommand: () => ({ command: 'grok', args: ['--version'] }),
|
|
248
287
|
getPromptCommand: (options) => {
|
|
249
288
|
const args = ['-p', options.input ?? ''];
|
|
250
|
-
|
|
289
|
+
const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
|
|
290
|
+
if (hasSession) {
|
|
291
|
+
// Session/pin path — never emit -c. grok has no session-dir flag;
|
|
292
|
+
// sessionDir is ignored (best-effort isolate).
|
|
293
|
+
if (options.sessionId !== undefined) args.push('--resume', options.sessionId);
|
|
294
|
+
} else if (options.continue === true) {
|
|
295
|
+
args.push('-c');
|
|
296
|
+
}
|
|
251
297
|
if (options.model !== undefined) args.push('-m', options.model);
|
|
252
298
|
// Grok has no `text` format; map ai-runner OutputMode `text` → `plain`.
|
|
253
299
|
const format = (options.mode ?? 'text') === 'json' ? 'json' : 'plain';
|
|
@@ -271,6 +317,44 @@ export const AGENT_SHIMS: Readonly<Record<AgentName, AgentShim>> = {
|
|
|
271
317
|
grok: grokShim,
|
|
272
318
|
};
|
|
273
319
|
|
|
320
|
+
/** Session-affinity capability for one coding agent (ADR-047). */
|
|
321
|
+
export interface AgentSessionCapability {
|
|
322
|
+
/** Can resume a specific prior session by id (e.g. `-r <id>` / `--resume <id>`). */
|
|
323
|
+
readonly supportsResumeById: boolean;
|
|
324
|
+
/** Can isolate session storage into a caller-supplied directory. */
|
|
325
|
+
readonly supportsSessionDir: boolean;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Session-affinity capability metadata per agent. Callers must consult this
|
|
330
|
+
* instead of inventing per-agent argv: when `sessionDir`/`sessionId` are set,
|
|
331
|
+
* `supportsResumeById` decides resume-by-id vs fresh-degrade, and
|
|
332
|
+
* `supportsSessionDir` decides whether `sessionDir` is honored (ADR-047 R2).
|
|
333
|
+
*
|
|
334
|
+
* Agents not in the six-agent affinity matrix default to no resume-by-id and no
|
|
335
|
+
* session-dir — they get the isolated-fresh / no-resume degrade.
|
|
336
|
+
*/
|
|
337
|
+
const AGENT_SESSION_CAPABILITY: Readonly<Record<AgentName, AgentSessionCapability>> = {
|
|
338
|
+
omp: { supportsResumeById: true, supportsSessionDir: true },
|
|
339
|
+
pi: { supportsResumeById: true, supportsSessionDir: true },
|
|
340
|
+
claude: { supportsResumeById: true, supportsSessionDir: false },
|
|
341
|
+
// codex resume is interactive-only (`exec resume` picker); no one-shot
|
|
342
|
+
// resume-by-id and no session-dir — degrades to fresh exec.
|
|
343
|
+
codex: { supportsResumeById: false, supportsSessionDir: false },
|
|
344
|
+
'antigravity-cli': { supportsResumeById: true, supportsSessionDir: false },
|
|
345
|
+
grok: { supportsResumeById: true, supportsSessionDir: false },
|
|
346
|
+
// Non-matrix agents: conservative default (isolated-fresh / no-resume).
|
|
347
|
+
gemini: { supportsResumeById: false, supportsSessionDir: false },
|
|
348
|
+
opencode: { supportsResumeById: false, supportsSessionDir: false },
|
|
349
|
+
openclaw: { supportsResumeById: false, supportsSessionDir: false },
|
|
350
|
+
hermes: { supportsResumeById: false, supportsSessionDir: false },
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/** Query a bundled agent's session-affinity capability by canonical name. */
|
|
354
|
+
export function getAgentSessionCapability(agent: AgentName): AgentSessionCapability {
|
|
355
|
+
return AGENT_SESSION_CAPABILITY[agent];
|
|
356
|
+
}
|
|
357
|
+
|
|
274
358
|
/** Tier-1 auto-selection priority. Deprecated ids are excluded. */
|
|
275
359
|
export const TIER1_PRIORITY: readonly AgentName[] = [
|
|
276
360
|
'pi',
|