@fleetagent/pi-coding-agent 0.0.6 → 0.0.8

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.
Files changed (157) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +3 -3
  3. package/dist/cli/file-processor.d.ts.map +1 -1
  4. package/dist/cli/file-processor.js +2 -3
  5. package/dist/cli/file-processor.js.map +1 -1
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +15 -2
  8. package/dist/config.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +12 -0
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +123 -18
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/export-html/template.js +6 -3
  14. package/dist/core/extensions/runner.d.ts +1 -1
  15. package/dist/core/extensions/runner.d.ts.map +1 -1
  16. package/dist/core/extensions/runner.js +8 -2
  17. package/dist/core/extensions/runner.js.map +1 -1
  18. package/dist/core/extensions/types.d.ts +4 -2
  19. package/dist/core/extensions/types.d.ts.map +1 -1
  20. package/dist/core/extensions/types.js.map +1 -1
  21. package/dist/core/model-registry.d.ts.map +1 -1
  22. package/dist/core/model-registry.js +65 -13
  23. package/dist/core/model-registry.js.map +1 -1
  24. package/dist/core/output-guard.d.ts +1 -0
  25. package/dist/core/output-guard.d.ts.map +1 -1
  26. package/dist/core/output-guard.js +52 -22
  27. package/dist/core/output-guard.js.map +1 -1
  28. package/dist/core/package-manager.d.ts.map +1 -1
  29. package/dist/core/package-manager.js +31 -12
  30. package/dist/core/package-manager.js.map +1 -1
  31. package/dist/core/pi-agent.d.ts.map +1 -1
  32. package/dist/core/pi-agent.js +12 -3
  33. package/dist/core/pi-agent.js.map +1 -1
  34. package/dist/core/resolve-config-value.d.ts +9 -1
  35. package/dist/core/resolve-config-value.d.ts.map +1 -1
  36. package/dist/core/resolve-config-value.js +134 -11
  37. package/dist/core/resolve-config-value.js.map +1 -1
  38. package/dist/core/session/jsonl-helpers.d.ts +2 -1
  39. package/dist/core/session/jsonl-helpers.d.ts.map +1 -1
  40. package/dist/core/session/jsonl-helpers.js +6 -3
  41. package/dist/core/session/jsonl-helpers.js.map +1 -1
  42. package/dist/core/session/local-session-manager.d.ts +1 -0
  43. package/dist/core/session/local-session-manager.d.ts.map +1 -1
  44. package/dist/core/session/local-session-manager.js +12 -4
  45. package/dist/core/session/local-session-manager.js.map +1 -1
  46. package/dist/core/session/session-manager.d.ts +1 -0
  47. package/dist/core/session/session-manager.d.ts.map +1 -1
  48. package/dist/core/session/session-manager.js.map +1 -1
  49. package/dist/core/session/stores/jsonl-session-store.d.ts +2 -1
  50. package/dist/core/session/stores/jsonl-session-store.d.ts.map +1 -1
  51. package/dist/core/session/stores/jsonl-session-store.js +105 -78
  52. package/dist/core/session/stores/jsonl-session-store.js.map +1 -1
  53. package/dist/core/settings-manager.d.ts +2 -0
  54. package/dist/core/settings-manager.d.ts.map +1 -1
  55. package/dist/core/settings-manager.js +14 -9
  56. package/dist/core/settings-manager.js.map +1 -1
  57. package/dist/core/tools/bash.d.ts.map +1 -1
  58. package/dist/core/tools/bash.js +73 -63
  59. package/dist/core/tools/bash.js.map +1 -1
  60. package/dist/core/tools/edit.d.ts.map +1 -1
  61. package/dist/core/tools/edit.js +45 -76
  62. package/dist/core/tools/edit.js.map +1 -1
  63. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  64. package/dist/core/tools/file-mutation-queue.js +27 -12
  65. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  66. package/dist/core/tools/find.d.ts.map +1 -1
  67. package/dist/core/tools/find.js +11 -2
  68. package/dist/core/tools/find.js.map +1 -1
  69. package/dist/core/tools/grep.d.ts.map +1 -1
  70. package/dist/core/tools/grep.js +3 -3
  71. package/dist/core/tools/grep.js.map +1 -1
  72. package/dist/core/tools/ls.d.ts.map +1 -1
  73. package/dist/core/tools/ls.js +13 -4
  74. package/dist/core/tools/ls.js.map +1 -1
  75. package/dist/core/tools/path-utils.d.ts +1 -0
  76. package/dist/core/tools/path-utils.d.ts.map +1 -1
  77. package/dist/core/tools/path-utils.js +37 -0
  78. package/dist/core/tools/path-utils.js.map +1 -1
  79. package/dist/core/tools/read.d.ts.map +1 -1
  80. package/dist/core/tools/read.js +7 -6
  81. package/dist/core/tools/read.js.map +1 -1
  82. package/dist/core/tools/write.d.ts.map +1 -1
  83. package/dist/core/tools/write.js +24 -32
  84. package/dist/core/tools/write.js.map +1 -1
  85. package/dist/main.d.ts.map +1 -1
  86. package/dist/main.js +3 -2
  87. package/dist/main.js.map +1 -1
  88. package/dist/migrations.d.ts.map +1 -1
  89. package/dist/migrations.js +118 -1
  90. package/dist/migrations.js.map +1 -1
  91. package/dist/modes/interactive/components/footer.d.ts +1 -0
  92. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  93. package/dist/modes/interactive/components/footer.js +14 -5
  94. package/dist/modes/interactive/components/footer.js.map +1 -1
  95. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  96. package/dist/modes/interactive/components/user-message.js +1 -1
  97. package/dist/modes/interactive/components/user-message.js.map +1 -1
  98. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  99. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  100. package/dist/modes/interactive/interactive-mode.js +34 -8
  101. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  102. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  103. package/dist/modes/interactive/theme/theme.js +10 -0
  104. package/dist/modes/interactive/theme/theme.js.map +1 -1
  105. package/dist/modes/rpc/rpc-client.d.ts +3 -0
  106. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  107. package/dist/modes/rpc/rpc-client.js +64 -7
  108. package/dist/modes/rpc/rpc-client.js.map +1 -1
  109. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  110. package/dist/modes/rpc/rpc-mode.js +15 -3
  111. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  112. package/dist/utils/clipboard-native.d.ts +3 -1
  113. package/dist/utils/clipboard-native.d.ts.map +1 -1
  114. package/dist/utils/clipboard-native.js +14 -8
  115. package/dist/utils/clipboard-native.js.map +1 -1
  116. package/dist/utils/deprecation.d.ts +4 -0
  117. package/dist/utils/deprecation.d.ts.map +1 -0
  118. package/dist/utils/deprecation.js +13 -0
  119. package/dist/utils/deprecation.js.map +1 -0
  120. package/dist/utils/image-resize-core.d.ts +30 -0
  121. package/dist/utils/image-resize-core.d.ts.map +1 -0
  122. package/dist/utils/image-resize-core.js +124 -0
  123. package/dist/utils/image-resize-core.js.map +1 -0
  124. package/dist/utils/image-resize-worker.d.ts +2 -0
  125. package/dist/utils/image-resize-worker.d.ts.map +1 -0
  126. package/dist/utils/image-resize-worker.js +31 -0
  127. package/dist/utils/image-resize-worker.js.map +1 -0
  128. package/dist/utils/image-resize.d.ts +6 -27
  129. package/dist/utils/image-resize.d.ts.map +1 -1
  130. package/dist/utils/image-resize.js +60 -116
  131. package/dist/utils/image-resize.js.map +1 -1
  132. package/dist/utils/json.d.ts +3 -0
  133. package/dist/utils/json.d.ts.map +1 -0
  134. package/dist/utils/json.js +7 -0
  135. package/dist/utils/json.js.map +1 -0
  136. package/dist/utils/version-check.d.ts.map +1 -1
  137. package/dist/utils/version-check.js +10 -4
  138. package/dist/utils/version-check.js.map +1 -1
  139. package/docs/custom-provider.md +22 -9
  140. package/docs/extensions.md +4 -3
  141. package/docs/models.md +34 -12
  142. package/docs/packages.md +5 -4
  143. package/docs/providers.md +13 -5
  144. package/docs/sdk.md +56 -0
  145. package/docs/settings.md +4 -2
  146. package/docs/terminal-setup.md +6 -0
  147. package/docs/usage.md +3 -3
  148. package/examples/extensions/README.md +1 -0
  149. package/examples/extensions/custom-provider-anthropic/index.ts +1 -1
  150. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  151. package/examples/extensions/custom-provider-gitlab-duo/index.ts +54 -3
  152. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  153. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  154. package/examples/extensions/sandbox/package.json +1 -1
  155. package/examples/extensions/with-deps/package.json +1 -1
  156. package/npm-shrinkwrap.json +13 -12
  157. package/package.json +5 -5
