@freibergergarcia/phone-a-friend 2.7.2 → 2.8.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "phone-a-friend",
3
3
  "description": "CLI relay that lets AI coding agents collaborate by sending prompts and repository context to backend agents.",
4
- "version": "2.7.2",
4
+ "version": "2.8.0",
5
5
  "author": {
6
6
  "name": "Bruno Freiberger"
7
7
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phone-a-friend",
3
- "version": "2.7.2",
3
+ "version": "2.8.0",
4
4
  "description": "CLI relay that lets AI coding agents collaborate by sending prompts and repository context to backend agents.",
5
5
  "author": {
6
6
  "name": "Bruno Freiberger"
package/README.md CHANGED
@@ -194,7 +194,7 @@ phone-a-friend --to codex --prompt "Review the auth module" --session auth-revie
194
194
  phone-a-friend --to codex --prompt "Now fix those issues" --session auth-review
195
195
  ```
196
196
 
197
- Sessions work reliably with Claude, Codex, and OpenCode. Ollama replays history (may hit token limits on long conversations). Gemini sessions are currently unsupported.
197
+ Sessions work reliably with Claude, Codex, Gemini, and OpenCode. Ollama replays history (may hit token limits on long conversations). (Gemini resumes natively via `--session-id`/`--resume`; resume depends on Gemini's session retention.)
198
198
 
199
199
  ### Job tracking
200
200
 
@@ -186,8 +186,8 @@ PAF_CONTEXT_EOF
186
186
 
187
187
  "$RELAY_BIN" --to codex --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" --context-file "$CONTEXT_FILE" $PAF_NO_DIFF [--fast] [--session <id>]
188
188
  # For gemini, omit --model by default (let auto-routing pick); see "Gemini model selection" below.
189
- # Do NOT pass --session to gemini it will error (see "Session continuity" below):
190
- "$RELAY_BIN" --to gemini --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" --context-file "$CONTEXT_FILE" $PAF_NO_DIFF [--fast]
189
+ # Gemini supports --session via native resume (see "Session continuity" below):
190
+ "$RELAY_BIN" --to gemini --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" --context-file "$CONTEXT_FILE" $PAF_NO_DIFF [--fast] [--session <id>]
191
191
  ```
192
192
 
193
193
  Use delimiter names that do not appear in the payload. The quoted heredoc
@@ -264,14 +264,10 @@ Benefits: the backend keeps full conversation history, so follow-up prompts
264
264
  can be shorter (no need to re-send context from previous turns).
265
265
 
266
266
  **Backend-specific behavior:**
267
- - **Codex, Claude, OpenCode**: native session resume. Follow-up prompts
268
- can send deltas only.
267
+ - **Codex, Claude, Gemini, OpenCode**: native session resume. Follow-up
268
+ prompts can send deltas only.
269
269
  - **Ollama**: replays full history each call. Sessions work but prompt
270
270
  size grows with each turn. Keep follow-ups concise.
271
- - **Gemini**: `--session` is **not supported**. PaF rejects it with a
272
- RelayError (`--session is not supported by the gemini backend ...`).
273
- Each Gemini relay call must be self-contained. Do not pass `--session`
274
- with `--to gemini`.
275
271
 
276
272
  On the FIRST relay under a new session label, PaF prints an informational
277
273
  stderr line: `[phone-a-friend] Session label "..." not found in store.
@@ -320,9 +320,8 @@ command:
320
320
 
321
321
  **Generate session IDs for every backend that supports session resume.**
322
322
  PaF declares a resume strategy per backend (`native-session` for
323
- codex, claude, opencode; `transcript-replay` for ollama; `unsupported`
324
- for gemini). Generate a session ID for any backend whose strategy is
325
- NOT `unsupported`:
323
+ codex, claude, gemini, opencode; `transcript-replay` for ollama).
324
+ Generate a session ID for every backend:
326
325
 
327
326
  | Backend | resumeStrategy | Generate SESSION_ID? |
328
327
  |---|---|---|
@@ -330,11 +329,11 @@ command:
330
329
  | claude | native-session | yes |
331
330
  | opencode | native-session | yes |
332
331
  | ollama | transcript-replay | yes |
333
- | gemini | unsupported | NO, omit `--session` entirely |
332
+ | gemini | native-session | YES, generate a SESSION_ID |
334
333
 
335
- For `--backend both` (codex + gemini), generate a SESSION_ID for codex
336
- only. For `--backend all`, generate SESSION_IDs for codex, claude,
337
- opencode, ollama (every backend that runs), but never gemini.
334
+ For `--backend both` (codex + gemini), generate a SESSION_ID for both
335
+ codex and gemini. For `--backend all`, generate SESSION_IDs for codex,
336
+ claude, opencode, ollama, and gemini (every backend that runs).
338
337
 
339
338
  ### Algorithm
340
339
 
@@ -404,9 +403,7 @@ command:
404
403
  asked for a diff/branch/staged review.
405
404
 
406
405
  Include `--session <SESSION_ID>` for every session-capable backend
407
- (`codex`, `claude`, `opencode`, `ollama`). Omit `--session` only for
408
- `gemini` because PaF rejects it (Gemini's resume strategy is
409
- `unsupported`).
406
+ (`codex`, `claude`, `gemini`, `opencode`, `ollama`).
410
407
 
411
408
  On the FIRST relay under a new session label, PaF prints an
412
409
  informational stderr line: `[phone-a-friend] Session label "..." not
@@ -605,15 +602,12 @@ PAF_TEAM_CONTEXT_EOF
605
602
 
606
603
  Always include `--fast` (relay prompts are self-contained). For
607
604
  `--to claude`, `--fast` has no effect. Include `--session` for every
608
- session-capable backend: `codex`, `claude`, `opencode`, `ollama`. Pass
609
- the backend-specific ID from `SESSION_IDS`. For `gemini`, omit
610
- `--session` entirely; PaF rejects `--session` against Gemini (resume
611
- strategy declared `unsupported`).
605
+ session-capable backend: `codex`, `claude`, `gemini`, `opencode`,
606
+ `ollama`. Pass the backend-specific ID from `SESSION_IDS`.
612
607
 
613
608
  When `--session` is used, the session lets the backend remember
614
609
  previous rounds, so follow-up prompts can focus on feedback deltas
615
- rather than re-sending full context. For Gemini (no session), each
616
- round is stateless — see "Per-round relay rules" below.
610
+ rather than re-sending full context.
617
611
 
618
612
  **Direct mode** (`RELAY_MODE = direct`):
619
613
  ```bash
@@ -774,11 +768,6 @@ The backend already has them in its session history.
774
768
  call (prompt size grows per turn). Sessions work but follow-up prompts
775
769
  must stay concise to avoid hitting size limits.
776
770
 
777
- **Exception: Gemini (no `--session`)**: Gemini runs stateless every round.
778
- Each Gemini relay call must include enough context for the backend to
779
- answer independently — include a brief task recap and the latest output
780
- alongside the feedback in every round.
781
-
782
771
  **Per-round relay rules (direct mode, no session)**:
783
772
  - Each relay call sends ONLY:
784
773
  - The original TASK_DESCRIPTION
package/dist/index.js CHANGED
@@ -77828,15 +77828,16 @@ var GeminiBackend = class {
77828
77828
  "workspace-write",
77829
77829
  "danger-full-access"
77830
77830
  ]);
77831
- // Session resume is declared 'unsupported' rather than 'transcript-replay':
77832
- // run() never reads opts.sessionHistory, and the --resume code path below
77833
- // depends on a session ID that the upstream extractor cannot reliably
77834
- // produce (see extractGeminiSessionId). Until the Gemini CLI's session
77835
- // surface is verified, --session against this backend is rejected at the
77836
- // relay layer instead of silently no-opping.
77831
+ // Session resume mirrors Claude's native-session model: PaF generates the
77832
+ // session UUID client-side (requiresClientSessionId), pins it on the first
77833
+ // call with `--session-id <uuid>`, and resumes later calls with
77834
+ // `--resume <uuid>`. Because the ID is client-generated and deterministic,
77835
+ // PaF never relies on extracting an ID from Gemini's output and never uses
77836
+ // `--resume latest`. History is not replayed (server-side session state),
77837
+ // so opts.sessionHistory is intentionally unused.
77837
77838
  capabilities = {
77838
- resumeStrategy: "unsupported",
77839
- requiresClientSessionId: false
77839
+ resumeStrategy: "native-session",
77840
+ requiresClientSessionId: true
77840
77841
  };
77841
77842
  async run(opts) {
77842
77843
  if (!isInPath("gemini")) {
@@ -77905,22 +77906,17 @@ var GeminiBackend = class {
77905
77906
  }
77906
77907
  }
77907
77908
  async runOnce(opts, model) {
77908
- const args = [];
77909
77909
  const useJsonOutput = Boolean(opts.schema);
77910
77910
  const prompt = opts.schema ? injectSchemaPrompt(opts.prompt, opts.schema) : opts.prompt;
77911
- if (opts.sandbox !== "danger-full-access") {
77912
- args.push("--sandbox");
77913
- }
77914
- args.push("--yolo");
77915
- args.push("--include-directories", opts.repoPath);
77916
- args.push("--output-format", useJsonOutput ? "json" : "text");
77917
- if (opts.resumeSession && opts.sessionId) {
77918
- args.push("--resume", opts.sessionId);
77919
- }
77920
- if (model) {
77921
- args.push("-m", model);
77922
- }
77923
- args.push("--prompt", prompt);
77911
+ const args = buildGeminiArgs({
77912
+ prompt,
77913
+ repoPath: opts.repoPath,
77914
+ sandbox: opts.sandbox,
77915
+ model,
77916
+ useJsonOutput,
77917
+ sessionId: opts.sessionId ?? null,
77918
+ resumeSession: Boolean(opts.resumeSession)
77919
+ });
77924
77920
  try {
77925
77921
  const result = await spawnCli("gemini", args, {
77926
77922
  timeoutMs: opts.timeoutSeconds * 1e3,
@@ -77948,10 +77944,42 @@ var GeminiBackend = class {
77948
77944
  }
77949
77945
  throw new GeminiBackendError("Gemini reached turn limit, response may be incomplete");
77950
77946
  }
77947
+ if (err instanceof SpawnCliError && opts.sessionId && isUnknownSessionFlagError(err.stderr)) {
77948
+ const flag = opts.resumeSession ? "--resume" : "--session-id";
77949
+ throw new GeminiBackendError(
77950
+ `The installed Gemini CLI does not support the \`${flag}\` flag required for --session resume. Upgrade it (\`${INSTALL_HINTS.gemini}\`), or drop --session to run a one-shot relay.`
77951
+ );
77952
+ }
77951
77953
  throw err;
