@freibergergarcia/phone-a-friend 2.7.1 → 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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/commands/phone-a-friend.md +4 -8
- package/commands/phone-a-team.md +10 -21
- package/dist/index.js +119 -48
- package/package.json +1 -1
- package/plugins/phone-a-friend/.codex-plugin/plugin.json +1 -1
- package/plugins/phone-a-friend/skills/phone-a-friend/SKILL.md +6 -6
- package/plugins/phone-a-friend/skills/phone-a-team/SKILL.md +2 -6
- package/skills/phone-a-friend/SKILL.md +6 -6
- package/skills/phone-a-team/.codex/SKILL.md +2 -6
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
|
|
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
|
-
#
|
|
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
|
|
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.
|
package/commands/phone-a-team.md
CHANGED
|
@@ -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
|
|
324
|
-
|
|
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 |
|
|
332
|
+
| gemini | native-session | YES, generate a SESSION_ID |
|
|
334
333
|
|
|
335
|
-
For `--backend both` (codex + gemini), generate a SESSION_ID for
|
|
336
|
-
|
|
337
|
-
opencode, ollama (every backend that runs)
|
|
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`).
|
|
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`, `
|
|
609
|
-
the backend-specific ID from `SESSION_IDS`.
|
|
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.
|
|
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
|
@@ -6979,6 +6979,9 @@ function looksLikeCodexAlready(output) {
|
|
|
6979
6979
|
"already registered"
|
|
6980
6980
|
].some((token) => text.includes(token));
|
|
6981
6981
|
}
|
|
6982
|
+
function codexMarketplaceSource(resolvedRepo) {
|
|
6983
|
+
return resolvedRepo.includes("@") ? GITHUB_REPO : resolvedRepo;
|
|
6984
|
+
}
|
|
6982
6985
|
function syncCodexPluginRegistration(source) {
|
|
6983
6986
|
const lines = [];
|
|
6984
6987
|
try {
|
|
@@ -7248,7 +7251,7 @@ function installHosts(opts) {
|
|
|
7248
7251
|
}
|
|
7249
7252
|
}
|
|
7250
7253
|
if (shouldInstallCodex && syncCodexCli) {
|
|
7251
|
-
lines.push(...syncCodexPluginRegistration(resolvedRepo));
|
|
7254
|
+
lines.push(...syncCodexPluginRegistration(codexMarketplaceSource(resolvedRepo)));
|
|
7252
7255
|
}
|
|
7253
7256
|
return lines;
|
|
7254
7257
|
}
|
|
@@ -77825,15 +77828,16 @@ var GeminiBackend = class {
|
|
|
77825
77828
|
"workspace-write",
|
|
77826
77829
|
"danger-full-access"
|
|
77827
77830
|
]);
|
|
77828
|
-
// Session resume
|
|
77829
|
-
//
|
|
77830
|
-
//
|
|
77831
|
-
//
|
|
77832
|
-
//
|
|
77833
|
-
//
|
|
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.
|
|
77834
77838
|
capabilities = {
|
|
77835
|
-
resumeStrategy: "
|
|
77836
|
-
requiresClientSessionId:
|
|
77839
|
+
resumeStrategy: "native-session",
|
|
77840
|
+
requiresClientSessionId: true
|
|
77837
77841
|
};
|
|
77838
77842
|
async run(opts) {
|
|
77839
77843
|
if (!isInPath("gemini")) {
|
|
@@ -77902,22 +77906,17 @@ var GeminiBackend = class {
|
|
|
77902
77906
|
}
|
|
77903
77907
|
}
|
|
77904
77908
|
async runOnce(opts, model) {
|
|
77905
|
-
const args = [];
|
|
77906
77909
|
const useJsonOutput = Boolean(opts.schema);
|
|
77907
77910
|
const prompt = opts.schema ? injectSchemaPrompt(opts.prompt, opts.schema) : opts.prompt;
|
|
77908
|
-
|
|
77909
|
-
|
|
77910
|
-
|
|
77911
|
-
|
|
77912
|
-
|
|
77913
|
-
|
|
77914
|
-
|
|
77915
|
-
|
|
77916
|
-
}
|
|
77917
|
-
if (model) {
|
|
77918
|
-
args.push("-m", model);
|
|
77919
|
-
}
|
|
77920
|
-
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
|
+
});
|
|
77921
77920
|
try {
|
|
77922
77921
|
const result = await spawnCli("gemini", args, {
|
|
77923
77922
|
timeoutMs: opts.timeoutSeconds * 1e3,
|
|
@@ -77945,10 +77944,42 @@ var GeminiBackend = class {
|
|
|
77945
77944
|
}
|
|
77946
77945
|
throw new GeminiBackendError("Gemini reached turn limit, response may be incomplete");
|
|
77947
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
|
+
}
|
|
77948
77953
|
throw err;
|
|
77949
77954
|
}
|
|
77950
77955
|
}
|
|
77951
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
|
+
}
|
|
77952
77983
|
function formatDeadModelError(model, entry, cachePath) {
|
|
77953
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.`;
|
|
77954
77985
|
}
|
|
@@ -78071,9 +78102,27 @@ async function* parseNDJSONStream(body, signal) {
|
|
|
78071
78102
|
throw new Error("Stream ended unexpectedly");
|
|
78072
78103
|
}
|
|
78073
78104
|
}
|
|
78105
|
+
function extractOpenCodeErrorMessage(event) {
|
|
78106
|
+
const error3 = event.error;
|
|
78107
|
+
if (error3 && typeof error3 === "object") {
|
|
78108
|
+
const data = error3.data;
|
|
78109
|
+
if (data && typeof data.message === "string" && data.message.trim()) {
|
|
78110
|
+
return data.message;
|
|
78111
|
+
}
|
|
78112
|
+
if (typeof error3.name === "string" && error3.name.trim()) {
|
|
78113
|
+
return error3.name;
|
|
78114
|
+
}
|
|
78115
|
+
try {
|
|
78116
|
+
return JSON.stringify(error3);
|
|
78117
|
+
} catch {
|
|
78118
|
+
}
|
|
78119
|
+
}
|
|
78120
|
+
return "opencode reported an error";
|
|
78121
|
+
}
|
|
78074
78122
|
async function* parseOpenCodeStreamJSON(stdout, opts) {
|
|
78075
78123
|
let buffer = "";
|
|
78076
78124
|
let sessionReported = false;
|
|
78125
|
+
let errorReported = false;
|
|
78077
78126
|
function* processLines(lines) {
|
|
78078
78127
|
for (const line of lines) {
|
|
78079
78128
|
const trimmed = line.trim();
|
|
@@ -78088,6 +78137,10 @@ async function* parseOpenCodeStreamJSON(stdout, opts) {
|
|
|
78088
78137
|
opts.onSessionCreated(parsed.sessionID);
|
|
78089
78138
|
sessionReported = true;
|
|
78090
78139
|
}
|
|
78140
|
+
if (!errorReported && parsed.type === "error" && opts?.onError) {
|
|
78141
|
+
opts.onError(extractOpenCodeErrorMessage(parsed));
|
|
78142
|
+
errorReported = true;
|
|
78143
|
+
}
|
|
78091
78144
|
if (parsed.type === "text") {
|
|
78092
78145
|
const part = parsed.part;
|
|
78093
78146
|
if (part?.text && typeof part.text === "string") {
|
|
@@ -78381,6 +78434,18 @@ function isClaudeAuthError(msg) {
|
|
|
78381
78434
|
const text = msg.toLowerCase();
|
|
78382
78435
|
return text.includes("not logged in") || text.includes("please run /login");
|
|
78383
78436
|
}
|
|
78437
|
+
function extractClaudeSchemaOutput(stdout) {
|
|
78438
|
+
let parsed;
|
|
78439
|
+
try {
|
|
78440
|
+
parsed = JSON.parse(stdout);
|
|
78441
|
+
} catch {
|
|
78442
|
+
return stdout;
|
|
78443
|
+
}
|
|
78444
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && Object.prototype.hasOwnProperty.call(parsed, "structured_output")) {
|
|
78445
|
+
return JSON.stringify(parsed.structured_output);
|
|
78446
|
+
}
|
|
78447
|
+
return stdout;
|
|
78448
|
+
}
|
|
78384
78449
|
var ClaudeBackend = class {
|
|
78385
78450
|
name = "claude";
|
|
78386
78451
|
localFileAccess = true;
|
|
@@ -78466,7 +78531,7 @@ var ClaudeBackend = class {
|
|
|
78466
78531
|
label: "claude"
|
|
78467
78532
|
});
|
|
78468
78533
|
if (result.stdout) {
|
|
78469
|
-
return result.stdout;
|
|
78534
|
+
return opts.schema ? extractClaudeSchemaOutput(result.stdout) : result.stdout;
|
|
78470
78535
|
}
|
|
78471
78536
|
throw new ClaudeBackendError("claude completed without producing output");
|
|
78472
78537
|
} catch (err) {
|
|
@@ -78716,42 +78781,48 @@ var OpenCodeBackend = class {
|
|
|
78716
78781
|
process.on("SIGINT", onSigint);
|
|
78717
78782
|
const stderrChunks = [];
|
|
78718
78783
|
child.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
|
|
78719
|
-
|
|
78720
|
-
|
|
78721
|
-
|
|
78722
|
-
|
|
78723
|
-
|
|
78724
|
-
|
|
78725
|
-
} else if (signal) {
|
|
78726
|
-
reject(new OpenCodeBackendError(
|
|
78727
|
-
`opencode killed by signal ${signal}`
|
|
78728
|
-
));
|
|
78729
|
-
} else if (code !== 0 && code !== null) {
|
|
78730
|
-
const stderr = Buffer.concat(stderrChunks).toString().trim();
|
|
78731
|
-
reject(new OpenCodeBackendError(
|
|
78732
|
-
stderr || `opencode exited with code ${code}`
|
|
78733
|
-
));
|
|
78734
|
-
} else {
|
|
78735
|
-
resolve5();
|
|
78736
|
-
}
|
|
78737
|
-
});
|
|
78738
|
-
});
|
|
78784
|
+
let streamError = null;
|
|
78785
|
+
const closePromise = new Promise(
|
|
78786
|
+
(resolve5) => {
|
|
78787
|
+
child.on("close", (code, signal) => resolve5({ code, signal }));
|
|
78788
|
+
}
|
|
78789
|
+
);
|
|
78739
78790
|
let chunkCount = 0;
|
|
78740
78791
|
try {
|
|
78741
78792
|
for await (const chunk of parseOpenCodeStreamJSON(
|
|
78742
78793
|
child.stdout,
|
|
78743
|
-
{
|
|
78794
|
+
{
|
|
78795
|
+
onSessionCreated: opts.onSessionCreated,
|
|
78796
|
+
onError: (msg) => {
|
|
78797
|
+
if (streamError === null) streamError = msg;
|
|
78798
|
+
}
|
|
78799
|
+
}
|
|
78744
78800
|
)) {
|
|
78745
78801
|
chunkCount++;
|
|
78746
78802
|
yield chunk;
|
|
78747
78803
|
}
|
|
78748
|
-
await closePromise;
|
|
78804
|
+
const { code, signal } = await closePromise;
|
|
78805
|
+
if (timedOut) {
|
|
78806
|
+
throw new OpenCodeBackendError(
|
|
78807
|
+
`opencode timed out after ${opts.timeoutSeconds}s`
|
|
78808
|
+
);
|
|
78809
|
+
}
|
|
78810
|
+
if (signal) {
|
|
78811
|
+
throw new OpenCodeBackendError(`opencode killed by signal ${signal}`);
|
|
78812
|
+
}
|
|
78813
|
+
if (code !== 0 && code !== null) {
|
|
78814
|
+
const stderr = Buffer.concat(stderrChunks).toString().trim();
|
|
78815
|
+
throw new OpenCodeBackendError(
|
|
78816
|
+
stderr || streamError || `opencode exited with code ${code}`
|
|
78817
|
+
);
|
|
78818
|
+
}
|
|
78819
|
+
if (streamError) {
|
|
78820
|
+
throw new OpenCodeBackendError(streamError);
|
|
78821
|
+
}
|
|
78749
78822
|
if (chunkCount === 0) {
|
|
78750
78823
|
throw new OpenCodeBackendError(OPENCODE_NO_OUTPUT_MESSAGE);
|
|
78751
78824
|
}
|
|
78752
78825
|
} catch (err) {
|
|
78753
|
-
closePromise.catch(() => {
|
|
78754
|
-
});
|
|
78755
78826
|
if (err instanceof OpenCodeBackendError) throw err;
|
|
78756
78827
|
const msg = err instanceof Error ? err.message : String(err);
|
|
78757
78828
|
if (timedOut) {
|
package/package.json
CHANGED
|
@@ -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
|
-
#
|
|
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**:
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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 |
|
|
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
|
-
|
|
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
|
-
#
|
|
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**:
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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 |
|
|
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
|
-
|
|
76
|
-
echo ""
|
|
77
|
-
else
|
|
78
|
-
echo "--session ${TEAM_ID}-${b}"
|
|
79
|
-
fi
|
|
75
|
+
echo "--session ${TEAM_ID}-${b}"
|
|
80
76
|
}
|
|
81
77
|
```
|
|
82
78
|
|