package/docs/models.md CHANGED
@@ -101,7 +101,7 @@ Use `google-generative-ai` with a `baseUrl` to add models from Google AI Studio,
101
101
  "my-google": {
102
102
  "baseUrl": "https://generativelanguage.googleapis.com/v1beta",
103
103
  "api": "google-generative-ai",
104
- "apiKey": "GEMINI_API_KEY",
104
+ "apiKey": "$GEMINI_API_KEY",
105
105
  "models": [
106
106
  {
107
107
  "id": "gemma-4-31b-it",
@@ -143,22 +143,31 @@ Set `api` at provider level (default for all models) or model level (override pe
143
143
 
144
144
  ### Value Resolution
145
145
 
146
- The `apiKey` and `headers` fields support three formats:
146
+ The `apiKey` and `headers` fields support command execution, environment interpolation, and literals:
147
147
 
148
- - **Shell command:** `"!command"` executes and uses stdout
148
+ - **Shell command:** `"!command"` at the start executes the whole value as a command and uses stdout
149
149
  ```json
150
150
  "apiKey": "!security find-generic-password -ws 'anthropic'"
151
151
  "apiKey": "!op read 'op://vault/item/credential'"
152
152
  ```
153
- - **Environment variable:** Uses the value of the named variable
153
+ - **Environment interpolation:** `"$ENV_VAR"` or `"${ENV_VAR}"` uses the value of the named variable. Interpolation works inside larger literals.
154
154
  ```json
155
- "apiKey": "MY_API_KEY"
155
+ "apiKey": "$MY_API_KEY"
156
+ "apiKey": "${KEY_PREFIX}_${KEY_SUFFIX}"
157
+ ```
158
+ `$FOO_BAR` is the variable `FOO_BAR`; use `${FOO}_BAR` when `BAR` is literal text. Missing environment variables make the value unresolved.
159
+ - **Escapes:** `"$$"` emits a literal `"$"`; `"$!"` emits a literal `"!"` without triggering command execution.
160
+ ```json
161
+ "apiKey": "$$literal-dollar-prefix"
162
+ "apiKey": "$!literal-bang-prefix"
156
163
  ```
157
164
  - **Literal value:** Used directly
158
165
  ```json
159
166
  "apiKey": "sk-..."
160
167
  ```
161
168
 
169
+ Legacy uppercase env-var-like values such as `MY_API_KEY` are migrated to `$MY_API_KEY` on startup.
170
+
162
171
  For `models.json`, shell commands are resolved at request time. pi intentionally does not apply built-in TTL, stale reuse, or recovery logic for arbitrary commands. Different commands need different caching and failure strategies, and pi cannot infer the right one.
163
172
 
164
173
  If your command is slow, expensive, rate-limited, or should keep using a previous value on transient failures, wrap it in your own script or command that implements the caching or TTL behavior you want.
@@ -172,10 +181,10 @@ If your command is slow, expensive, rate-limited, or should keep using a previou
172
181
  "providers": {
173
182
  "custom-proxy": {
174
183
  "baseUrl": "https://proxy.example.com/v1",
175
- "apiKey": "MY_API_KEY",
184
+ "apiKey": "$MY_API_KEY",
176
185
  "api": "anthropic-messages",
177
186
  "headers": {
178
- "x-portkey-api-key": "PORTKEY_API_KEY",
187
+ "x-portkey-api-key": "$PORTKEY_API_KEY",
179
188
  "x-secret": "!op read 'op://vault/item/secret'"
180
189
  },
181
190
  "models": [...]
@@ -268,7 +277,7 @@ To merge custom models into a built-in provider, include the `models` array:
268
277
  "providers": {
269
278
  "anthropic": {
270
279
  "baseUrl": "https://my-proxy.example.com/v1",
271
- "apiKey": "ANTHROPIC_API_KEY",
280
+ "apiKey": "$ANTHROPIC_API_KEY",
272
281
  "api": "anthropic-messages",
273
282
  "models": [...]
274
283
  }
@@ -319,16 +328,24 @@ For providers or proxies using `api: "anthropic-messages"`, use `compat.supports
319
328
 
320
329
  By default pi sends per-tool `eager_input_streaming: true`. If a proxy or Anthropic-compatible backend rejects that field, set `supportsEagerToolInputStreaming` to `false`. Pi will omit `tools[].eager_input_streaming` and send the legacy `fine-grained-tool-streaming-2025-05-14` beta header for tool-enabled requests instead.
321
330
 
331
+ Some Anthropic models require adaptive thinking (`thinking.type: "adaptive"` plus `output_config.effort`) instead of the legacy budget-based thinking payload. Built-in models set this automatically. For custom providers or aliases that route to those models, set `forceAdaptiveThinking` to `true`.
332
+
333
+ Some Anthropic-compatible providers emit thinking blocks with empty signatures and still expect them on replay. Set `allowEmptySignature` to `true` only for those providers; real Anthropic rejects empty thinking signatures.
334
+
335
+
322
336
  ```json
323
337
  {
324
338
  "providers": {
325
339
  "anthropic-proxy": {
326
340
  "baseUrl": "https://proxy.example.com",
327
341
  "api": "anthropic-messages",
328
- "apiKey": "ANTHROPIC_PROXY_KEY",
342
+ "apiKey": "$ANTHROPIC_PROXY_KEY",
329
343
  "compat": {
330
344
  "supportsEagerToolInputStreaming": false,
331
- "supportsLongCacheRetention": true
345
+ "supportsLongCacheRetention": true,
346
+ "forceAdaptiveThinking": true,
347
+ "allowEmptySignature": true
348
+
332
349
  },
333
350
  "models": [
334
351
  {
@@ -346,6 +363,11 @@ By default pi sends per-tool `eager_input_streaming: true`. If a proxy or Anthro
346
363
  |-------|-------------|
347
364
  | `supportsEagerToolInputStreaming` | Whether the provider accepts per-tool `eager_input_streaming`. Default: `true`. Set to `false` to omit that field and use the legacy fine-grained tool streaming beta header on tool-enabled requests. |
348
365
  | `supportsLongCacheRetention` | Whether the provider accepts Anthropic long cache retention (`cache_control.ttl: "1h"`) when cache retention is `long`. Default: `true`. |
366
+ | `sendSessionAffinityHeaders` | Whether to send `x-session-affinity` from the session id when caching is enabled. Default: auto-detected for known providers. |
367
+ | `supportsCacheControlOnTools` | Whether the provider accepts Anthropic-style `cache_control` markers on tool definitions. Default: `true`. |
368
+ | `forceAdaptiveThinking` | Whether to send adaptive thinking (`thinking.type: "adaptive"` plus `output_config.effort`) for this model. Built-in adaptive models set this automatically. Default: `false`. |
369
+ | `allowEmptySignature` | Whether to replay empty thinking signatures as `signature: ""` instead of converting thinking to text. Default: `false`. |
370
+
349
371
 
350
372
  ## OpenAI Compatibility
351
373
 
@@ -399,7 +421,7 @@ Example:
399
421
  "providers": {
400
422
  "openrouter": {
401
423
  "baseUrl": "https://openrouter.ai/api/v1",
402
- "apiKey": "OPENROUTER_API_KEY",
424
+ "apiKey": "$OPENROUTER_API_KEY",
403
425
  "api": "openai-completions",
404
426
  "models": [
405
427
  {
@@ -449,7 +471,7 @@ Vercel AI Gateway example:
449
471
  "providers": {
450
472
  "vercel-ai-gateway": {
451
473
  "baseUrl": "https://ai-gateway.vercel.sh/v1",
452
- "apiKey": "AI_GATEWAY_API_KEY",
474
+ "apiKey": "$AI_GATEWAY_API_KEY",
453
475
  "api": "openai-completions",
454
476
  "models": [
455
477
  {
package/docs/packages.md CHANGED
@@ -28,8 +28,8 @@ pi install ./relative/path/to/package
28
28
 
29
29
  pi remove npm:@foo/bar
30
30
  pi list # show installed packages from settings
31
- pi update # update pi and all non-pinned packages
32
- pi update --extensions # update all non-pinned packages only
31
+ pi update # update pi, update packages, and reconcile pinned git refs
32
+ pi update --extensions # update packages and reconcile pinned git refs only
33
33
  pi update --self # update pi only
34
34
  pi update --self --force # reinstall pi even if current
35
35
  pi update npm:@foo/bar # update one package
@@ -85,9 +85,10 @@ ssh://git@github.com/user/repo@v1
85
85
  - HTTPS and SSH URLs are both supported.
86
86
  - SSH URLs use your configured SSH keys automatically (respects `~/.ssh/config`).
87
87
  - For non-interactive runs (for example CI), you can set `GIT_TERMINAL_PROMPT=0` to disable credential prompts and set `GIT_SSH_COMMAND` (for example `ssh -o BatchMode=yes -o ConnectTimeout=5`) to fail fast.
88
- - Refs are pinned tags or commits and skip package updates (`pi update`, `pi update --extensions`). Use `pi install git:host/user/repo@new-ref` to move an existing package to a new pinned ref.
88
+ - Refs are pinned tags or commits. `pi update` and `pi update --extensions` do not move them to newer refs, but they do reconcile an existing clone to the configured ref.
89
+ - Use `pi install git:host/user/repo@new-ref` to update settings and move an existing package to a new pinned ref.
89
90
  - Cloned to `~/.pi/agent/git/<host>/<path>` (global) or `.pi/git/<host>/<path>` (project).
90
- - Runs `npm install` after clone, pull, or pinned ref change if `package.json` exists.
91
+ - When reconciliation changes the checkout, pi resets and cleans the clone, then runs `npm install` if `package.json` exists.
91
92
 
92
93
  **SSH examples:**
93
94
  ```bash
package/docs/providers.md CHANGED
@@ -101,23 +101,31 @@ The file is created with `0600` permissions (user read/write only). Auth file cr
101
101
 
102
102
  ### Key Resolution
103
103
 
104
- The `key` field supports three formats:
104
+ The `key` field supports command execution, environment interpolation, and literals:
105
105
 
106
- - **Shell command:** `"!command"` executes and uses stdout (cached for process lifetime)
106
+ - **Shell command:** `"!command"` at the start executes the whole value as a command and uses stdout (cached for process lifetime)
107
107
  ```json
108
108
  { "type": "api_key", "key": "!security find-generic-password -ws 'anthropic'" }
109
109
  { "type": "api_key", "key": "!op read 'op://vault/item/credential'" }
110
110
  ```
111
- - **Environment variable:** Uses the value of the named variable
111
+ - **Environment interpolation:** `"$ENV_VAR"` or `"${ENV_VAR}"` uses the value of the named variable. Interpolation works inside larger literals.
112
112
  ```json
113
- { "type": "api_key", "key": "MY_ANTHROPIC_KEY" }
113
+ { "type": "api_key", "key": "$MY_ANTHROPIC_KEY" }
114
+ { "type": "api_key", "key": "${KEY_PREFIX}_${KEY_SUFFIX}" }
115
+ ```
116
+ `$FOO_BAR` is the variable `FOO_BAR`; use `${FOO}_BAR` when `BAR` is literal text. Missing environment variables make the value unresolved.
117
+ - **Escapes:** `"$$"` emits a literal `"$"`; `"$!"` emits a literal `"!"` without triggering command execution.
118
+ ```json
119
+ { "type": "api_key", "key": "$$literal-dollar-prefix" }
120
+ { "type": "api_key", "key": "$!literal-bang-prefix" }
114
121
  ```
115
122
  - **Literal value:** Used directly
116
123
  ```json
117
124
  { "type": "api_key", "key": "sk-ant-..." }
125
+ { "type": "api_key", "key": "public" }
118
126
  ```
119
127
 
120
- OAuth credentials are also stored here after `/login` and managed automatically.
128
+ Legacy uppercase env-var-like values such as `MY_API_KEY` are migrated to `$MY_API_KEY` on startup. OAuth credentials are also stored here after `/login` and managed automatically.
121
129
 
122
130
  ## Cloud Providers
123
131
 
package/docs/sdk.md CHANGED
@@ -47,6 +47,52 @@ const pi = await PiAgent.create({
47
47
  const session = await pi.createAgentSession();
48
48
  ```
49
49
 
50
+ The session manages agent lifecycle, message history, model state, compaction, and event streaming.
51
+
52
+ ```typescript
53
+ interface AgentSession {
54
+ // Send a prompt and wait for completion
55
+ prompt(text: string, options?: PromptOptions): Promise<void>;
56
+
57
+ // Queue messages during streaming
58
+ steer(text: string): Promise<void>;
59
+ followUp(text: string): Promise<void>;
60
+
61
+ // Subscribe to events (returns unsubscribe function)
62
+ subscribe(listener: (event: AgentSessionEvent) => void): () => void;
63
+
64
+ // Session info
65
+ sessionFile: string | undefined;
66
+ sessionId: string;
67
+
68
+ // Model control
69
+ setModel(model: Model): Promise<void>;
70
+ setThinkingLevel(level: ThinkingLevel): void;
71
+ cycleModel(): Promise<ModelCycleResult | undefined>;
72
+ cycleThinkingLevel(): ThinkingLevel | undefined;
73
+
74
+ // State access
75
+ agent: Agent;
76
+ model: Model | undefined;
77
+ thinkingLevel: ThinkingLevel;
78
+ messages: AgentMessage[];
79
+ isStreaming: boolean;
80
+
81
+ // In-place tree navigation within the current session file
82
+ navigateTree(targetId: string, options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string }): Promise<{ editorText?: string; cancelled: boolean }>;
83
+
84
+ // Compaction
85
+ compact(customInstructions?: string): Promise<CompactionResult>;
86
+ abortCompaction(): void;
87
+
88
+ // Abort current operation
89
+ abort(): Promise<void>;
90
+
91
+ // Cleanup
92
+ dispose(): void;
93
+ }
94
+ ```
95
+
50
96
  Most one-off session options can be passed directly to `PiAgent.create()`:
51
97
 
52
98
  ```typescript
@@ -73,6 +119,16 @@ Use `session.sessionReference` when you need the backend-neutral active session
73
119
 
74
120
  Session replacement APIs such as new-session, resume, fork, clone, and import live on `PiAgent`, not on `AgentSession`.
75
121
 
122
+ ```typescript
123
+ interface PromptOptions {
124
+ expandPromptTemplates?: boolean;
125
+ images?: ImageContent[];
126
+ streamingBehavior?: "steer" | "followUp";
127
+ source?: InputSource;
128
+ preflightResult?: (success: boolean) => void;
129
+ }
130
+ ```
131
+
76
132
  ### SessionManager and Session
77
133
 
78
134
  `SessionManager` handles lifecycle and discovery: create, open, continue, list, fork, and import. It returns a `Session`.
package/docs/settings.md CHANGED
@@ -50,7 +50,7 @@ Edit directly or use `/settings` for common options.
50
50
 
51
51
  ### Telemetry and update checks
52
52
 
53
- `enableInstallTelemetry` only controls the anonymous install/update ping to `https://pi.dev/api/report-install`. Opting out of telemetry does not disable update checks; Pi can still fetch `https://pi.dev/api/latest-version` to look for the latest version.
53
+ `enableInstallTelemetry` only controls the anonymous install/update ping to `https://pi.dev/api/report-install`. Opting out of telemetry does not disable update checks; Pi can still fetch npm metadata for `@fleetagent/pi-coding-agent` to look for the latest version.
54
54
 
55
55
  Set `PI_SKIP_VERSION_CHECK=1` to disable the Pi version update check. Use `--offline` or `PI_OFFLINE=1` to disable all startup network operations described here, including update checks, package update checks, and install/update telemetry.
56
56
 
@@ -101,11 +101,13 @@ Set `PI_SKIP_VERSION_CHECK=1` to disable the Pi version update check. Use `--off
101
101
  | `retry.maxRetries` | number | `3` | Maximum agent-level retry attempts |
102
102
  | `retry.baseDelayMs` | number | `2000` | Base delay for agent-level exponential backoff (2s, 4s, 8s) |
103
103
  | `retry.provider.timeoutMs` | number | SDK default | Provider/SDK request timeout in milliseconds |
104
- | `retry.provider.maxRetries` | number | SDK default | Provider/SDK retry attempts |
104
+ | `retry.provider.maxRetries` | number | `0` | Provider/SDK retry attempts |
105
105
  | `retry.provider.maxRetryDelayMs` | number | `60000` | Max server-requested delay before failing (60s) |
106
106
 
107
107
  When a provider requests a retry delay longer than `retry.provider.maxRetryDelayMs` (e.g., Google's "quota will reset after 5h"), the request fails immediately with an informative error instead of waiting silently. Set to `0` to disable the cap.
108
108
 
109
+ Keep `retry.provider.maxRetries` at `0` unless provider-level retries are explicitly needed. Setting it above `0` can make SDK/provider retries handle out-of-usage-limit errors before Pi sees them, which may block the agent until the provider quota resets in some circumstances.
110
+
109
111
  ```json
110
112
  {
111
113
  "retry": {
@@ -6,6 +6,12 @@ Pi uses the [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-p
6
6
 
7
7
  Work out of the box.
8
8
 
9
+ ## Apple Terminal
10
+
11
+ Pi enables enhanced key reporting when available. If Terminal.app still sends plain Return for `Shift+Enter`, pi uses a local macOS modifier fallback to treat that Return as `Shift+Enter`.
12
+
13
+ This fallback only works when pi runs on the same Mac as Terminal.app. It cannot detect the local keyboard over remote SSH.
14
+
9
15
  ## Ghostty
10
16
 
11
17
  Add to your Ghostty config (`~/Library/Application Support/com.mitchellh.ghostty/config` on macOS, `~/.config/ghostty/config` on Linux):
package/docs/usage.md CHANGED
@@ -129,8 +129,8 @@ pi [options] [@files...] [messages...]
129
129
  pi install <source> [-l] # Install package, -l for project-local
130
130
  pi remove <source> [-l] # Remove package
131
131
  pi uninstall <source> [-l] # Alias for remove
132
- pi update [source|self|pi] # Update pi and packages; skips pinned packages
133
- pi update --extensions # Update packages only
132
+ pi update [source|self|pi] # Update pi and packages; reconcile pinned git refs
133
+ pi update --extensions # Update packages only; reconcile pinned git refs
134
134
  pi update --self # Update pi only
135
135
  pi update --extension <src> # Update one package
136
136
  pi list # List installed packages
@@ -267,7 +267,7 @@ pi --tools read,grep,find,ls -p "Review the code"
267
267
  | `PI_CODING_AGENT_SESSION_DIR` | Override session storage directory; overridden by `--session-dir` |
268
268
  | `PI_PACKAGE_DIR` | Override package directory, useful for Nix/Guix store paths |
269
269
  | `PI_OFFLINE` | Disable startup network operations, including update checks, package update checks, and install/update telemetry |
270
- | `PI_SKIP_VERSION_CHECK` | Skip the Pi version update check at startup. This prevents the `pi.dev` latest-version request |
270
+ | `PI_SKIP_VERSION_CHECK` | Skip the Pi version update check at startup. This prevents the npm metadata request |
271
271
  | `PI_TELEMETRY` | Override install/update telemetry: `1`/`true`/`yes` or `0`/`false`/`no`. This does not disable update checks |
272
272
  | `PI_CACHE_RETENTION` | Set to `long` for extended prompt cache where supported |
273
273
  | `VISUAL`, `EDITOR` | External editor for Ctrl+G |
@@ -75,6 +75,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
75
75
  | `reload-runtime.ts` | Adds `/reload-runtime` and `reload_runtime` tool showing safe reload flow |
76
76
  | `interactive-shell.ts` | Run interactive commands (vim, htop) with full terminal via `user_bash` hook |
77
77
  | `inline-bash.ts` | Expands `!{command}` patterns in prompts via `input` event transformation |
78
+ | `input-transform-streaming.ts` | Skips expensive input preprocessing for mid-stream steering via `streamingBehavior` |
78
79
 
79
80
  ### Git Integration
80
81
 
@@ -568,7 +568,7 @@ function streamCustomAnthropic(
568
568
  export default function (pi: ExtensionAPI) {
569
569
  pi.registerProvider("custom-anthropic", {
570
570
  baseUrl: "https://api.anthropic.com",
571
- apiKey: "CUSTOM_ANTHROPIC_API_KEY",
571
+ apiKey: "$CUSTOM_ANTHROPIC_API_KEY",
572
572
  api: "custom-anthropic-api",
573
573
 
574
574
  models: [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -20,6 +20,7 @@ import {
20
20
  type SimpleStreamOptions,
21
21
  streamSimpleAnthropic,
22
22
  streamSimpleOpenAIResponses,
23
+ type ThinkingLevelMap,
23
24
  } from "@fleetagent/pi-ai";
24
25
  import type { ExtensionAPI } from "@fleetagent/pi-coding-agent";
25
26
 
@@ -49,6 +50,7 @@ interface GitLabModel {
49
50
  backend: Backend;
50
51
  baseUrl: string;
51
52
  reasoning: boolean;
53
+ thinkingLevelMap?: ThinkingLevelMap;
52
54
  input: ("text" | "image")[];
53
55
  cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
54
56
  contextWindow: number;
@@ -57,12 +59,37 @@ interface GitLabModel {
57
59
 
58
60
  export const MODELS: GitLabModel[] = [
59
61
  // Anthropic
62
+ {
63
+ id: "claude-opus-4-8",
64
+ name: "Claude Opus 4.8",
65
+ backend: "anthropic",
66
+ baseUrl: ANTHROPIC_PROXY_URL,
67
+ reasoning: true,
68
+ thinkingLevelMap: { xhigh: "max" },
69
+ input: ["text", "image"],
70
+ cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
71
+ contextWindow: 1000000,
72
+ maxTokens: 128000,
73
+ },
74
+ {
75
+ id: "claude-sonnet-4-6",
76
+ name: "Claude Sonnet 4.6",
77
+ backend: "anthropic",
78
+ baseUrl: ANTHROPIC_PROXY_URL,
79
+ reasoning: true,
80
+ thinkingLevelMap: { xhigh: "max" },
81
+ input: ["text", "image"],
82
+ cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
83
+ contextWindow: 1000000,
84
+ maxTokens: 64000,
85
+ },
60
86
  {
61
87
  id: "claude-opus-4-5-20251101",
62
88
  name: "Claude Opus 4.5",
63
89
  backend: "anthropic",
64
90
  baseUrl: ANTHROPIC_PROXY_URL,
65
91
  reasoning: true,
92
+ thinkingLevelMap: { xhigh: "max" },
66
93
  input: ["text", "image"],
67
94
  cost: { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
68
95
  contextWindow: 200000,
@@ -74,6 +101,7 @@ export const MODELS: GitLabModel[] = [
74
101
  backend: "anthropic",
75
102
  baseUrl: ANTHROPIC_PROXY_URL,
76
103
  reasoning: true,
104
+ thinkingLevelMap: { xhigh: "max" },
77
105
  input: ["text", "image"],
78
106
  cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
79
107
  contextWindow: 200000,
@@ -85,12 +113,24 @@ export const MODELS: GitLabModel[] = [
85
113
  backend: "anthropic",
86
114
  baseUrl: ANTHROPIC_PROXY_URL,
87
115
  reasoning: true,
116
+ thinkingLevelMap: { xhigh: "max" },
88
117
  input: ["text", "image"],
89
118
  cost: { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
90
119
  contextWindow: 200000,
91
120
  maxTokens: 8192,
92
121
  },
93
122
  // OpenAI (all use Responses API)
123
+ {
124
+ id: "gpt-5.5-2026-04-23",
125
+ name: "GPT-5.5",
126
+ backend: "openai",
127
+ baseUrl: OPENAI_PROXY_URL,
128
+ reasoning: true,
129
+ input: ["text", "image"],
130
+ cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
131
+ contextWindow: 272000,
132
+ maxTokens: 128000,
133
+ },
94
134
  {
95
135
  id: "gpt-5.1-2025-11-13",
96
136
  name: "GPT-5.1",
@@ -285,7 +325,17 @@ export function streamGitLabDuo(
285
325
 
286
326
  const innerStream =
287
327
  cfg.backend === "anthropic"
288
- ? streamSimpleAnthropic(modelWithBaseUrl as Model<"anthropic-messages">, context, streamOptions)
328
+ ? streamSimpleAnthropic(
329
+ {
330
+ ...(modelWithBaseUrl as Model<"anthropic-messages">),
331
+ compat: {
332
+ ...(modelWithBaseUrl as Model<"anthropic-messages">).compat,
333
+ forceAdaptiveThinking: true,
334
+ },
335
+ },
336
+ context,
337
+ streamOptions,
338
+ )
289
339
  : streamSimpleOpenAIResponses(modelWithBaseUrl as Model<"openai-responses">, context, streamOptions);
290
340
 
291
341
  for await (const event of innerStream) stream.push(event);
@@ -327,12 +377,13 @@ export function streamGitLabDuo(
327
377
  export default function (pi: ExtensionAPI) {
328
378
  pi.registerProvider("gitlab-duo", {
329
379
  baseUrl: AI_GATEWAY_URL,
330
- apiKey: "GITLAB_TOKEN",
380
+ apiKey: "$GITLAB_TOKEN",
331
381
  api: "gitlab-duo-api",
332
- models: MODELS.map(({ id, name, reasoning, input, cost, contextWindow, maxTokens }) => ({
382
+ models: MODELS.map(({ id, name, reasoning, thinkingLevelMap, input, cost, contextWindow, maxTokens }) => ({
333
383
  id,
334
384
  name,
335
385
  reasoning,
386
+ thinkingLevelMap,
336
387
  input,
337
388
  cost,
338
389
  contextWindow,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Merge and Resolve
3
+ *
4
+ * Keeps the working branch up to date with its upstream tracking ref.
5
+ * After each agent turn, fetches and merges. Clean merges complete
6
+ * silently. When conflicts arise, the working tree is left dirty and
7
+ * the agent receives a follow-up message listing each conflict block
8
+ * with file, line range, and ours/theirs sections so it can resolve them.
9
+ * Also re-sends unresolved conflicts from a previous incomplete merge.
10
+ *
11
+ * Start pi with this extension:
12
+ * pi -e ./examples/extensions/git-merge-and-resolve.ts
13
+ */
14
+ import { createReadStream } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { createInterface } from "node:readline";
17
+ import type { ExtensionAPI } from "@fleetagent/pi-coding-agent";
18
+
19
+ interface ConflictBlock {
20
+ file: string;
21
+ startLine: number;
22
+ separatorLine: number;
23
+ endLine: number;
24
+ }
25
+
26
+ /** Parse conflict markers from working tree files with unmerged paths. */
27
+ async function findConflicts(pi: ExtensionAPI, cwd: string): Promise<ConflictBlock[]> {
28
+ const { stdout, code } = await pi.exec("git", ["diff", "--name-only", "--diff-filter=U"]);
29
+ if (code !== 0 || !stdout.trim()) return [];
30
+
31
+ const blocks: ConflictBlock[] = [];
32
+ for (const file of stdout.trim().split("\n")) {
33
+ try {
34
+ const rl = createInterface({ input: createReadStream(join(cwd, file), "utf-8") });
35
+ let lineNo = 0;
36
+ let blockStart: number | undefined;
37
+ let separatorLine: number | undefined;
38
+ for await (const line of rl) {
39
+ lineNo++;
40
+ if (line.startsWith("<<<<<<<")) {
41
+ blockStart = lineNo;
42
+ separatorLine = undefined;
43
+ } else if (line.startsWith("=======") && blockStart !== undefined) {
44
+ separatorLine = lineNo;
45
+ } else if (line.startsWith(">>>>>>>") && blockStart !== undefined && separatorLine !== undefined) {
46
+ blocks.push({ file, startLine: blockStart, separatorLine, endLine: lineNo });
47
+ blockStart = undefined;
48
+ separatorLine = undefined;
49
+ }
50
+ }
51
+ } catch {}
52
+ }
53
+ return blocks;
54
+ }
55
+
56
+ function formatRange(start: number, end: number): string {
57
+ if (start > end) return "empty";
58
+ if (start === end) return `${start}`;
59
+ return `${start}-${end}`;
60
+ }
61
+
62
+ function formatConflicts(ref: string, blocks: ConflictBlock[]): string {
63
+ const lines = [`Merged ${ref} with conflicts:`, ""];
64
+ for (const b of blocks) {
65
+ const ours = formatRange(b.startLine + 1, b.separatorLine - 1);
66
+ const theirs = formatRange(b.separatorLine + 1, b.endLine - 1);
67
+ lines.push(` ${b.file}:${b.startLine}-${b.endLine} (ours ${ours}, theirs ${theirs})`);
68
+ }
69
+ lines.push("", "Resolve these conflicts.");
70
+ return lines.join("\n");
71
+ }
72
+
73
+ export default function (pi: ExtensionAPI) {
74
+ pi.on("agent_end", async (_event, ctx) => {
75
+ const { code: revParseCode } = await pi.exec("git", ["rev-parse", "--git-dir"]);
76
+ if (revParseCode !== 0) return;
77
+
78
+ let ref = "MERGE_HEAD";
79
+
80
+ // If not already in a merge, attempt one
81
+ const { code: mergeHeadCode } = await pi.exec("git", ["rev-parse", "MERGE_HEAD"]);
82
+ if (mergeHeadCode !== 0) {
83
+ // Only attempt a new merge if the working tree is clean
84
+ const { stdout: status } = await pi.exec("git", ["status", "--porcelain"]);
85
+ if (status.trim()) return;
86
+
87
+ const { stdout: upstream, code: upstreamCode } = await pi.exec("git", [
88
+ "rev-parse",
89
+ "--abbrev-ref",
90
+ "--symbolic-full-name",
91
+ "@{u}",
92
+ ]);
93
+ if (upstreamCode !== 0) return;
94
+
95
+ ref = upstream.trim();
96
+ const remote = ref.split("/")[0];
97
+ ctx.ui.notify(`git-merge-and-resolve: fetching ${remote}, merging ${ref}`, "info");
98
+
99
+ const { code: fetchCode, stderr: fetchErr } = await pi.exec("git", ["fetch", remote]);
100
+ if (fetchCode !== 0) {
101
+ ctx.ui.notify(`git-merge-and-resolve: fetch failed: ${fetchErr.trim()}`, "warning");
102
+ return;
103
+ }
104
+
105
+ const { code: mergeCode } = await pi.exec("git", ["merge", "--no-ff", ref]);
106
+ if (mergeCode === 0) return;
107
+ }
108
+
109
+ // Either we just merged with conflicts, or we were already in an unfinished merge
110
+ const conflicts = await findConflicts(pi, ctx.cwd);
111
+ if (conflicts.length === 0) return;
112
+
113
+ pi.sendUserMessage(formatConflicts(ref, conflicts), { deliverAs: "followUp" });
114
+ });
115
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
3
  "private": true,
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",