zidane 6.5.1 → 6.5.3
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/dist/{acp-o-LQAjFw.js → acp-CIpD8UIw.js} +4 -4
- package/dist/{acp-o-LQAjFw.js.map → acp-CIpD8UIw.js.map} +1 -1
- package/dist/acp-cli.js +5 -5
- package/dist/acp.d.ts +1 -1
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-BVNkC1h4.js → agent-8kfGIDb_.js} +755 -586
- package/dist/agent-8kfGIDb_.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{anthropic-Dov2mC5e.js → anthropic-_Hn1YiWr.js} +2 -2
- package/dist/{anthropic-Dov2mC5e.js.map → anthropic-_Hn1YiWr.js.map} +1 -1
- package/dist/{auth-B8ZuBcKC.js → auth-CS_e8ng1.js} +6 -5
- package/dist/{auth-B8ZuBcKC.js.map → auth-CS_e8ng1.js.map} +1 -1
- package/dist/chat/pure.d.ts +4 -4
- package/dist/chat/pure.js +3 -3
- package/dist/chat.d.ts +124 -10
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +6 -6
- package/dist/{context-breakdown-BLF10tOO.js → context-segments-1mcqSCM3.js} +110 -2
- package/dist/context-segments-1mcqSCM3.js.map +1 -0
- package/dist/{context-transport-D-daWOmp.js → context-transport-Bco9Wo0b.js} +3 -3
- package/dist/{context-transport-D-daWOmp.js.map → context-transport-Bco9Wo0b.js.map} +1 -1
- package/dist/contexts/daytona.d.ts +1 -1
- package/dist/contexts/daytona.d.ts.map +1 -1
- package/dist/contexts/daytona.js +5 -2
- package/dist/contexts/daytona.js.map +1 -1
- package/dist/contexts/e2b.d.ts +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/{index-CWBSrqI6.d.ts → index-BKyWhILQ.d.ts} +179 -7
- package/dist/index-BKyWhILQ.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -7
- package/dist/{login-CR3fDHXh.js → login-D3CKhgbI.js} +2 -2
- package/dist/{login-CR3fDHXh.js.map → login-D3CKhgbI.js.map} +1 -1
- package/dist/{mcp-C6IkXNOS.js → mcp-D9fqOQo5.js} +3 -3
- package/dist/{mcp-C6IkXNOS.js.map → mcp-D9fqOQo5.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/{oauth-bkEOybqm.js → oauth-BYjlFR-J.js} +49 -10
- package/dist/oauth-BYjlFR-J.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +68 -1
- package/dist/providers/xai.js.map +1 -0
- package/dist/{providers-B6mFe7OD.js → providers-VypZh7rD.js} +3 -3
- package/dist/{providers-B6mFe7OD.js.map → providers-VypZh7rD.js.map} +1 -1
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +4 -4
- package/dist/restate.d.ts +1 -1
- package/dist/restate.js +1 -1
- package/dist/rolldown-runtime-BhDjJH2R.js +15 -0
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/{stdio-DzUUkkhS.js → stdio-DPpBDPx5.js} +13 -4
- package/dist/{stdio-DzUUkkhS.js.map → stdio-DPpBDPx5.js.map} +1 -1
- package/dist/{stdio-loader-BILyUGmi.js → stdio-loader-CI4sPFve.js} +11 -6
- package/dist/stdio-loader-CI4sPFve.js.map +1 -0
- package/dist/{tool-formatters-gFqQbzPY.d.ts → tool-formatters-DMr62Hpy.d.ts} +3 -3
- package/dist/{tool-formatters-gFqQbzPY.d.ts.map → tool-formatters-DMr62Hpy.d.ts.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-qCs3wZpu.d.ts → transcript-anchors-BHihk9nf.d.ts} +2 -2
- package/dist/{transcript-anchors-qCs3wZpu.d.ts.map → transcript-anchors-BHihk9nf.d.ts.map} +1 -1
- package/dist/{transcript-anchors-DEIr0tJC.js → transcript-anchors-uU_KZLsp.js} +161 -36
- package/dist/transcript-anchors-uU_KZLsp.js.map +1 -0
- package/dist/tui.d.ts +74 -3
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +823 -81
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-M9-jUyDd.d.ts → turn-operations-BgqVUjtl.d.ts} +3 -3
- package/dist/{turn-operations-M9-jUyDd.d.ts.map → turn-operations-BgqVUjtl.d.ts.map} +1 -1
- package/dist/{turn-operations-C3IijXMW.js → turn-operations-iGu5J08j.js} +2 -2
- package/dist/{turn-operations-C3IijXMW.js.map → turn-operations-iGu5J08j.js.map} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/{xai-Du6Wfnlb.js → xai-CfmI5Y5k.js} +10 -67
- package/dist/xai-CfmI5Y5k.js.map +1 -0
- package/docs/CHAT.md +42 -1
- package/docs/INTERACTIONS.md +39 -0
- package/docs/RESTATE.md +7 -0
- package/docs/TUI.md +28 -2
- package/package.json +18 -17
- package/dist/agent-BVNkC1h4.js.map +0 -1
- package/dist/context-breakdown-BLF10tOO.js.map +0 -1
- package/dist/index-CWBSrqI6.d.ts.map +0 -1
- package/dist/oauth-bkEOybqm.js.map +0 -1
- package/dist/stdio-loader-BILyUGmi.js.map +0 -1
- package/dist/transcript-anchors-DEIr0tJC.js.map +0 -1
- package/dist/xai-Du6Wfnlb.js.map +0 -1
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { d as ClassifiedErrorKind, i as AgentContextExceededError, l as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, n as AgentAbortedError, o as AgentProviderError, p as matchesContextExceeded, r as AgentBudgetExceededError, s as AgentToolNotAllowedError, u as ClassifiedError } from "./timeout-DEaqslLO.js";
|
|
2
|
-
import { $
|
|
2
|
+
import { $m as RepeatGuardConfig, Ah as toolOutputBudgetByteLength, Bf as StreamCallbacks, Bm as McpToolHookContext, C as SuccessfulMutationOutcome, Ch as ToolResultImageContent, Cm as AgentClock, Dh as ToolResultVideoContent, Dp as OpenAIParams, Eh as ToolResultTextContent, Fh as ToolMap, Gf as ToolSpec, Gi as AgentHookMap, Gm as ProgressNudgeKind, Jh as ToolStateOp, Jm as PromptDocumentPart, Jp as AnthropicParams, Kf as TurnResult, Kh as StagedToolState, Ki as AgentHooks, Km as ProgressNudgeMessage, Lf as Provider, Lh as ReadStateEntry, Mh as toolResultToText, Nh as ToolContext, Ns as SpawnToolOptions, Oh as TurnFinishReason, Os as ChildAgent, Ph as ToolDef, Pm as DEFAULT_AGENT_CLOCK, Ps as SpawnToolState, Qm as PromptVideoPart, Rf as ProviderCapabilities, Rh as ReadStateMap, Rr as InteractionToolOptions, S as MutationValidationResult, Sg as RemoteStoreOptions, Sh as ToolResultDocumentRefContent, Sm as AgentBehavior, Th as ToolResultMetadata, Tm as AgentStats, Uf as ToolCall, Vf as StreamOptions, Wf as ToolResult, Wi as Agent, Wm as OAuthRefreshHookContext, Xh as ToolStateView, Xm as PromptPart, Yh as ToolStateStore, Ym as PromptImagePart, Zm as PromptTextPart, _ as statsByModel, _h as ToolHookContext, ag as SkillsConfig, ah as SessionContentBlock, b as MutationValidationHooksOptions, bh as ToolResultContent, cg as Session, ch as SessionMessage, dg as SessionRun, eh as RepeatGuardToolMatcher, fg as SessionStore, h as flattenTurns, hh as ThinkingLevel, ih as RunHookMap, jh as toolOutputByteLength, kh as TurnUsage, km as ChildRunStats, lg as SessionData, lh as SessionTurn, nh as ResolveContentRefBlock, og as CreateSessionOptions, oh as SessionEndStatus, p as ModelUsage, ph as StreamHookContext, qh as ToolStateController, qi as AgentOptions, qm as PromptAudioPart, rg as SkillResource, rh as RetryConfig, rp as OpenRouterParams, sh as SessionHookContext, t as Preset, t_ as McpConnection, tg as SkillConfig, th as ResolveContentRef, uh as SpawnHookContext, v as MutationValidationBatch, vh as ToolOutcome, vr as ValidationResult, wh as ToolResultImageRefContent, wm as AgentRunOptions, x as MutationValidationMutation, xh as ToolResultDocumentContent, y as MutationValidationFailure, yh as ToolResultAudioContent, zm as McpServerConfig, zp as CerebrasParams } from "./index-BKyWhILQ.js";
|
|
3
3
|
import { _ as TaskHandle, a as ContextType, c as ExecutionContext, g as TaskExitInfo, h as TaskEntry, l as ExecutionHandle, n as ContextCapabilities, o as DetachedTasksCapability, p as SpawnConfig, r as ContextHardening, s as ExecResult, t as BackgroundTaskStatus, v as TaskStallInfo } from "./types-M2DauK2D.js";
|
|
4
4
|
import { t as SandboxProvider } from "./sandbox-DLY6uah7.js";
|
|
5
5
|
export { type Agent, AgentAbortedError, type AgentBehavior, AgentBudgetExceededError, type AgentClock, AgentContextExceededError, type AgentHookMap, type AgentHooks, type AgentOptions, AgentProviderError, type AgentRunOptions, type AgentStats, AgentToolNotAllowedError, type AnthropicParams, type BackgroundTaskStatus, CONTEXT_EXCEEDED_MESSAGE_PATTERNS, type CerebrasParams, type ChildAgent, type ChildRunStats, type ClassifiedError, type ClassifiedErrorKind, type ContextCapabilities, type ContextHardening, type ContextType, type CreateSessionOptions, DEFAULT_AGENT_CLOCK, type DetachedTasksCapability, type ExecResult, type ExecutionContext, type ExecutionHandle, type InteractionToolOptions, type McpConnection, type McpServerConfig, type McpToolHookContext, type ModelUsage, type MutationValidationBatch, type MutationValidationFailure, type MutationValidationHooksOptions, type MutationValidationMutation, type MutationValidationResult, type OAuthRefreshHookContext, type OpenAIParams, type OpenRouterParams, type Preset, type ProgressNudgeKind, type ProgressNudgeMessage, type PromptAudioPart, type PromptDocumentPart, type PromptImagePart, type PromptPart, type PromptTextPart, type PromptVideoPart, type Provider, type ProviderCapabilities, type ReadStateEntry, type ReadStateMap, type RemoteStoreOptions, type RepeatGuardConfig, type RepeatGuardToolMatcher, type ResolveContentRef, type ResolveContentRefBlock, type RetryConfig, type RunHookMap, type SandboxProvider, type Session, type SessionContentBlock, type SessionData, type SessionEndStatus, type SessionHookContext, type SessionMessage, type SessionRun, type SessionStore, type SessionTurn, type SkillConfig, type SkillResource, type SkillsConfig, type SpawnConfig, type SpawnHookContext, type SpawnToolOptions, type SpawnToolState, type StagedToolState, type StreamCallbacks, type StreamHookContext, type StreamOptions, type SuccessfulMutationOutcome, type TaskEntry, type TaskExitInfo, type TaskHandle, type TaskStallInfo, type ThinkingLevel, type ToolCall, type ToolContext, type ToolDef, type ToolHookContext, type ToolMap, type ToolOutcome, type ToolResult, type ToolResultAudioContent, type ToolResultContent, type ToolResultDocumentContent, type ToolResultDocumentRefContent, type ToolResultImageContent, type ToolResultImageRefContent, type ToolResultMetadata, type ToolResultTextContent, type ToolResultVideoContent, type ToolSpec, type ToolStateController, type ToolStateOp, type ToolStateStore, type ToolStateView, type TurnFinishReason, type TurnResult, type TurnUsage, type ValidationResult, flattenTurns, matchesContextExceeded, statsByModel, toolOutputBudgetByteLength, toolOutputByteLength, toolResultToText };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as isOAuthAccessToken, r as extractRuntimeCredentials, s as resolveOAuthApiKey } from "./oauth-bkEOybqm.js";
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.js";
|
|
3
2
|
import { createServer } from "node:http";
|
|
4
3
|
//#region src/chat/oauth-page/pkce.ts
|
|
5
4
|
/**
|
|
@@ -29,6 +28,13 @@ async function generatePkce() {
|
|
|
29
28
|
}
|
|
30
29
|
//#endregion
|
|
31
30
|
//#region src/chat/oauth-page/xai.ts
|
|
31
|
+
var xai_exports = /* @__PURE__ */ __exportAll({
|
|
32
|
+
XAI_OAUTH_PROVIDER_ID: () => XAI_OAUTH_PROVIDER_ID,
|
|
33
|
+
createXaiOAuthProvider: () => createXaiOAuthProvider,
|
|
34
|
+
loginXai: () => loginXai,
|
|
35
|
+
refreshXaiToken: () => refreshXaiToken,
|
|
36
|
+
xaiBaseUrl: () => xaiBaseUrl
|
|
37
|
+
});
|
|
32
38
|
/** pi-ai / zidane OAuth provider id. Also the credentials-file key. */
|
|
33
39
|
const XAI_OAUTH_PROVIDER_ID = "xai-oauth";
|
|
34
40
|
const DEFAULT_BASE_URL = "https://api.x.ai/v1";
|
|
@@ -311,69 +317,6 @@ function createXaiOAuthProvider(renderPage) {
|
|
|
311
317
|
};
|
|
312
318
|
}
|
|
313
319
|
//#endregion
|
|
314
|
-
|
|
315
|
-
const DEFAULT_MODEL = "grok-4.3";
|
|
316
|
-
function xai(params) {
|
|
317
|
-
const defaultModel = params?.defaultModel || DEFAULT_MODEL;
|
|
318
|
-
const baseURL = xaiBaseUrl();
|
|
319
|
-
const capabilities = params?.capabilities ?? {
|
|
320
|
-
vision: true,
|
|
321
|
-
imageInToolResult: false
|
|
322
|
-
};
|
|
323
|
-
const baseCredentials = extractRuntimeCredentials(params);
|
|
324
|
-
let runtimeCredentials = baseCredentials;
|
|
325
|
-
const staticKey = params?.apiKey ?? process.env.XAI_API_KEY;
|
|
326
|
-
const isOAuth = baseCredentials !== void 0 || !staticKey || isOAuthAccessToken(staticKey);
|
|
327
|
-
/**
|
|
328
|
-
* Build the openai-compat delegate for a resolved bearer. Cheap — the factory
|
|
329
|
-
* only wires closures — so we re-create it per turn with the freshly resolved
|
|
330
|
-
* (possibly refreshed) OAuth token rather than baking a stale key in once.
|
|
331
|
-
*/
|
|
332
|
-
function delegateFor(apiKey) {
|
|
333
|
-
return openaiCompat({
|
|
334
|
-
name: "xai",
|
|
335
|
-
apiKey,
|
|
336
|
-
baseURL,
|
|
337
|
-
defaultModel,
|
|
338
|
-
capabilities,
|
|
339
|
-
extraHeaders: params?.extraHeaders
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
const skeleton = delegateFor("placeholder-resolved-at-stream");
|
|
343
|
-
return {
|
|
344
|
-
...skeleton,
|
|
345
|
-
name: "xai",
|
|
346
|
-
meta: {
|
|
347
|
-
...skeleton.meta,
|
|
348
|
-
defaultModel,
|
|
349
|
-
isOAuth
|
|
350
|
-
},
|
|
351
|
-
async stream(options, callbacks) {
|
|
352
|
-
return delegateFor(await resolveOAuthApiKey({
|
|
353
|
-
provider: "xai",
|
|
354
|
-
providerId: XAI_OAUTH_PROVIDER_ID,
|
|
355
|
-
params: runtimeCredentials ? {
|
|
356
|
-
...params,
|
|
357
|
-
...runtimeCredentials
|
|
358
|
-
} : params,
|
|
359
|
-
envKey: "XAI_API_KEY",
|
|
360
|
-
missingError: "No xAI credentials found. Set XAI_API_KEY or run `bun run auth --xai` first.",
|
|
361
|
-
refreshError: (reason) => `xAI OAuth token refresh failed. Run \`bun run auth --xai\` again. ${reason}`
|
|
362
|
-
}, {
|
|
363
|
-
...callbacks,
|
|
364
|
-
async onOAuthRefresh(ctx) {
|
|
365
|
-
if (ctx.source === "params") runtimeCredentials = {
|
|
366
|
-
access: ctx.credentials.access,
|
|
367
|
-
refresh: ctx.credentials.refresh,
|
|
368
|
-
expires: ctx.credentials.expires
|
|
369
|
-
};
|
|
370
|
-
await callbacks.onOAuthRefresh?.(ctx);
|
|
371
|
-
}
|
|
372
|
-
})).stream(options, callbacks);
|
|
373
|
-
}
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
//#endregion
|
|
377
|
-
export { createXaiOAuthProvider as n, generatePkce as r, xai as t };
|
|
320
|
+
export { generatePkce as a, xai_exports as i, createXaiOAuthProvider as n, xaiBaseUrl as r, XAI_OAUTH_PROVIDER_ID as t };
|
|
378
321
|
|
|
379
|
-
//# sourceMappingURL=xai-
|
|
322
|
+
//# sourceMappingURL=xai-CfmI5Y5k.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xai-CfmI5Y5k.js","names":[],"sources":["../src/chat/oauth-page/pkce.ts","../src/chat/oauth-page/xai.ts"],"sourcesContent":["/**\n * PKCE helper. Inlined here (rather than imported from pi-ai) because\n * `@earendil-works/pi-ai/oauth` does not re-export it, and we don't want\n * to reach into `node_modules/.../utils/oauth/pkce.js` — that's an\n * implementation-detail path that breaks on minor upstream rearrangements.\n *\n * Web Crypto API only. Works under Bun + Node 22+ without any node:crypto\n * import (matches pi-ai's own behavior). Output is base64url per RFC 7636.\n */\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = ''\n for (const byte of bytes)\n binary += String.fromCharCode(byte)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '')\n}\n\nexport interface PkcePair {\n /** Random verifier used in the token-exchange step. */\n verifier: string\n /** SHA-256(verifier) sent on the authorize URL. */\n challenge: string\n}\n\nexport async function generatePkce(): Promise<PkcePair> {\n const verifierBytes = new Uint8Array(32)\n crypto.getRandomValues(verifierBytes)\n const verifier = base64UrlEncode(verifierBytes)\n\n const data = new TextEncoder().encode(verifier)\n const hashBuffer = await crypto.subtle.digest('SHA-256', data)\n const challenge = base64UrlEncode(new Uint8Array(hashBuffer))\n\n return { verifier, challenge }\n}\n","/**\n * xAI Grok OAuth flow (SuperGrok / X Premium+).\n *\n * xAI is **not** built into pi-ai, so — like Cursor — we implement the flow\n * here and register it with pi-ai's OAuth registry (`registerXaiOAuthProvider`)\n * so `getOAuthApiKey('xai-oauth', …)` resolves to it during lazy token refresh.\n *\n * Auth shape: OAuth 2.0 + PKCE with a loopback callback server, against xAI's\n * OIDC issuer (`https://auth.x.ai`). Differs from the Anthropic / Codex flows\n * (which use the shared `startCallbackServer`) on three points, so it gets its\n * own login impl rather than reusing that primitive:\n *\n * 1. The `redirect_uri` must be `http://127.0.0.1:56121/callback` verbatim\n * (xAI matches the registered loopback IP, not `localhost`).\n * 2. Endpoints come from OIDC discovery, not hard-coded constants.\n * 3. Token exchange + refresh are `application/x-www-form-urlencoded`\n * (the Anthropic flow posts JSON).\n *\n * The callback page still routes through {@link OAuthCallbackPageRenderer} so\n * the post-redirect HTML matches the rest of zidane's themed flows.\n *\n * Flow + constants adapted from the public reference implementations\n * (`stnly/pi-grok`, `BlockedPath/pi-xai-oauth`) and the Hermes Agent docs.\n * xAI's OAuth surface is unofficial/undocumented — re-verify the client id,\n * scopes, and callback port if login starts failing.\n */\n\nimport type {\n OAuthCredentials,\n OAuthLoginCallbacks,\n} from '@earendil-works/pi-ai/oauth'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { OAuthProviderInterface } from '../../providers/oauth-registry'\nimport type { OAuthCallbackPageRenderer } from './render'\nimport { createServer } from 'node:http'\nimport { generatePkce } from './pkce'\n\n/** pi-ai / zidane OAuth provider id. Also the credentials-file key. */\nexport const XAI_OAUTH_PROVIDER_ID = 'xai-oauth'\n\nconst DEFAULT_BASE_URL = 'https://api.x.ai/v1'\nconst ISSUER = 'https://auth.x.ai'\nconst DISCOVERY_URL = `${ISSUER}/.well-known/openid-configuration`\n/** Public client id used by the Grok CLI OAuth surface. */\nconst DEFAULT_CLIENT_ID = 'b1a00492-073a-47ea-816f-4c329264a828'\nconst DEFAULT_SCOPE = 'openid profile email offline_access grok-cli:access api:access'\n/** xAI matches the registered loopback IP exactly — `localhost` is rejected. */\nconst DEFAULT_CALLBACK_HOST = '127.0.0.1'\nconst DEFAULT_CALLBACK_PORT = 56121\nconst CALLBACK_PATH = '/callback'\nconst PROVIDER_NAME = 'xAI Grok'\n/** Refresh slightly early so requests don't race expiry. */\nconst REFRESH_SKEW_MS = 120_000\nconst CALLBACK_TIMEOUT_MS = 180_000\n\nfunction xaiOAuthClientId(): string {\n return process.env.PI_XAI_OAUTH_CLIENT_ID || DEFAULT_CLIENT_ID\n}\n\nfunction xaiOAuthScope(): string {\n return process.env.PI_XAI_OAUTH_SCOPE || DEFAULT_SCOPE\n}\n\nfunction xaiOAuthCallbackHost(): string {\n return process.env.PI_XAI_OAUTH_CALLBACK_HOST || DEFAULT_CALLBACK_HOST\n}\n\nfunction xaiOAuthCallbackPort(): number {\n const parsed = Number.parseInt(process.env.PI_XAI_OAUTH_CALLBACK_PORT || String(DEFAULT_CALLBACK_PORT), 10)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CALLBACK_PORT\n}\n\n/** Resolve the xAI API base URL (env override → default). */\nexport function xaiBaseUrl(): string {\n return (process.env.PI_XAI_BASE_URL || process.env.XAI_BASE_URL || DEFAULT_BASE_URL)\n .replace(/\\/+$/, '')\n}\n\ninterface XaiDiscovery {\n authorization_endpoint: string\n token_endpoint: string\n}\n\n/**\n * xAI OAuth credentials. Extends the base `{ access, refresh, expires }` with\n * the resolved `token_endpoint` so refresh doesn't need a second discovery\n * round-trip, plus the cached discovery doc for validation.\n */\nexport interface XaiOAuthCredentials extends OAuthCredentials {\n tokenEndpoint?: string\n discovery?: XaiDiscovery\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n let binary = ''\n for (const b of bytes)\n binary += String.fromCharCode(b)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction randomToken(byteLength = 16): string {\n return base64Url(crypto.getRandomValues(new Uint8Array(byteLength)))\n}\n\n/**\n * Refuse any OIDC endpoint that isn't HTTPS on an xAI origin.\n *\n * The discovery doc is cached long-term in `credentials.json`. A single MITM\n * during initial login could otherwise pin a malicious `token_endpoint` that\n * receives every subsequent refresh token. Validating scheme + host keeps the\n * endpoint on `x.ai` / `*.x.ai`.\n */\nfunction validateEndpoint(value: string, field: string): string {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n throw new Error(`xAI OAuth discovery returned an invalid ${field}: ${value}`)\n }\n if (url.protocol !== 'https:')\n throw new Error(`xAI OAuth ${field} must use HTTPS: ${value}`)\n const host = url.hostname.toLowerCase()\n if (host !== 'x.ai' && !host.endsWith('.x.ai'))\n throw new Error(`Refusing non-xAI OAuth ${field}: ${value}`)\n return url.toString()\n}\n\nasync function discover(): Promise<XaiDiscovery> {\n let response: Response\n try {\n response = await fetch(DISCOVERY_URL, {\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(15_000),\n })\n }\n catch (cause) {\n throw new Error(`xAI OIDC discovery failed: ${cause instanceof Error ? cause.message : String(cause)}`)\n }\n if (!response.ok)\n throw new Error(`xAI OIDC discovery returned ${response.status}`)\n\n const payload = await response.json() as Record<string, unknown>\n return {\n authorization_endpoint: validateEndpoint(String(payload.authorization_endpoint ?? ''), 'authorization_endpoint'),\n token_endpoint: validateEndpoint(String(payload.token_endpoint ?? ''), 'token_endpoint'),\n }\n}\n\ninterface CallbackResult {\n code?: string\n state?: string\n error?: string\n errorDescription?: string\n}\n\ninterface XaiCallbackServer {\n redirectUri: string\n waitForCallback: (timeoutMs: number) => Promise<CallbackResult>\n close: () => void\n}\n\n/**\n * Start the loopback callback server on `127.0.0.1:56121`. Falls back to an\n * OS-assigned port if 56121 is taken — xAI accepts any loopback port as long\n * as the `redirect_uri` we send on the authorize URL matches the listener.\n */\nasync function startXaiCallbackServer(renderPage: OAuthCallbackPageRenderer): Promise<XaiCallbackServer> {\n const callbackHost = xaiOAuthCallbackHost()\n const callbackPort = xaiOAuthCallbackPort()\n let settle: ((value: CallbackResult) => void) | undefined\n let settled = false\n const callbackPromise = new Promise<CallbackResult>((resolve) => {\n settle = (value) => {\n if (settled)\n return\n settled = true\n resolve(value)\n }\n })\n\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n try {\n // accounts.x.ai may issue a CORS preflight against the loopback listener.\n const origin = req.headers.origin\n if (origin === 'https://accounts.x.ai' || origin === 'https://auth.x.ai') {\n res.setHeader('Access-Control-Allow-Origin', origin)\n res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS')\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type')\n res.setHeader('Access-Control-Allow-Private-Network', 'true')\n res.setHeader('Vary', 'Origin')\n }\n if (req.method === 'OPTIONS') {\n res.statusCode = 204\n res.end()\n return\n }\n\n const url = new URL(req.url ?? '/', `http://${callbackHost}`)\n if (url.pathname !== CALLBACK_PATH) {\n res.statusCode = 404\n res.end('Not found')\n return\n }\n\n const result: CallbackResult = {\n code: url.searchParams.get('code') ?? undefined,\n state: url.searchParams.get('state') ?? undefined,\n error: url.searchParams.get('error') ?? undefined,\n errorDescription: url.searchParams.get('error_description') ?? undefined,\n }\n\n res.statusCode = result.error ? 400 : 200\n res.setHeader('Content-Type', 'text/html; charset=utf-8')\n res.end(renderPage(result.error\n ? {\n kind: 'error',\n provider: PROVIDER_NAME,\n message: `${PROVIDER_NAME} authentication did not complete.`,\n details: result.errorDescription ?? result.error,\n }\n : {\n kind: 'success',\n provider: PROVIDER_NAME,\n message: 'xAI authentication completed. You can close this window.',\n }))\n settle?.(result)\n }\n catch {\n res.statusCode = 500\n res.end('Internal error')\n }\n })\n\n const listen = (port: number) => new Promise<number>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, callbackHost, () => {\n server.removeListener('error', reject)\n const addr = server.address()\n resolve(typeof addr === 'object' && addr ? addr.port : port)\n })\n })\n\n let actualPort: number\n try {\n actualPort = await listen(callbackPort)\n }\n catch {\n actualPort = await listen(0)\n }\n\n return {\n redirectUri: `http://${callbackHost}:${actualPort}${CALLBACK_PATH}`,\n waitForCallback: timeoutMs => Promise.race([\n callbackPromise,\n new Promise<CallbackResult>(resolve =>\n setTimeout(\n resolve,\n timeoutMs,\n { error: 'timeout', errorDescription: 'Timed out waiting for the xAI OAuth callback.' },\n )),\n ]),\n close: () => {\n try { server.close() }\n catch { /* already closed */ }\n },\n }\n}\n\nfunction expiryFromPayload(payload: Record<string, unknown>): number {\n const expiresIn = typeof payload.expires_in === 'number'\n ? payload.expires_in\n : Number(payload.expires_in ?? 3600)\n return Date.now() + expiresIn * 1000 - REFRESH_SKEW_MS\n}\n\nasync function exchangeCode(\n tokenEndpoint: string,\n code: string,\n redirectUri: string,\n verifier: string,\n discovery: XaiDiscovery,\n): Promise<XaiOAuthCredentials> {\n const clientId = xaiOAuthClientId()\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: clientId,\n code,\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n signal: AbortSignal.timeout(30_000),\n })\n if (!response.ok)\n throw new Error(`xAI token exchange failed: ${response.status} ${await response.text().catch(() => '')}`)\n\n const payload = await response.json() as Record<string, unknown>\n const access = String(payload.access_token ?? '')\n const refresh = String(payload.refresh_token ?? '')\n if (!access)\n throw new Error('xAI token exchange did not return an access_token.')\n if (!refresh)\n throw new Error('xAI token exchange did not return a refresh_token.')\n\n return { access, refresh, expires: expiryFromPayload(payload), tokenEndpoint, discovery }\n}\n\nexport async function loginXai(\n renderPage: OAuthCallbackPageRenderer,\n callbacks: OAuthLoginCallbacks,\n): Promise<XaiOAuthCredentials> {\n const discovery = await discover()\n const { verifier, challenge } = await generatePkce()\n const state = randomToken()\n const nonce = randomToken()\n const server = await startXaiCallbackServer(renderPage)\n\n try {\n const authUrl = new URL(discovery.authorization_endpoint)\n authUrl.searchParams.set('response_type', 'code')\n authUrl.searchParams.set('client_id', xaiOAuthClientId())\n authUrl.searchParams.set('redirect_uri', server.redirectUri)\n authUrl.searchParams.set('scope', xaiOAuthScope())\n authUrl.searchParams.set('code_challenge', challenge)\n authUrl.searchParams.set('code_challenge_method', 'S256')\n authUrl.searchParams.set('state', state)\n authUrl.searchParams.set('nonce', nonce)\n // Opt into xAI's generic OAuth plan tier (SuperGrok / X Premium+).\n authUrl.searchParams.set('plan', 'generic')\n authUrl.searchParams.set('referrer', 'zidane')\n\n callbacks.onAuth({\n url: authUrl.toString(),\n instructions: `Sign in to xAI and approve access. If the browser is on another machine, the callback listener is ${server.redirectUri}.`,\n })\n\n const result = await server.waitForCallback(CALLBACK_TIMEOUT_MS)\n if (result.error)\n throw new Error(result.errorDescription ?? result.error)\n if (result.state !== state)\n throw new Error('xAI OAuth state mismatch — possible CSRF. Please retry.')\n if (!result.code)\n throw new Error('xAI OAuth callback did not include an authorization code.')\n\n callbacks.onProgress?.('Exchanging authorization code for tokens...')\n return await exchangeCode(discovery.token_endpoint, result.code, server.redirectUri, verifier, discovery)\n }\n finally {\n server.close()\n }\n}\n\nexport async function refreshXaiToken(credentials: OAuthCredentials): Promise<XaiOAuthCredentials> {\n const xai = credentials as XaiOAuthCredentials\n const clientId = xaiOAuthClientId()\n if (!credentials.refresh)\n throw new Error('xAI credentials are missing a refresh token — re-login required.')\n\n const tokenEndpoint = xai.tokenEndpoint\n ?? xai.discovery?.token_endpoint\n ?? (await discover()).token_endpoint\n validateEndpoint(tokenEndpoint, 'token_endpoint')\n\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: clientId,\n refresh_token: credentials.refresh,\n }),\n signal: AbortSignal.timeout(30_000),\n })\n if (!response.ok)\n throw new Error(`xAI token refresh failed: ${response.status} ${await response.text().catch(() => '')}`)\n\n const payload = await response.json() as Record<string, unknown>\n const access = String(payload.access_token ?? '')\n if (!access)\n throw new Error('xAI token refresh did not return an access_token.')\n\n return {\n ...xai,\n access,\n // xAI may omit a rotated refresh token — keep the existing one.\n refresh: String(payload.refresh_token ?? credentials.refresh),\n expires: expiryFromPayload(payload),\n tokenEndpoint,\n }\n}\n\n/**\n * Build an xAI `OAuthProviderInterface`. Shape matches Anthropic / Codex /\n * Cursor so it drops into `BUILTIN_PROVIDERS` and `src/auth.ts` unchanged.\n *\n * `usesCallbackServer: true` — the loopback flow has a real callback server,\n * so the TUI doesn't need to prompt for a manual code paste.\n */\nexport function createXaiOAuthProvider(renderPage: OAuthCallbackPageRenderer): OAuthProviderInterface {\n return {\n id: XAI_OAUTH_PROVIDER_ID,\n name: 'xAI Grok (SuperGrok / X Premium+)',\n usesCallbackServer: true,\n login: callbacks => loginXai(renderPage, callbacks),\n refreshToken: refreshXaiToken,\n getApiKey: credentials => credentials.access,\n }\n}\n"],"mappings":"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"}
|
package/docs/CHAT.md
CHANGED
|
@@ -783,10 +783,51 @@ A categorized view of what's consuming the context window — system prompt, rul
|
|
|
783
783
|
- **`buildContextBreakdown(snapshot)`** (`zidane/chat` + `zidane/chat/pure`, node-free) is the pure shaper. Live categories always reconcile to the real last-turn total (`conversation = used − Σ(other live)`), so the bar can never contradict the footer's `ctx` gauge. Free space and the autocompact buffer are exact; per-section counts are heuristic (`estimateTokens`) unless the provider supplies exact counts.
|
|
784
784
|
- **Exact counts (optional).** When a provider implements `Provider.countTokens(payload)` (Anthropic `messages.countTokens`, OpenAI `responses.input_tokens`), `getContextBreakdown` differences a system-only and a system+tools probe to attribute the System prompt + Tool definitions buckets exactly; categories with no exact source render with a `~` prefix. `countTokens` returns `null` when unavailable (no endpoint / wrong auth) and the heuristic fills in — it can never break the breakdown.
|
|
785
785
|
- **Autocompact zone.** Pass `opts.compactThreshold` (the `Settings.autoCompactThreshold` fraction) and the "Autocompact buffer" bucket becomes the whole tail from the threshold to the end of the window, so the bar shows exactly where compaction will fire.
|
|
786
|
-
- **Rules attribution.** The agent can't isolate the rules block from the opaque host-composed system string, so the host passes `AgentRunOptions.contextSections = { rulesBlock, rulesFiles }` (the rendered AGENTS.md block + its source files) for a dedicated, per-file "Rules" row.
|
|
786
|
+
- **Rules attribution.** The agent can't isolate the rules block from the opaque host-composed system string, so the host passes `AgentRunOptions.contextSections = { rulesBlock, rulesFiles }` (the rendered AGENTS.md block + its source files) for a dedicated, per-file "Rules" row. Both hosts discover rules via `discoverAgentsMd`, but only the **GUI** currently threads `contextSections` into `agent.run()` — the TUI does not, so its breakdown has no "Rules" bucket. (The TUI's Settings → Instructions tab gets its per-file rows from the host side instead; see below.)
|
|
787
787
|
|
|
788
788
|
`ContextBreakdown`, `ContextBreakdownOptions`, `ContextSnapshot`, `ContextCategory`, `ContextItem`, `ContextUsageSplit`, `ContextExactCounts`, `ContextMcpGroup`, and `buildContextBreakdown` are all exported from both `zidane/chat` and `zidane/chat/pure`.
|
|
789
789
|
|
|
790
|
+
## `SYSTEM.md` — overriding the built-in prompt
|
|
791
|
+
|
|
792
|
+
`discoverSystemMd` (`chat/system-md`) finds a `SYSTEM.md` that **replaces** the built-in doctrine. Deliberately inverted semantics vs `AGENTS.md`:
|
|
793
|
+
|
|
794
|
+
| | `AGENTS.md` | `SYSTEM.md` |
|
|
795
|
+
|------------|-----------------------------|-----------------------------------|
|
|
796
|
+
| effect | appends to the doctrine | replaces the doctrine |
|
|
797
|
+
| multiples | every match loads, in order | exactly one wins |
|
|
798
|
+
| precedence | broad → narrow, all present | project beats user, rest shadowed |
|
|
799
|
+
|
|
800
|
+
Discovery honors the same `userInstructionsScope` setting as `AGENTS.md`, with the same per-scope rules — `DiscoverSystemMdOptions.scope` takes the `AgentsMdScope` itself, not a boolean. `'none'` skips discovery before any filesystem probe (it exists to debug the base prompt "without any user doctrine bleeding in", and an override is the most invasive form of user doctrine there is). `'user'` drops the project candidate and `'project'` drops the user one, mirroring how `discoverAgentsMd` builds its list. That matters most for `'user'`: it is how someone says "do not trust this checkout", so a repo-root `SYSTEM.md` must not load there.
|
|
801
|
+
|
|
802
|
+
> **A project-scope `SYSTEM.md` deletes safety doctrine.** `AGENTS.md` can only append, and is rendered under a header marking it as user-authored guidance. An override REPLACES the built-in prompt — `ACTIONS_WITH_CARE_DOCTRINE` and `GIT_DOCTRINE` included — so a file that merely exists at the root of a checkout can drop "destructive actions need explicit confirmation" and "never force-push without confirmation". Appending to doctrine and deleting doctrine are different risk profiles. Because discovery is automatic at session activation, the TUI paints a one-line transcript warning naming the file whenever a *project*-scope override is in force; the user-scope file stays quiet, since the user wrote it in their own home directory. Set `userInstructionsScope` to `'user'` or `'none'` to refuse repo-supplied overrides entirely.
|
|
803
|
+
|
|
804
|
+
Search order, winner first: `<project root>/SYSTEM.md`, then `~/.{prefix}/SYSTEM.md`. Concatenating overrides would be incoherent — two files each claiming to *be* the system prompt can't both be it — so one wins and the losers come back in `SystemMdResult.shadowed` for the UI to mark inactive. An empty or whitespace-only file counts as absent, so a created-but-unwritten file can't hand the model a blank prompt.
|
|
805
|
+
|
|
806
|
+
`BuildSystemOptions.systemOverride` is what applies it: `buildBuildSystem` / `buildPlanSystem` swap it in for identity + doctrine + guidance. Two things still compose around it:
|
|
807
|
+
|
|
808
|
+
- **`userInstructions` is appended below it** — `AGENTS.md` stays an additive layer, so overriding the prompt doesn't silently disable your project instructions.
|
|
809
|
+
- **The `<env>` block still lands below the cache boundary** — it carries cwd + project root, which the file tools' path resolution depends on. Dropping it would break `read_file` in ways that look like model error rather than configuration.
|
|
810
|
+
|
|
811
|
+
`allowInteraction` is not consulted for the override text (the built-in fragments swap in no-prompts variants of themselves; your text is yours). One file overrides whichever profile is active — there is no plan-specific override.
|
|
812
|
+
|
|
813
|
+
A host-supplied custom profile **cannot** be overridden: it keeps its own `preset.system`, so there is no built-in doctrine for the file to replace and the file is never read. Hosts must not report such a file as the prompt's source — the TUI threads a `supportsOverride` flag out of `composeHostContext` for exactly this, and refuses to create an override on those profiles rather than writing one that could never apply.
|
|
814
|
+
|
|
815
|
+
`renderSystemMdSeed(staticPrompt)` builds the contents for a newly created override: an HTML-comment header explaining the semantics, followed by the doctrine that was in effect. Seeding with the real prompt makes creating the file a behavioral no-op until edited — an empty seed would strip ~1.2k tokens of doctrine the instant the file appeared, with no visible cause. Callers pass `splitSystemPrompt(system).static` so the per-turn `<env>` block never gets frozen into the file.
|
|
816
|
+
|
|
817
|
+
## Context segments
|
|
818
|
+
|
|
819
|
+
Where the breakdown above answers "how many tokens is each part of my context", **context segments** answer "what IS each part, and where did it come from" — the data behind the TUI's Settings → Instructions tab.
|
|
820
|
+
|
|
821
|
+
- **`agent.getContextSegments(opts?)`** returns `ContextSegment[] | null` from the same assembly the breakdown uses (live run snapshot, falling back to the pre-run assembly), so it's populated before the first message and `null` only after `destroy()`. No provider round-trip and no tool serialization — just the assembled strings.
|
|
822
|
+
- **`buildContextSegments(input)`** (`zidane/chat` + `zidane/chat/pure`, node-free) is the pure shaper. Each `ContextSegment` carries `{ id, label, origin, detail?, text, path?, tokens }`. The base "System prompt" segment is `baseSystem` minus `rulesBlock`, so instruction text is never reported twice; empty / whitespace-only pieces are dropped.
|
|
823
|
+
- **Origins are honest about openability.** `'file'` is the only origin that carries a `path`, and therefore the only one a UI can hand to an editor. `'built-in'` is compiled in (base doctrine, subagent defs), `'generated'` is assembled at prompt time (the skills catalog), `'mcp'` is served by a connected server.
|
|
824
|
+
- **`systemPath` promotes the base prompt to a file segment.** Pass it when a `SYSTEM.md` override produced the prompt and the "System prompt" segment flips from `'built-in'` to `'file'`, becoming openable. Host-supplied for the same reason as `rulesFiles`: the agent receives an already-composed system string with no provenance attached.
|
|
825
|
+
- **`rulesBlock` is what makes the de-dup work.** The base-prompt segment subtracts it so instruction text isn't reported twice (once inline, once as its own file rows). `getContextSegments` falls back to the assembly's copy, but that only exists when the host threaded `AgentRunOptions.contextSections` into `run()` — which the TUI does not — so a host that renders its own instruction block should pass it explicitly.
|
|
826
|
+
- **Instruction files are host-supplied.** Pass `opts.rulesFiles` — a host that already called `discoverAgentsMd` passes its `files` straight through, since `AgentsMdFile` is structurally compatible with `ContextSegmentFile`. The agent never reads those files itself (it receives the rendered block as an opaque substring), so without this it knows paths at best and never text.
|
|
827
|
+
- **Skill bodies are not here.** Activating a skill puts its body in the *conversation* as a tool result, not the system prompt; only the catalog blurbs are default context. `ContextBreakdown.activeSkills` reports which skills are live.
|
|
828
|
+
|
|
829
|
+
`ContextSegment`, `ContextSegmentFile`, `ContextSegmentOrigin`, `ContextSegmentsInput`, and `buildContextSegments` are exported from `zidane/chat` and `zidane/chat/pure` (types also from the root barrel).
|
|
830
|
+
|
|
790
831
|
## Edit-diff rendering
|
|
791
832
|
|
|
792
833
|
Renderer-agnostic helpers for turning `edit` / `multi_edit` / `write_file` tool calls into a unified diff. Two tiers:
|
package/docs/INTERACTIONS.md
CHANGED
|
@@ -88,11 +88,50 @@ In the question wizard (multi-question batches):
|
|
|
88
88
|
the whole selection, at least one pick required unless `required: false`)
|
|
89
89
|
- `shift+↹` — back one question (preserves your answers, pre-fills the
|
|
90
90
|
input with the prior value)
|
|
91
|
+
- `pagedown` / `pageup` — scroll the form down / up by one viewport, keeping a
|
|
92
|
+
1-row overlap so the line at the fold stays visible. The form's scrollbox is
|
|
93
|
+
deliberately not focusable (the option list and text inputs own the keyboard),
|
|
94
|
+
so these are how you read a long intro or peek at a later question without
|
|
95
|
+
reaching for the mouse. Advancing a question also scrolls it into view
|
|
96
|
+
automatically. The plan panel uses the same keys and the same page size.
|
|
97
|
+
|
|
98
|
+
Not `ctrl+d` / `ctrl+u`: OpenTUI's textarea bindings map those to
|
|
99
|
+
delete-to-line-start and delete, and a textarea question is focused whenever
|
|
100
|
+
this handler is, so they would destroy your typed answer as they scrolled.
|
|
101
|
+
While a form is up the transcript releases these keys, so one press moves one
|
|
102
|
+
surface.
|
|
91
103
|
- `esc` — leave the form
|
|
92
104
|
- Live (agent waiting): aborts the run
|
|
93
105
|
- Resumed (you reopened the session): leaves the form, the session
|
|
94
106
|
re-enqueues the same interaction on next activation
|
|
95
107
|
|
|
108
|
+
### Sizing
|
|
109
|
+
|
|
110
|
+
The wizard is content-sized inside a ceiling, and all three numbers come from
|
|
111
|
+
the terminal rather than from percentages of a parent:
|
|
112
|
+
|
|
113
|
+
- **Ceiling** — `wizardMaxRows(terminalRows)`, roughly 75% of the terminal,
|
|
114
|
+
never below 8 rows and never within 8 rows of the top so some transcript stays
|
|
115
|
+
visible. It used to be a `maxHeight: '60%'`, which resolves against the
|
|
116
|
+
parent's height; the wizard's parent is content-sized, so Yoga had nothing to
|
|
117
|
+
take 60% of and the cap silently never applied — the shell grew to whatever
|
|
118
|
+
its content estimate claimed and drew its border there, leaving questions
|
|
119
|
+
rendered below a closed frame.
|
|
120
|
+
- **Content height** — `introRows` + `estimateQuestionRowHeight` per question,
|
|
121
|
+
both **wrap-aware** (`wrappedRows`) and both given the panel's content width.
|
|
122
|
+
They previously counted `\n` only and ignored choice descriptions, so any
|
|
123
|
+
wrapped markdown intro or wrapped description under-sized the panel.
|
|
124
|
+
- **Floor** — the shell takes `minHeight` equal to the rows its content needs.
|
|
125
|
+
The shell is `flexShrink: 1` (it was `0`, which is why it could neither cap
|
|
126
|
+
nor shrink), and shrinkability alone let a `flexGrow` transcript sibling
|
|
127
|
+
squeeze the form to a fraction of its content; the floor makes the transcript
|
|
128
|
+
yield instead.
|
|
129
|
+
|
|
130
|
+
The height is also **monotonic**: answering a question collapses its row to a
|
|
131
|
+
one-line summary, which would otherwise shrink the panel mid-form. A high-water
|
|
132
|
+
mark keyed by request id means the panel settles at the largest size it has
|
|
133
|
+
needed and stays there.
|
|
134
|
+
|
|
96
135
|
In the plan picker:
|
|
97
136
|
|
|
98
137
|
- Decision picker → `revise` / `reject` → comment textarea
|
package/docs/RESTATE.md
CHANGED
|
@@ -699,6 +699,13 @@ const agent = createAgent({
|
|
|
699
699
|
- **`createSandboxContext`** re-attaches by handle id: its handle id IS the sandbox id, so a handle from a prior invocation routes operations to the same sandbox even though this instance never ran `spawn()`. Pair with the provider's reconnect option (`createDaytonaProvider({ sandboxId })`).
|
|
700
700
|
- **`createDaytonaProvider({ ownAttached: true })`** — by default an attached (`sandboxId`) sandbox is treated as externally owned and left running on `destroy()`. Set `ownAttached` when the *host* owns it across invocations so `destroy()` applies `onDestroy` (stop / archive / delete). Spawn-time failures still never delete a pre-existing attached sandbox.
|
|
701
701
|
- **`zidane/contexts/sandbox` subpath** — import `createSandboxContext` from here (not the `zidane/contexts` barrel) on workerd/edge: this entry's graph is node-free, so it doesn't drag `createProcessContext` → `node:child_process` into the bundle.
|
|
702
|
+
- **`@daytona/sdk` needs a root `tslib` override.** It is a peer dependency, so *you* install it — and it declares no `tslib` dependency of its own while reaching AWS SDK v3 through `@aws-sdk/client-s3`. A stale `@aws-crypto` v3/v4 floor (`tslib: ^1.11.1`) can win the hoist, and the AWS SDK's initializer helpers then resolve against tslib 1.x, which lacks them — `import('@daytona/sdk')` fails with `Export named '__runInitializers' not found`. npm/bun overrides are **root-only** and are not inherited from zidane, so add this to your own `package.json`:
|
|
703
|
+
|
|
704
|
+
```json
|
|
705
|
+
{ "overrides": { "tslib": "^2.8.1" } }
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
Safe because tslib 2 is a superset of the v1 helper set; the `^1.11.1` requirements are stale floors from packages AWS has since moved off. `createDaytonaProvider` reports the underlying load error (and names tslib) rather than claiming the package is missing.
|
|
702
709
|
|
|
703
710
|
### Streaming on fresh vs resumed invocations
|
|
704
711
|
|
package/docs/TUI.md
CHANGED
|
@@ -99,7 +99,7 @@ import {
|
|
|
99
99
|
| `Modal`, `ModalRoot`, `useModal`, `useModalAwareFocus` | Overlay layer. `Modal` owns its own esc-to-close handler. `useModalAwareFocus(preferred)` returns `false` while a modal is open so background inputs blur. **Single-slot:** `modal.open(node)` replaces the active node; modals do not stack. |
|
|
100
100
|
| `ModelPickerModal` | Cross-provider searchable model picker. Two commit modes: **single pick** (`onPick`) — ↵ returns `{ providerKey, modelId }`, used by the compaction-model picker; **ordered multi-select** (`onApplyChain`) — ↵ toggles the row in a numbered fallback chain (the number is the row's 1-based position; pick order = fallback order), esc applies. Entry 1 is the primary (provider swap, effort, options); entries 2+ become run-level fallback targets (`agent.run({ model: […] })`). Esc with an unchanged or emptied chain is a plain cancel. Cross-provider chains trigger a provider swap + session re-activation in the host; the footer model chip shows `+N` while fallbacks are configured. When a run actually falls back (`model:fallback`), the chip switches to the model NOW serving the run, warn-colored as the "temporary" cue, and a transcript info line records the switch; both snap back at the next run (fallback is run-sticky per run). |
|
|
101
101
|
| `EffortPickerModal` | Reasoning-effort picker. Lists `'off' / 'minimal' / 'low' / 'medium' / 'high'` plus `'adaptive'` when `supportsAdaptive` is true (Anthropic only). Surfaced via `ctrl+l` when `modelSupportsReasoning(descriptor, model)`. |
|
|
102
|
-
| `SettingsModal` | Tabbed: **Agent** (behaviour, persistence), **UI** (display, theming, animations), **Keybindings** (read-only catalog · `↵` opens `keybindings.json`), **Authentication** (current provider + detected providers · `↵` pops the AuthScreen), **Skills**, **MCP servers**. Category tab order is data-driven by `SETTINGS_CATEGORIES` from `zidane/chat`. Keybindings + Authentication tabs only appear when the host wires the `keybindings` / `authentication` data props; otherwise the legacy "open keybindings file" / "re-authenticate" rows render in the UI / Agent tabs as a fallback. One shared search input filters the active tab; `←/→` cycle tabs (preventDefault keeps the input cursor put), `↑/↓` navigate, `↵` toggle/cycle/pick/edit. MCP tab adds `ctrl+L` login, `ctrl+O` logout, and `esc` cancel-authorizing (Modal's esc-close still fires alongside). Sized at `maxWidth: 160, maxHeight: 50` — wider than the legacy modal so the Keybindings catalog + Authentication status block sit comfortably. |
|
|
102
|
+
| `SettingsModal` | Tabbed: **Agent** (behaviour, persistence), **UI** (display, theming, animations), **Keybindings** (read-only catalog · `↵` opens `keybindings.json`), **Authentication** (current provider + detected providers · `↵` pops the AuthScreen), **Instructions** (live default-context segments · `tab` reads inline, `↵` opens file-backed sources), **Skills**, **MCP servers**. Category tab order is data-driven by `SETTINGS_CATEGORIES` from `zidane/chat`. Keybindings + Authentication tabs only appear when the host wires the `keybindings` / `authentication` data props; otherwise the legacy "open keybindings file" / "re-authenticate" rows render in the UI / Agent tabs as a fallback. One shared search input filters the active tab; `←/→` cycle tabs (preventDefault keeps the input cursor put), `↑/↓` navigate, `↵` toggle/cycle/pick/edit. MCP tab adds `ctrl+L` login, `ctrl+O` logout, and `esc` cancel-authorizing (Modal's esc-close still fires alongside). Sized at `maxWidth: 160, maxHeight: 50` — wider than the legacy modal so the Keybindings catalog + Authentication status block sit comfortably. |
|
|
103
103
|
| `AgentPickerModal` | Multi-agent profile picker. Hidden when only one profile is registered. |
|
|
104
104
|
| `SkillsSettingsModal`, `McpsSettingsModal` | Standalone surfaces re-exported for embedders. The main `SettingsModal` integrates the same lists as tabs — prefer that for the in-app flow. `McpsSettingsModal` uses single-letter shortcuts (`l` / `o`); the integrated tab uses `ctrl+L` / `ctrl+O`. |
|
|
105
105
|
| `ToggleListModal` | Generic checkbox-list modal for `settings.enabledX` allowlists. Powers both the Skills + MCP server pickers; reuse with `keyOf` + `settingKey` + `renderDetail` to add a third toggle surface. |
|
|
@@ -378,6 +378,28 @@ Because top-level runs share `session.metadata.todos.session`, a list written in
|
|
|
378
378
|
|
|
379
379
|
The Settings modal is tabbed. The **Agent** and **UI** tabs each render three row kinds (toggle / choice / action) sourced from the chat-level tables, filtered by their `category` field. The **Keybindings** tab embeds the action catalog from `chat/keybindings.ts` (shared with the standalone `KeybindingsModal` via `groupBindings` + `KEYBINDING_KEY_COL_WIDTH`); `↵` opens the JSON file. The **Authentication** tab shows the active provider + a list of detected ones (from `detectAuth`); `↵` pops the full-screen `AuthScreen` (the multi-step wizard doesn't embed cleanly inside a modal). **Skills** and **MCP servers** render the discovered catalogs as enable/disable checklists.
|
|
380
380
|
|
|
381
|
+
The **Instructions** tab answers "what text is in my context, and where did it come from". Each row is a `ContextSegment` (`chat/context-segments`) — the base system prompt, each discovered `AGENTS.md` / `CLAUDE.md`, the skills catalog, the MCP instructions block, the subagent definitions — labelled with its origin (`built-in` / `file` / `generated` / `mcp server`) and heuristic token size. `tab` expands a row to read its text inline (clamped to `INSTRUCTION_PREVIEW_LINE_CAP` lines); `↵` opens the source in `$EDITOR`, but only for `origin: 'file'` rows — the other origins have no file, and the tab says so rather than no-op'ing. The shared search box matches each segment's full body, so it doubles as "which instruction source told the agent this?".
|
|
382
|
+
|
|
383
|
+
The data is split by who knows what: the agent supplies the segments it assembled via `agent.getContextSegments()`, while the host supplies the instruction files (path **and** text) it loaded at session activation — the agent only ever sees the rendered rules block as an opaque substring of the system prompt, so it can't attribute text back to a path. `App` holds those files in `sessionInstructionFilesRef`, captured alongside the prompt they were rendered into, so the tab reports what the session is actually running on rather than what happens to be on disk now. Because `SettingsModal` is opened with snapshot props, the fetch is an *action* (`actions.getContextSegments`) that the modal calls on every activation of the tab and whose loading / error state it owns; wiring that action is what makes the tab appear.
|
|
384
|
+
|
|
385
|
+
**`SYSTEM.md` override.** When a `SYSTEM.md` is active (see `docs/CHAT.md`), the System prompt row reports `origin: 'file'` and `↵` opens it like any other file row. When none exists, `↵` **creates** `~/.{prefix}/SYSTEM.md` seeded with the doctrine currently in effect and opens that — the one Instructions action that writes, so the footer hint reads `create SYSTEM.md override` rather than `open file`. Creation is idempotent (an existing file is opened, never clobbered) and the seed makes it a behavioral no-op until edited. A `SYSTEM.md` that exists on disk but isn't loaded — beaten on precedence, or found on a profile that ignores overrides — is reported in a note above the list, so editing an inactive file doesn't look ignored for no reason.
|
|
386
|
+
|
|
387
|
+
Two things the seed gets right, both of which were bugs worth naming:
|
|
388
|
+
|
|
389
|
+
- **It seeds from the DOCTRINE-ONLY prompt** (`composeHostContext().doctrineOnlySystem()`), not from the composed one. The composed prompt already contains the rendered `AGENTS.md` block in its static half, and an override composes *above* a freshly-appended copy of that block — so seeding from it would duplicate every instruction on the next session.
|
|
390
|
+
- **It refuses on profiles that can't be overridden.** `composeHostContext` returns `system: null` for any profile that isn't `build` or `plan`, which would seed a comment-only file. That file is still a *valid* override, so switching to `build` later would silently reduce the entire doctrine to an HTML comment. The create path throws instead, and the row explains that the profile supplies its own prompt.
|
|
391
|
+
|
|
392
|
+
`supportsOverride` also gates the hint and the row action, so a profile that ignores overrides never advertises a create action or claims a file is the prompt's source.
|
|
393
|
+
|
|
394
|
+
**`ctrl+r` reload.** Matches the refresh key on the Skills and MCPs tabs. Re-discovery is implicit — `composeHostContext` hits the filesystem on every call, so re-fetching picks up new files — and the real work is `onRefreshInstructions` rebuilding the live agent through the existing `rebuildActiveAgent()` so a running session adopts the new prompt without a TUI restart. While a run is in flight the rebuild is deferred onto `pendingAgentRebuildRef` and applied by `drainFollowUps` once the turn ends, because swapping the system prompt underneath a streaming turn is not safe. The row notice reports which happened (`agent rebuilt` vs `rebuild queued until the current run finishes`) since the consequences differ.
|
|
395
|
+
|
|
396
|
+
A mid-session rebuild costs more than it looks like it should, which is why this is an explicit keypress rather than a filesystem watcher:
|
|
397
|
+
|
|
398
|
+
- It replaces the system prompt on an existing conversation, guaranteeing a **prompt-cache miss** on the next turn, and the model sees different doctrine mid-conversation.
|
|
399
|
+
- `rebuildActiveAgent` destroys the stale agent, which **closes its MCP connections**. The fresh agent reconnects lazily on its next run, so the turn after a reload pays that reconnect. The notice says so (`MCP servers reconnect on the next turn`).
|
|
400
|
+
|
|
401
|
+
**No active session.** Settings opens on every screen but `auth`, so the tab is reachable from home / sessions / model-picker before `agentRef.current` exists. The action then composes a preview of the *next* session instead of reporting an empty context: `composeHostContext(profile, allowInteraction)` — the same helper `buildAgent` uses at activation, so the two can't drift — supplies the system prompt and the discovered instruction files, and `buildCatalog` over the enabled discovered skills supplies the catalog. The result carries `live: false` and the tab renders a note, because this view necessarily omits what the agent appends at run time (MCP instructions, extension system fragments, subagent definitions) and extension-contributed skills, which are only merged during `buildAgent`.
|
|
402
|
+
|
|
381
403
|
| Kind | Source | Activation |
|
|
382
404
|
|---|---|---|
|
|
383
405
|
| `toggle` | `SETTINGS_TOGGLES` (`BooleanSettingKey`) | `enter` flips the boolean |
|
|
@@ -389,6 +411,8 @@ The Settings modal is tabbed. The **Agent** and **UI** tabs each render three ro
|
|
|
389
411
|
|
|
390
412
|
`App` wires two general actions: `Keybindings` (open `keybindings.json` in `$EDITOR`) and `Authentication` (open `AuthScreen`). Authentication is gated on `!busy && !pendingApproval` so re-config can't tear down an in-flight run.
|
|
391
413
|
|
|
414
|
+
**Editor launching.** `launchEditor(path)` resolves `$VISUAL` / `$EDITOR`, falling back to the platform opener (`open` / `xdg-open` / `cmd /c start`). Those variables hold a command *line*, not a bare executable — `cursor --wait`, `code --wait`, `subl -w` — so `parseEditorCommand` (`src/tui/editor-command.ts`, quote-aware, no backslash escaping so Windows paths survive) splits the flags off and keeps them before the path. The promise resolves on the child's `'spawn'` event and rejects on `'error'`: `child_process.spawn` reports ENOENT through an event rather than a throw, so without that funnel a missing binary escaped every caller's `try`/`catch` as an unhandled emitter error and crashed into OpenTUI's error console. Callers therefore get a real rejection — the Instructions tab renders it as a row notice, the keybindings action logs it under `ZIDANE_DEBUG`.
|
|
415
|
+
|
|
392
416
|
A single shared search input filters the active tab's list (`label + description` for general items, `name + description` for skills, `name + transport + command/URL` for MCP servers). Typing resets the cursor on the active tab to 0; tab switches (`←/→`) preserve each tab's cursor (clamped against the current filtered size).
|
|
393
417
|
|
|
394
418
|
For the live source of truth, see `SETTINGS_TOGGLES` / `SETTINGS_CHOICES` in `src/chat/settings-context.tsx`. The current shape includes:
|
|
@@ -630,7 +654,9 @@ src/tui/
|
|
|
630
654
|
modal.tsx ModalRoot + Modal + useModalAwareFocus
|
|
631
655
|
model-picker.tsx Cross-provider searchable model list modal
|
|
632
656
|
effort-picker.tsx Reasoning-effort modal
|
|
633
|
-
settings-modal.tsx Tabbed settings panel (Agent / UI / Keybindings / Authentication / Skills / MCPs)
|
|
657
|
+
settings-modal.tsx Tabbed settings panel (Agent / UI / Keybindings / Authentication / Instructions / Skills / MCPs)
|
|
658
|
+
instruction-rows.ts Instructions-tab row model + preview clamping (pure)
|
|
659
|
+
editor-command.ts $EDITOR / $VISUAL command-line tokenizer (pure)
|
|
634
660
|
agent-picker.tsx Multi-agent profile modal (accentColor re-exported from zidane/chat)
|
|
635
661
|
skills-settings.tsx Skills list + toggle modal (standalone for embedders)
|
|
636
662
|
mcps-settings.tsx MCP servers list + toggle modal (standalone for embedders)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zidane",
|
|
3
|
-
"version": "6.5.
|
|
3
|
+
"version": "6.5.3",
|
|
4
4
|
"description": "an agent that goes straight to the goal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -267,15 +267,16 @@
|
|
|
267
267
|
"typecheck": "tsc --noEmit"
|
|
268
268
|
},
|
|
269
269
|
"dependencies": {
|
|
270
|
-
"@earendil-works/pi-ai": "0.
|
|
270
|
+
"@earendil-works/pi-ai": "0.84.2",
|
|
271
271
|
"chalk": "^5.6.2",
|
|
272
272
|
"fzf": "^0.5.2",
|
|
273
273
|
"hookable": "^6.1.1",
|
|
274
|
-
"marked": "18.0.
|
|
274
|
+
"marked": "18.0.9"
|
|
275
275
|
},
|
|
276
276
|
"overrides": {
|
|
277
|
-
"@types/react": "19.2.
|
|
278
|
-
"@types/react-dom": "19.2.3"
|
|
277
|
+
"@types/react": "19.2.18",
|
|
278
|
+
"@types/react-dom": "19.2.3",
|
|
279
|
+
"tslib": "^2.8.1"
|
|
279
280
|
},
|
|
280
281
|
"peerDependencies": {
|
|
281
282
|
"@anthropic-ai/bedrock-sdk": ">=0.32.0",
|
|
@@ -327,25 +328,25 @@
|
|
|
327
328
|
},
|
|
328
329
|
"devDependencies": {
|
|
329
330
|
"@antfu/eslint-config": "^8.2.0",
|
|
330
|
-
"@anthropic-ai/bedrock-sdk": "^0.32.
|
|
331
|
+
"@anthropic-ai/bedrock-sdk": "^0.32.4",
|
|
331
332
|
"@anthropic-ai/sdk": "^0.104.2",
|
|
332
333
|
"@daytona/sdk": "^0.187.0",
|
|
333
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
334
|
-
"@opentui/core": "^0.5.
|
|
335
|
-
"@opentui/core-darwin-x64": "^0.5.
|
|
336
|
-
"@opentui/core-linux-arm64": "^0.5.
|
|
337
|
-
"@opentui/core-linux-x64": "^0.5.
|
|
338
|
-
"@opentui/react": "^0.5.
|
|
339
|
-
"@restatedev/restate-sdk": "^1.
|
|
334
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
335
|
+
"@opentui/core": "^0.5.3",
|
|
336
|
+
"@opentui/core-darwin-x64": "^0.5.3",
|
|
337
|
+
"@opentui/core-linux-arm64": "^0.5.3",
|
|
338
|
+
"@opentui/core-linux-x64": "^0.5.3",
|
|
339
|
+
"@opentui/react": "^0.5.3",
|
|
340
|
+
"@restatedev/restate-sdk": "^1.16.5",
|
|
340
341
|
"@types/bun": "^1.3.13",
|
|
341
342
|
"@types/dockerode": "^4.0.1",
|
|
342
|
-
"@types/react": "19.2.
|
|
343
|
+
"@types/react": "19.2.18",
|
|
343
344
|
"bumpp": "^11.0.1",
|
|
344
345
|
"dockerode": "^5.0.0",
|
|
345
|
-
"e2b": "^2.
|
|
346
|
-
"eslint": "^10.
|
|
346
|
+
"e2b": "^2.39.0",
|
|
347
|
+
"eslint": "^10.8.1",
|
|
347
348
|
"jiti": "^2.6.1",
|
|
348
|
-
"react": "^19.2.
|
|
349
|
+
"react": "^19.2.8",
|
|
349
350
|
"tsdown": "^0.22.0",
|
|
350
351
|
"typescript": "~6.0.3",
|
|
351
352
|
"unrun": "^0.3.1"
|