@gobing-ai/ts-ai-runner 0.4.68 → 0.4.69

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.
@@ -54,7 +54,21 @@ export interface PromptOptions {
54
54
  type: string;
55
55
  purpose?: string;
56
56
  }>;
57
+ /**
58
+ * Request the agent's persistent-stdin dispatch: argv starts a long-lived
59
+ * process that reads framed prompts from stdin (one per line) instead of
60
+ * consuming a one-shot `-p <input>` and exiting. Only shims exposing a
61
+ * `persistentStdinProtocol` accept this — callers must consult
62
+ * {@link getAgentSessionCapability}.`supportsPersistentStdin` first. In
63
+ * persistent mode the shim omits `input` from argv entirely; the caller
64
+ * writes the identity preamble / prompts via the shim's `frame` (see
65
+ * `TeamAgentProcess` `stdinFramer`). rpc/stream-json modes supersede
66
+ * `mode` (the structured-output flag rides inside the protocol).
67
+ */
68
+ persistentStdin?: boolean;
57
69
  }
70
+ /** Frames one user prompt as a single stdin line for a persistent-stdin dispatch. */
71
+ export type StdinFrame = (input: string) => string;
58
72
  /** Deprecation metadata attached to a retired or superseded agent shim. */
59
73
  export interface AgentDeprecation {
60
74
  /** Release date (ISO) when the id was marked deprecated. */
@@ -82,6 +96,15 @@ export interface AgentShim {
82
96
  getPromptCommand(options: PromptOptions): ShimCommand;
83
97
  /** Build an auth-status command, or null when unsupported. */
84
98
  getAuthCommand(): ShimCommand | null;
99
+ /**
100
+ * Persistent-stdin framing contract; present iff the shim wires a
101
+ * stdin-listener dispatch (mirrors `supportsPersistentStdin`). Callers
102
+ * frame every prompt (including the identity preamble) through it before
103
+ * writing to the process stdin.
104
+ */
105
+ readonly persistentStdinProtocol?: {
106
+ readonly frame: StdinFrame;
107
+ };
85
108
  }
86
109
  /** All bundled agent shims keyed by canonical agent name. */
87
110
  export declare const AGENT_SHIMS: Readonly<Record<AgentName, AgentShim>>;
@@ -1 +1 @@
1
- {"version":3,"file":"shims.d.ts","sourceRoot":"","sources":["../../src/agents/shims.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shims.d.ts","sourceRoot":"","sources":["../../src/agents/shims.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,8 @@
1
1
  import { getLogger } from '@gobing-ai/ts-infra';
2
+ /** pi-family rpc dialect (pi docs/rpc.md): one JSON object per line. */
3
+ const piRpcFrame = (input) => `${JSON.stringify({ type: 'prompt', message: input })}\n`;
4
+ /** claude stream-json input dialect: one user-message JSONL envelope per line. */
5
+ const claudeStreamJsonFrame = (input) => `${JSON.stringify({ type: 'user', message: { role: 'user', content: [{ type: 'text', text: input }] } })}\n`;
2
6
  const claudeShim = {
3
7
  name: 'claude',
4
8
  command: 'claude',
@@ -8,7 +12,22 @@ const claudeShim = {
8
12
  getPromptCommand: (options) => {
9
13
  // Headless dispatch cannot answer edit approval prompts. Keep the grant
10
14
  // scoped to file edits; shell and broader tools remain gated.
11
- const args = ['-p', options.input ?? '', '--permission-mode', 'acceptEdits', '--allowedTools', 'Write', 'Edit'];
15
+ // Persistent stdin rides the print path: `--input-format` only works
16
+ // with --print, and stream-json input keeps the process alive reading
17
+ // JSONL envelopes until stdin closes (verified --help, CLI 2.1.274).
18
+ const persistent = options.persistentStdin === true;
19
+ const args = persistent
20
+ ? [
21
+ '-p',
22
+ '--input-format',
23
+ 'stream-json',
24
+ '--permission-mode',
25
+ 'acceptEdits',
26
+ '--allowedTools',
27
+ 'Write',
28
+ 'Edit',
29
+ ]
30
+ : ['-p', options.input ?? '', '--permission-mode', 'acceptEdits', '--allowedTools', 'Write', 'Edit'];
12
31
  const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
13
32
  if (hasSession) {
14
33
  // Session/pin path — never emit --continue. Claude has no session-dir
@@ -21,10 +40,13 @@ const claudeShim = {
21
40
  }
22
41
  if (options.model !== undefined)
23
42
  args.push('--model', options.model);
24
- args.push('--output-format', options.mode ?? 'text');
43
+ // stream-json output is part of the persistent protocol (the framing
44
+ // assumes JSONL stdout); one-shot keeps the caller-requested mode.
45
+ args.push('--output-format', persistent ? 'stream-json' : (options.mode ?? 'text'));
25
46
  return { command: 'claude', args };
26
47
  },
27
48
  getAuthCommand: () => ({ command: 'claude', args: ['auth', 'status'] }),
49
+ persistentStdinProtocol: { frame: claudeStreamJsonFrame },
28
50
  };
29
51
  const codexShim = {
30
52
  name: 'codex',
@@ -82,11 +104,16 @@ const piShim = {
82
104
  getPromptCommand: (options) => {
83
105
  const args = [];
84
106
  const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
107
+ const persistent = options.persistentStdin === true;
85
108
  // Session/pin path: durable — never ephemeral --no-session, never global -c.
86
- if (!hasSession && options.continue !== true)
109
+ if (!hasSession && !persistent && options.continue !== true)
87
110
  args.push('--no-session');
88
- args.push('-p', options.input ?? '');
89
- if (!hasSession && options.continue === true)
111
+ // Persistent stdin: no -p positional (prompts arrive framed over stdin);
112
+ // rpc mode is the long-lived listener (verified headless probe + pi
113
+ // docs/rpc.md). It supersedes any text/json output mode.
114
+ if (!persistent)
115
+ args.push('-p', options.input ?? '');
116
+ if (!hasSession && !persistent && options.continue === true)
90
117
  args.push('-c');
91
118
  if (options.sessionDir !== undefined)
92
119
  args.push('--session-dir', options.sessionDir);
@@ -94,10 +121,11 @@ const piShim = {
94
121
  args.push('-r', options.sessionId);
95
122
  if (options.model !== undefined)
96
123
  args.push('--model', options.model);
97
- args.push('--mode', options.mode ?? 'text');
124
+ args.push('--mode', persistent ? 'rpc' : (options.mode ?? 'text'));
98
125
  return { command: 'pi', args };
99
126
  },
100
127
  getAuthCommand: () => ({ command: 'pi', args: ['--list-models'] }),
128
+ persistentStdinProtocol: { frame: piRpcFrame },
101
129
  };
102
130
  const opencodeShim = {
103
131
  name: 'opencode',
@@ -206,11 +234,15 @@ const ompShim = {
206
234
  getPromptCommand: (options) => {
207
235
  const args = [];
208
236
  const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
237
+ const persistent = options.persistentStdin === true;
209
238
  // Session/pin path: durable — never ephemeral --no-session, never global -c.
210
- if (!hasSession && options.continue !== true)
239
+ if (!hasSession && !persistent && options.continue !== true)
211
240
  args.push('--no-session');
212
- args.push('-p', options.input ?? '');
213
- if (!hasSession && options.continue === true)
241
+ // Persistent stdin mirrors pi (omp is a pi fork; same rpc dialect) —
242
+ // verified `--mode rpc` in `omp --help` at the pi-compatible surface.
243
+ if (!persistent)
244
+ args.push('-p', options.input ?? '');
245
+ if (!hasSession && !persistent && options.continue === true)
214
246
  args.push('-c');
215
247
  if (options.sessionDir !== undefined)
216
248
  args.push('--session-dir', options.sessionDir);
@@ -218,10 +250,11 @@ const ompShim = {
218
250
  args.push('-r', options.sessionId);
219
251
  if (options.model !== undefined)
220
252
  args.push('--model', options.model);
221
- args.push('--mode', options.mode ?? 'text');
253
+ args.push('--mode', persistent ? 'rpc' : (options.mode ?? 'text'));
222
254
  return { command: 'omp', args };
223
255
  },
224
256
  getAuthCommand: () => ({ command: 'omp', args: ['--list-models'] }),
257
+ persistentStdinProtocol: { frame: piRpcFrame },
225
258
  };
226
259
  /**
227
260
  * Grok Build CLI (`grok`) — xAI coding agent. Headless one-shot via `-p`/`--single`;
@@ -331,9 +364,10 @@ const AGENT_SESSION_CAPABILITY = {
331
364
  supportsPersistentStdin: true,
332
365
  supportsStructuredOutput: true,
333
366
  verifiedAgainst: '2.1.274',
334
- // Verified `--input-format stream-json` (realtime streaming input) at CLI
335
- // 2.1.274, but the shim has no persistent-stdin dispatch mode yet.
336
- note: 'no session-dir flag — sessionDir is ignored (best-effort isolate); persistent stdin (--input-format stream-json) is not yet shim-wired',
367
+ // Persistent stdin is shim-wired: `-p --input-format stream-json --output-format
368
+ // stream-json` keeps the process alive reading JSONL envelopes (verified --help;
369
+ // --input-format only works with --print).
370
+ note: 'no session-dir flag — sessionDir is ignored (best-effort isolate)',
337
371
  },
338
372
  codex: {
339
373
  supportsResumeById: true,
@@ -2,6 +2,7 @@ import { Buffer } from 'node:buffer';
2
2
  import { type EventBus, type Logger } from '@gobing-ai/ts-infra';
3
3
  import { type ProcessExecutor } from '@gobing-ai/ts-runtime';
4
4
  import type { AgentSpec } from './agent-spec';
5
+ import type { StdinFrame } from './agents/shims';
5
6
  import type { AgentEvents } from './events';
6
7
  import { type QuotaAttribution } from './quota';
7
8
  /** Options for spawning a team agent subprocess. */
@@ -16,6 +17,11 @@ export interface AgentProcessOptions {
16
17
  events?: EventBus<AgentEvents>;
17
18
  /** Optional exact quota attribution; absent fields stay absent on observations — never inferred. */
18
19
  quotaContext?: QuotaAttribution;
20
+ /**
21
+ * Frames each sent prompt for the agent's persistent-stdin dialect (from
22
+ * the shim's `persistentStdinProtocol.frame`); absent = raw text + newline.
23
+ */
24
+ stdinFramer?: StdinFrame;
19
25
  }
20
26
  type ProcessStatus = 'running' | 'stopped' | 'errored';
21
27
  /**
@@ -38,6 +44,7 @@ export declare class TeamAgentProcess {
38
44
  private readonly quotaProducer;
39
45
  private readonly spec;
40
46
  private readonly quotaContext;
47
+ private readonly stdinFramer;
41
48
  constructor(options: AgentProcessOptions);
42
49
  start(): Promise<void>;
43
50
  stop(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"team-agent-process.d.ts","sourceRoot":"","sources":["../src/team-agent-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,QAAQ,EAAa,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAoC,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAIH,KAAK,gBAAgB,EAExB,MAAM,SAAS,CAAC;AAEjB,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sHAAsH;IACtH,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC/B,oGAAoG;IACpG,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACnC;AAED,KAAK,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEvD;;;GAGG;AACH,qBAAa,gBAAgB;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqC;IACzD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqC;IACjE,kGAAkG;IAClG,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;IACzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA+B;gBAEhD,OAAO,EAAE,mBAAmB;IAYlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BrB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;IAerD,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAOvD,SAAS,IAAI,aAAa;IAI1B,MAAM,IAAI,MAAM,GAAG,IAAI;IAIvB,WAAW,IAAI,MAAM,GAAG,IAAI;YAId,IAAI;IAkBlB,sHAAsH;IACtH,OAAO,CAAC,gBAAgB;IAIxB,qGAAqG;IACrG,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,IAAI;CAOf"}
1
+ {"version":3,"file":"team-agent-process.d.ts","sourceRoot":"","sources":["../src/team-agent-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,QAAQ,EAAa,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAoC,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAIH,KAAK,gBAAgB,EAExB,MAAM,SAAS,CAAC;AAEjB,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sHAAsH;IACtH,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC/B,oGAAoG;IACpG,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC;;;OAGG;IACH,WAAW,CAAC,EAAE,UAAU,CAAC;CAC5B;AAED,KAAK,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEvD;;;GAGG;AACH,qBAAa,gBAAgB;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqC;IACzD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqC;IACjE,kGAAkG;IAClG,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;IACzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA+B;IAC5D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;gBAEzC,OAAO,EAAE,mBAAmB;IAalC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BrB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;IAerD,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAOvD,SAAS,IAAI,aAAa;IAI1B,MAAM,IAAI,MAAM,GAAG,IAAI;IAIvB,WAAW,IAAI,MAAM,GAAG,IAAI;YAId,IAAI;IAkBlB,sHAAsH;IACtH,OAAO,CAAC,gBAAgB;IAIxB,qGAAqG;IACrG,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,IAAI;CAOf"}
@@ -22,6 +22,7 @@ export class TeamAgentProcess {
22
22
  quotaProducer;
23
23
  spec;
24
24
  quotaContext;
25
+ stdinFramer;
25
26
  constructor(options) {
26
27
  this.agentId = options.spec.id;
27
28
  this.command = options.command;
@@ -32,6 +33,7 @@ export class TeamAgentProcess {
32
33
  this.quotaProducer = new QuotaObservationProducer(options.events);
33
34
  this.spec = options.spec;
34
35
  this.quotaContext = options.quotaContext;
36
+ this.stdinFramer = options.stdinFramer;
35
37
  }
36
38
  async start() {
37
39
  if (this.status === 'running')
@@ -95,7 +97,7 @@ export class TeamAgentProcess {
95
97
  return { ok: false };
96
98
  }
97
99
  try {
98
- this.subprocess.writeStdin(`${message}\n`);
100
+ this.subprocess.writeStdin(this.stdinFramer !== undefined ? this.stdinFramer(message) : `${message}\n`);
99
101
  return { ok: true };
100
102
  }
101
103
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-ai-runner",
3
- "version": "0.4.68",
3
+ "version": "0.4.69",
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.68",
51
- "@gobing-ai/ts-runtime": "^0.4.68"
50
+ "@gobing-ai/ts-infra": "^0.4.69",
51
+ "@gobing-ai/ts-runtime": "^0.4.69"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/bun": "1.3.14",
55
- "@gobing-ai/ts-db": "^0.4.68"
55
+ "@gobing-ai/ts-db": "^0.4.69"
56
56
  },
57
57
  "publishConfig": {
58
58
  "access": "public"
@@ -66,8 +66,30 @@ export interface PromptOptions {
66
66
  taskId?: string;
67
67
  /** Peer agents included in the identity preamble. */
68
68
  peers?: Array<{ id: string; type: string; purpose?: string }>;
69
+ /**
70
+ * Request the agent's persistent-stdin dispatch: argv starts a long-lived
71
+ * process that reads framed prompts from stdin (one per line) instead of
72
+ * consuming a one-shot `-p <input>` and exiting. Only shims exposing a
73
+ * `persistentStdinProtocol` accept this — callers must consult
74
+ * {@link getAgentSessionCapability}.`supportsPersistentStdin` first. In
75
+ * persistent mode the shim omits `input` from argv entirely; the caller
76
+ * writes the identity preamble / prompts via the shim's `frame` (see
77
+ * `TeamAgentProcess` `stdinFramer`). rpc/stream-json modes supersede
78
+ * `mode` (the structured-output flag rides inside the protocol).
79
+ */
80
+ persistentStdin?: boolean;
69
81
  }
70
82
 
83
+ /** Frames one user prompt as a single stdin line for a persistent-stdin dispatch. */
84
+ export type StdinFrame = (input: string) => string;
85
+
86
+ /** pi-family rpc dialect (pi docs/rpc.md): one JSON object per line. */
87
+ const piRpcFrame: StdinFrame = (input) => `${JSON.stringify({ type: 'prompt', message: input })}\n`;
88
+
89
+ /** claude stream-json input dialect: one user-message JSONL envelope per line. */
90
+ const claudeStreamJsonFrame: StdinFrame = (input) =>
91
+ `${JSON.stringify({ type: 'user', message: { role: 'user', content: [{ type: 'text', text: input }] } })}\n`;
92
+
71
93
  /** Deprecation metadata attached to a retired or superseded agent shim. */
72
94
  export interface AgentDeprecation {
73
95
  /** Release date (ISO) when the id was marked deprecated. */
@@ -96,6 +118,13 @@ export interface AgentShim {
96
118
  getPromptCommand(options: PromptOptions): ShimCommand;
97
119
  /** Build an auth-status command, or null when unsupported. */
98
120
  getAuthCommand(): ShimCommand | null;
121
+ /**
122
+ * Persistent-stdin framing contract; present iff the shim wires a
123
+ * stdin-listener dispatch (mirrors `supportsPersistentStdin`). Callers
124
+ * frame every prompt (including the identity preamble) through it before
125
+ * writing to the process stdin.
126
+ */
127
+ readonly persistentStdinProtocol?: { readonly frame: StdinFrame };
99
128
  }
100
129
 
101
130
  const claudeShim: AgentShim = {
@@ -107,7 +136,22 @@ const claudeShim: AgentShim = {
107
136
  getPromptCommand: (options) => {
108
137
  // Headless dispatch cannot answer edit approval prompts. Keep the grant
109
138
  // scoped to file edits; shell and broader tools remain gated.
110
- const args = ['-p', options.input ?? '', '--permission-mode', 'acceptEdits', '--allowedTools', 'Write', 'Edit'];
139
+ // Persistent stdin rides the print path: `--input-format` only works
140
+ // with --print, and stream-json input keeps the process alive reading
141
+ // JSONL envelopes until stdin closes (verified --help, CLI 2.1.274).
142
+ const persistent = options.persistentStdin === true;
143
+ const args = persistent
144
+ ? [
145
+ '-p',
146
+ '--input-format',
147
+ 'stream-json',
148
+ '--permission-mode',
149
+ 'acceptEdits',
150
+ '--allowedTools',
151
+ 'Write',
152
+ 'Edit',
153
+ ]
154
+ : ['-p', options.input ?? '', '--permission-mode', 'acceptEdits', '--allowedTools', 'Write', 'Edit'];
111
155
  const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
112
156
  if (hasSession) {
113
157
  // Session/pin path — never emit --continue. Claude has no session-dir
@@ -117,10 +161,13 @@ const claudeShim: AgentShim = {
117
161
  args.push('--continue');
118
162
  }
119
163
  if (options.model !== undefined) args.push('--model', options.model);
120
- args.push('--output-format', options.mode ?? 'text');
164
+ // stream-json output is part of the persistent protocol (the framing
165
+ // assumes JSONL stdout); one-shot keeps the caller-requested mode.
166
+ args.push('--output-format', persistent ? 'stream-json' : (options.mode ?? 'text'));
121
167
  return { command: 'claude', args };
122
168
  },
123
169
  getAuthCommand: () => ({ command: 'claude', args: ['auth', 'status'] }),
170
+ persistentStdinProtocol: { frame: claudeStreamJsonFrame },
124
171
  };
125
172
 
126
173
  const codexShim: AgentShim = {
@@ -178,17 +225,22 @@ const piShim: AgentShim = {
178
225
  getPromptCommand: (options) => {
179
226
  const args: string[] = [];
180
227
  const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
228
+ const persistent = options.persistentStdin === true;
181
229
  // Session/pin path: durable — never ephemeral --no-session, never global -c.
182
- if (!hasSession && options.continue !== true) args.push('--no-session');
183
- args.push('-p', options.input ?? '');
184
- if (!hasSession && options.continue === true) args.push('-c');
230
+ if (!hasSession && !persistent && options.continue !== true) args.push('--no-session');
231
+ // Persistent stdin: no -p positional (prompts arrive framed over stdin);
232
+ // rpc mode is the long-lived listener (verified headless probe + pi
233
+ // docs/rpc.md). It supersedes any text/json output mode.
234
+ if (!persistent) args.push('-p', options.input ?? '');
235
+ if (!hasSession && !persistent && options.continue === true) args.push('-c');
185
236
  if (options.sessionDir !== undefined) args.push('--session-dir', options.sessionDir);
186
237
  if (options.sessionId !== undefined) args.push('-r', options.sessionId);
187
238
  if (options.model !== undefined) args.push('--model', options.model);
188
- args.push('--mode', options.mode ?? 'text');
239
+ args.push('--mode', persistent ? 'rpc' : (options.mode ?? 'text'));
189
240
  return { command: 'pi', args };
190
241
  },
191
242
  getAuthCommand: () => ({ command: 'pi', args: ['--list-models'] }),
243
+ persistentStdinProtocol: { frame: piRpcFrame },
192
244
  };
193
245
 
194
246
  const opencodeShim: AgentShim = {
@@ -292,17 +344,21 @@ const ompShim: AgentShim = {
292
344
  getPromptCommand: (options) => {
293
345
  const args: string[] = [];
294
346
  const hasSession = options.sessionId !== undefined || options.sessionDir !== undefined;
347
+ const persistent = options.persistentStdin === true;
295
348
  // Session/pin path: durable — never ephemeral --no-session, never global -c.
296
- if (!hasSession && options.continue !== true) args.push('--no-session');
297
- args.push('-p', options.input ?? '');
298
- if (!hasSession && options.continue === true) args.push('-c');
349
+ if (!hasSession && !persistent && options.continue !== true) args.push('--no-session');
350
+ // Persistent stdin mirrors pi (omp is a pi fork; same rpc dialect) —
351
+ // verified `--mode rpc` in `omp --help` at the pi-compatible surface.
352
+ if (!persistent) args.push('-p', options.input ?? '');
353
+ if (!hasSession && !persistent && options.continue === true) args.push('-c');
299
354
  if (options.sessionDir !== undefined) args.push('--session-dir', options.sessionDir);
300
355
  if (options.sessionId !== undefined) args.push('-r', options.sessionId);
301
356
  if (options.model !== undefined) args.push('--model', options.model);
302
- args.push('--mode', options.mode ?? 'text');
357
+ args.push('--mode', persistent ? 'rpc' : (options.mode ?? 'text'));
303
358
  return { command: 'omp', args };
304
359
  },
305
360
  getAuthCommand: () => ({ command: 'omp', args: ['--list-models'] }),
361
+ persistentStdinProtocol: { frame: piRpcFrame },
306
362
  };
307
363
 
308
364
  /**
@@ -429,9 +485,10 @@ const AGENT_SESSION_CAPABILITY: Readonly<Record<AgentName, AgentSessionCapabilit
429
485
  supportsPersistentStdin: true,
430
486
  supportsStructuredOutput: true,
431
487
  verifiedAgainst: '2.1.274',
432
- // Verified `--input-format stream-json` (realtime streaming input) at CLI
433
- // 2.1.274, but the shim has no persistent-stdin dispatch mode yet.
434
- note: 'no session-dir flag — sessionDir is ignored (best-effort isolate); persistent stdin (--input-format stream-json) is not yet shim-wired',
488
+ // Persistent stdin is shim-wired: `-p --input-format stream-json --output-format
489
+ // stream-json` keeps the process alive reading JSONL envelopes (verified --help;
490
+ // --input-format only works with --print).
491
+ note: 'no session-dir flag — sessionDir is ignored (best-effort isolate)',
435
492
  },
436
493
  codex: {
437
494
  supportsResumeById: true,
@@ -2,6 +2,7 @@ import { Buffer } from 'node:buffer';
2
2
  import { type EventBus, getLogger, type Logger } from '@gobing-ai/ts-infra';
3
3
  import { nodeBunFactory, type PipeProcess, type ProcessExecutor } from '@gobing-ai/ts-runtime';
4
4
  import type { AgentSpec } from './agent-spec';
5
+ import type { StdinFrame } from './agents/shims';
5
6
  import type { AgentEvents } from './events';
6
7
  import {
7
8
  buildQuotaObservation,
@@ -23,6 +24,11 @@ export interface AgentProcessOptions {
23
24
  events?: EventBus<AgentEvents>;
24
25
  /** Optional exact quota attribution; absent fields stay absent on observations — never inferred. */
25
26
  quotaContext?: QuotaAttribution;
27
+ /**
28
+ * Frames each sent prompt for the agent's persistent-stdin dialect (from
29
+ * the shim's `persistentStdinProtocol.frame`); absent = raw text + newline.
30
+ */
31
+ stdinFramer?: StdinFrame;
26
32
  }
27
33
 
28
34
  type ProcessStatus = 'running' | 'stopped' | 'errored';
@@ -47,6 +53,7 @@ export class TeamAgentProcess {
47
53
  private readonly quotaProducer: QuotaObservationProducer;
48
54
  private readonly spec: AgentSpec;
49
55
  private readonly quotaContext: QuotaAttribution | undefined;
56
+ private readonly stdinFramer: StdinFrame | undefined;
50
57
 
51
58
  constructor(options: AgentProcessOptions) {
52
59
  this.agentId = options.spec.id;
@@ -58,6 +65,7 @@ export class TeamAgentProcess {
58
65
  this.quotaProducer = new QuotaObservationProducer(options.events);
59
66
  this.spec = options.spec;
60
67
  this.quotaContext = options.quotaContext;
68
+ this.stdinFramer = options.stdinFramer;
61
69
  }
62
70
 
63
71
  async start(): Promise<void> {
@@ -117,7 +125,7 @@ export class TeamAgentProcess {
117
125
  return { ok: false };
118
126
  }
119
127
  try {
120
- this.subprocess.writeStdin(`${message}\n`);
128
+ this.subprocess.writeStdin(this.stdinFramer !== undefined ? this.stdinFramer(message) : `${message}\n`);
121
129
  return { ok: true };
122
130
  } catch (error) {
123
131
  this.warn('stdin write failed', 'send.writeStdin', error);