77952
77954
  }
77953
77955
  }
77954
77956
  };
77957
+ function buildGeminiArgs(opts) {
77958
+ const args = [];
77959
+ if (opts.sandbox !== "danger-full-access") {
77960
+ args.push("--sandbox");
77961
+ }
77962
+ args.push("--yolo");
77963
+ args.push("--include-directories", opts.repoPath);
77964
+ args.push("--output-format", opts.useJsonOutput ? "json" : "text");
77965
+ if (opts.sessionId) {
77966
+ if (opts.resumeSession) {
77967
+ args.push("--resume", opts.sessionId);
77968
+ } else {
77969
+ args.push("--session-id", opts.sessionId);
77970
+ }
77971
+ }
77972
+ if (opts.model) {
77973
+ args.push("-m", opts.model);
77974
+ }
77975
+ args.push("--prompt", opts.prompt);
77976
+ return args;
77977
+ }
77978
+ function isUnknownSessionFlagError(stderr) {
77979
+ const text = stderr.toLowerCase();
77980
+ if (!/unknown argument|unknown option|unrecognized/.test(text)) return false;
77981
+ return text.includes("session-id") || text.includes("resume");
77982
+ }
77955
77983
  function formatDeadModelError(model, entry, cachePath) {
77956
77984
  return `Model \`${model}\` returned 404 from Gemini (ModelNotFoundError). Cached as unavailable until ${entry.expiresAt} at ${cachePath}. Run without \`--model\` to use Gemini's auto-routing, set \`PHONE_A_FRIEND_GEMINI_DEAD_CACHE=false\` to bypass the cache, or delete the cache file to clear it.`;
77957
77985
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freibergergarcia/phone-a-friend",
3
- "version": "2.7.2",
3
+ "version": "2.8.0",
4
4
  "description": "CLI relay that lets AI coding agents collaborate",
5
5
  "keywords": [
6
6
  "ai-agent",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phone-a-friend",
3
- "version": "2.7.2",
3
+ "version": "2.8.0",
4
4
  "description": "CLI relay that lets AI coding agents collaborate by sending prompts and repository context to backend agents.",
5
5
  "author": {
6
6
  "name": "Bruno Freiberger"
@@ -260,8 +260,8 @@ PAF_CONTEXT_EOF
260
260
 
261
261
  "$RELAY_BIN" --to codex --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" --context-file "$CONTEXT_FILE" $PAF_NO_DIFF [--fast] [--session <id>]
262
262
  # For gemini, omit --model by default (let auto-routing pick); see "Gemini model selection" below.
263
- # Do NOT pass --session to gemini it will error (see "Session continuity" below):
264
- "$RELAY_BIN" --to gemini --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" --context-file "$CONTEXT_FILE" $PAF_NO_DIFF [--fast]
263
+ # Gemini supports --session via native resume (see "Session continuity" below):
264
+ "$RELAY_BIN" --to gemini --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" --context-file "$CONTEXT_FILE" $PAF_NO_DIFF [--fast] [--session <id>]
265
265
  ```
266
266
 
267
267
  Use delimiter names that do not appear in the payload. The quoted heredoc
@@ -389,10 +389,10 @@ can be shorter (no need to re-send context from previous turns).
389
389
  can send deltas only.
390
390
  - **Ollama**: replays full history each call. Sessions work but prompt
391
391
  size grows with each turn. Keep follow-ups concise.
392
- - **Gemini**: `--session` is **not supported**. PaF rejects it with a
393
- RelayError (`--session is not supported by the gemini backend ...`).
394
- Each Gemini relay call must be self-contained. Do not pass `--session`
395
- with `--to gemini`.
392
+ - **Gemini**: native session resume (same as Codex/Claude/OpenCode).
393
+ PaF generates the session UUID client-side, pins it with `--session-id`
394
+ on the first call, and resumes with `--resume` later. Follow-up prompts
395
+ can send deltas only.
396
396
 
397
397
  On the FIRST relay under a new session label, PaF prints an informational
398
398
  stderr line: `[phone-a-friend] Session label "..." not found in store.
@@ -65,18 +65,14 @@ For each backend in the comma-separated list, the session label is `${TEAM_ID}-<
65
65
  | codex | native-session | yes (never used here; recursion guard) |
66
66
  | opencode | native-session | yes |
67
67
  | ollama | transcript-replay | yes |
68
- | gemini | unsupported | **no, omit the flag entirely** |
68
+ | gemini | native-session | yes |
69
69
 
70
70
  Build the relay flag accordingly per backend:
71
71
 
72
72
  ```bash
73
73
  session_flag_for() {
74
74
  local b="$1"
75
- if [ "$b" = "gemini" ]; then
76
- echo ""
77
- else
78
- echo "--session ${TEAM_ID}-${b}"
79
- fi
75
+ echo "--session ${TEAM_ID}-${b}"
80
76
  }
81
77
  ```
82
78
 
@@ -260,8 +260,8 @@ PAF_CONTEXT_EOF
260
260
 
261
261
  "$RELAY_BIN" --to codex --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" --context-file "$CONTEXT_FILE" $PAF_NO_DIFF [--fast] [--session <id>]
262
262
  # For gemini, omit --model by default (let auto-routing pick); see "Gemini model selection" below.
263
- # Do NOT pass --session to gemini it will error (see "Session continuity" below):
264
- "$RELAY_BIN" --to gemini --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" --context-file "$CONTEXT_FILE" $PAF_NO_DIFF [--fast]
263
+ # Gemini supports --session via native resume (see "Session continuity" below):
264
+ "$RELAY_BIN" --to gemini --repo "$PWD" --prompt "$(cat "$PROMPT_FILE")" --context-file "$CONTEXT_FILE" $PAF_NO_DIFF [--fast] [--session <id>]
265
265
  ```
266
266
 
267
267
  Use delimiter names that do not appear in the payload. The quoted heredoc
@@ -389,10 +389,10 @@ can be shorter (no need to re-send context from previous turns).
389
389
  can send deltas only.
390
390
  - **Ollama**: replays full history each call. Sessions work but prompt
391
391
  size grows with each turn. Keep follow-ups concise.
392
- - **Gemini**: `--session` is **not supported**. PaF rejects it with a
393
- RelayError (`--session is not supported by the gemini backend ...`).
394
- Each Gemini relay call must be self-contained. Do not pass `--session`
395
- with `--to gemini`.
392
+ - **Gemini**: native session resume (same as Codex/Claude/OpenCode).
393
+ PaF generates the session UUID client-side, pins it with `--session-id`
394
+ on the first call, and resumes with `--resume` later. Follow-up prompts
395
+ can send deltas only.
396
396
 
397
397
  On the FIRST relay under a new session label, PaF prints an informational
398
398
  stderr line: `[phone-a-friend] Session label "..." not found in store.
@@ -65,18 +65,14 @@ For each backend in the comma-separated list, the session label is `${TEAM_ID}-<
65
65
  | codex | native-session | yes (never used here; recursion guard) |
66
66
  | opencode | native-session | yes |
67
67
  | ollama | transcript-replay | yes |
68
- | gemini | unsupported | **no, omit the flag entirely** |
68
+ | gemini | native-session | yes |
69
69
 
70
70
  Build the relay flag accordingly per backend:
71
71
 
72
72
  ```bash
73
73
  session_flag_for() {
74
74
  local b="$1"
75
- if [ "$b" = "gemini" ]; then
76
- echo ""
77
- else
78
- echo "--session ${TEAM_ID}-${b}"
79
- fi
75
+ echo "--session ${TEAM_ID}-${b}"
80
76
  }
81
77
  ```
82
78