@oh-my-pi/pi-coding-agent 15.9.0 → 15.9.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/CHANGELOG.md +67 -0
- package/dist/types/cli/dry-balance-cli.d.ts +104 -0
- package/dist/types/cli/update-cli.d.ts +15 -1
- package/dist/types/commands/dry-balance.d.ts +31 -0
- package/dist/types/config/append-only-context-mode.d.ts +8 -0
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +18 -0
- package/dist/types/config/settings-schema.d.ts +3 -3
- package/dist/types/config/settings.d.ts +11 -0
- package/dist/types/discovery/helpers.d.ts +1 -0
- package/dist/types/exa/mcp-client.d.ts +2 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -3
- package/dist/types/hindsight/bank.d.ts +17 -9
- package/dist/types/hindsight/mental-models.d.ts +1 -1
- package/dist/types/hindsight/state.d.ts +9 -3
- package/dist/types/mcp/json-rpc.d.ts +6 -1
- package/dist/types/mcp/manager.d.ts +1 -1
- package/dist/types/mcp/tool-bridge.d.ts +4 -0
- package/dist/types/mnemopi/state.d.ts +2 -2
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -0
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -0
- package/dist/types/modes/components/plugin-settings.d.ts +40 -8
- package/dist/types/modes/components/session-selector.d.ts +8 -3
- package/dist/types/modes/components/settings-selector.d.ts +1 -1
- package/dist/types/modes/components/transcript-container.d.ts +3 -2
- package/dist/types/modes/utils/keybinding-matchers.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +13 -1
- package/dist/types/session/auth-storage.d.ts +2 -2
- package/dist/types/session/history-storage.d.ts +3 -4
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +1 -0
- package/dist/types/slash-commands/types.d.ts +17 -4
- package/dist/types/task/types.d.ts +2 -0
- package/dist/types/tiny/text.d.ts +17 -0
- package/dist/types/tools/index.d.ts +16 -0
- package/dist/types/tools/path-utils.d.ts +11 -0
- package/dist/types/web/search/providers/base.d.ts +14 -0
- package/dist/types/web/search/providers/exa.d.ts +9 -0
- package/dist/types/web/search/providers/perplexity.d.ts +2 -2
- package/dist/types/web/search/types.d.ts +2 -1
- package/package.json +9 -9
- package/src/cli/dry-balance-cli.ts +823 -0
- package/src/cli/session-picker.ts +1 -0
- package/src/cli/update-cli.ts +54 -2
- package/src/cli-commands.ts +1 -0
- package/src/commands/completions.ts +1 -1
- package/src/commands/dry-balance.ts +43 -0
- package/src/config/append-only-context-mode.ts +37 -0
- package/src/config/model-registry.ts +6 -0
- package/src/config/models-config-schema.ts +3 -0
- package/src/config/settings-schema.ts +2 -2
- package/src/config/settings.ts +38 -0
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -0
- package/src/discovery/github.ts +37 -1
- package/src/discovery/helpers.ts +3 -1
- package/src/exa/mcp-client.ts +11 -5
- package/src/extensibility/plugins/legacy-pi-compat.ts +245 -25
- package/src/hindsight/backend.ts +184 -35
- package/src/hindsight/bank.ts +32 -22
- package/src/hindsight/mental-models.ts +1 -1
- package/src/hindsight/state.ts +21 -7
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/internal-urls/omp-protocol.ts +8 -2
- package/src/main.ts +4 -2
- package/src/mcp/json-rpc.ts +8 -0
- package/src/mcp/manager.ts +40 -21
- package/src/mcp/render.ts +3 -0
- package/src/mcp/tool-bridge.ts +10 -2
- package/src/mcp/transports/http.ts +33 -16
- package/src/mnemopi/state.ts +4 -4
- package/src/modes/acp/acp-agent.ts +168 -3
- package/src/modes/components/agent-dashboard.ts +103 -31
- package/src/modes/components/extensions/extension-dashboard.ts +56 -10
- package/src/modes/components/history-search.ts +128 -14
- package/src/modes/components/plugin-settings.ts +270 -36
- package/src/modes/components/session-selector.ts +45 -14
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/tips.txt +5 -1
- package/src/modes/components/transcript-container.ts +35 -6
- package/src/modes/components/tree-selector.ts +29 -2
- package/src/modes/controllers/command-controller.ts +4 -3
- package/src/modes/controllers/input-controller.ts +18 -7
- package/src/modes/controllers/selector-controller.ts +30 -19
- package/src/modes/interactive-mode.ts +38 -3
- package/src/modes/setup-wizard/scenes/sign-in.ts +27 -7
- package/src/modes/utils/keybinding-matchers.ts +10 -0
- package/src/prompts/agents/explore.md +1 -0
- package/src/prompts/agents/librarian.md +1 -0
- package/src/prompts/dry-balance-bench.md +8 -0
- package/src/prompts/steering/user-interjection.md +10 -0
- package/src/prompts/system/agent-creation-architect.md +1 -26
- package/src/prompts/system/system-prompt.md +143 -145
- package/src/prompts/system/title-system.md +3 -2
- package/src/prompts/tools/browser.md +29 -29
- package/src/prompts/tools/render-mermaid.md +2 -2
- package/src/sdk.ts +87 -30
- package/src/session/agent-session.ts +96 -14
- package/src/session/auth-storage.ts +4 -0
- package/src/session/history-storage.ts +11 -18
- package/src/session/messages.ts +80 -0
- package/src/session/session-manager.ts +7 -1
- package/src/slash-commands/types.ts +27 -10
- package/src/task/executor.ts +6 -2
- package/src/task/index.ts +8 -7
- package/src/task/types.ts +2 -0
- package/src/tiny/text.ts +112 -1
- package/src/tools/bash.ts +3 -4
- package/src/tools/index.ts +16 -0
- package/src/tools/job.ts +3 -3
- package/src/tools/memory-recall.ts +1 -1
- package/src/tools/memory-reflect.ts +3 -3
- package/src/tools/path-utils.ts +21 -0
- package/src/tools/search.ts +18 -1
- package/src/tools/ssh.ts +26 -10
- package/src/tools/write.ts +14 -2
- package/src/tui/status-line.ts +15 -4
- package/src/utils/file-mentions.ts +7 -107
- package/src/utils/title-generator.ts +66 -38
- package/src/web/search/index.ts +3 -1
- package/src/web/search/provider.ts +1 -1
- package/src/web/search/providers/base.ts +17 -0
- package/src/web/search/providers/exa.ts +111 -7
- package/src/web/search/providers/perplexity.ts +8 -4
- package/src/web/search/types.ts +2 -1
|
@@ -441,8 +441,35 @@ class TreeList implements Component {
|
|
|
441
441
|
const lines: string[] = [];
|
|
442
442
|
|
|
443
443
|
if (this.#filteredNodes.length === 0) {
|
|
444
|
-
|
|
445
|
-
|
|
444
|
+
// Three empty-state shapes:
|
|
445
|
+
// - flatNodes empty → no entries at all (truly fresh session).
|
|
446
|
+
// - search query rejects everything → tell the user the search is the cause.
|
|
447
|
+
// - filter mode rejects everything → tell the user the filter is the cause and
|
|
448
|
+
// how to widen it. Otherwise fresh sessions whose only persisted entries are
|
|
449
|
+
// `model_change` + `thinking_level_change` (both hidden by the default filter)
|
|
450
|
+
// read as "broken /tree" — see #1909.
|
|
451
|
+
if (this.#flatNodes.length === 0) {
|
|
452
|
+
lines.push(truncateToWidth(theme.fg("muted", " No entries found"), width));
|
|
453
|
+
lines.push(truncateToWidth(theme.fg("muted", ` (0/0)${this.#getFilterLabel()}`), width));
|
|
454
|
+
} else if (this.#searchQuery.length > 0) {
|
|
455
|
+
lines.push(truncateToWidth(theme.fg("muted", ` No entries match search "${this.#searchQuery}"`), width));
|
|
456
|
+
lines.push(truncateToWidth(theme.fg("muted", " Press Backspace to clear the search"), width));
|
|
457
|
+
lines.push(
|
|
458
|
+
truncateToWidth(theme.fg("muted", ` (0/${this.#flatNodes.length})${this.#getFilterLabel()}`), width),
|
|
459
|
+
);
|
|
460
|
+
} else {
|
|
461
|
+
const filterLabel = this.#getFilterLabel().trim() || "[default]";
|
|
462
|
+
lines.push(
|
|
463
|
+
truncateToWidth(
|
|
464
|
+
theme.fg("muted", ` ${this.#flatNodes.length} entries hidden by the current filter ${filterLabel}`),
|
|
465
|
+
width,
|
|
466
|
+
),
|
|
467
|
+
);
|
|
468
|
+
lines.push(truncateToWidth(theme.fg("muted", " Press Alt+A to show all, Alt+D for default"), width));
|
|
469
|
+
lines.push(
|
|
470
|
+
truncateToWidth(theme.fg("muted", ` (0/${this.#flatNodes.length})${this.#getFilterLabel()}`), width),
|
|
471
|
+
);
|
|
472
|
+
}
|
|
446
473
|
return lines;
|
|
447
474
|
}
|
|
448
475
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
14
14
|
import { formatDuration, Snowflake } from "@oh-my-pi/pi-utils";
|
|
15
15
|
import { $ } from "bun";
|
|
16
|
+
import { shouldEnableAppendOnlyContext } from "../../config/append-only-context-mode";
|
|
16
17
|
import { loadCustomShare } from "../../export/custom-share";
|
|
17
18
|
import type { CompactOptions } from "../../extensibility/extensions/types";
|
|
18
19
|
import {
|
|
@@ -397,10 +398,10 @@ export class CommandController {
|
|
|
397
398
|
// Append-only context
|
|
398
399
|
{
|
|
399
400
|
const setting = this.ctx.settings.get("provider.appendOnlyContext") ?? "auto";
|
|
400
|
-
const
|
|
401
|
-
const mode = setting
|
|
401
|
+
const model = this.ctx.session.model;
|
|
402
|
+
const mode = shouldEnableAppendOnlyContext(setting, model);
|
|
402
403
|
const activeLabel = mode ? theme.fg("success", "active") : theme.fg("dim", "inactive");
|
|
403
|
-
const settingLabel = setting === "auto" ? `${setting} (${provider ?? "?"})` : setting;
|
|
404
|
+
const settingLabel = setting === "auto" ? `${setting} (${model?.provider ?? "?"})` : setting;
|
|
404
405
|
info += `${theme.fg("dim", "Append-Only:")} ${activeLabel} (setting: ${settingLabel})\n`;
|
|
405
406
|
}
|
|
406
407
|
info += `${theme.bold("Tokens")}\n`;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
-
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
3
2
|
import type { AutocompleteProvider, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
4
|
-
import { $env, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import { $env, logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
5
4
|
import { getRoleInfo } from "../../config/model-registry";
|
|
6
5
|
import { isSettingsInitialized, settings } from "../../config/settings";
|
|
7
6
|
import { renderSegmentTrack } from "../../modes/components/segment-track";
|
|
@@ -13,6 +12,7 @@ import type { AgentSessionEvent } from "../../session/agent-session";
|
|
|
13
12
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
14
13
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
15
14
|
import { isTinyTitleLocalModelKey } from "../../tiny/models";
|
|
15
|
+
import { isLowSignalTitleInput } from "../../tiny/text";
|
|
16
16
|
import { tinyTitleClient } from "../../tiny/title-client";
|
|
17
17
|
import type { TinyTitleProgressEvent } from "../../tiny/title-protocol";
|
|
18
18
|
import { copyToClipboard, readImageFromClipboard, readTextFromClipboard } from "../../utils/clipboard";
|
|
@@ -376,9 +376,12 @@ export class InputController {
|
|
|
376
376
|
// First, move any pending bash components to chat
|
|
377
377
|
this.ctx.flushPendingBashComponents();
|
|
378
378
|
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
379
|
+
// Auto-generate a session title while the session is still unnamed.
|
|
380
|
+
// Greetings / acknowledgements / empty input carry no task, so they are
|
|
381
|
+
// skipped deterministically (no model invoked, no download-progress UI)
|
|
382
|
+
// and the session stays unnamed — the next user message gets a fresh
|
|
383
|
+
// chance, so titling defers past "hi" instead of latching onto it.
|
|
384
|
+
if (!this.ctx.sessionManager.getSessionName() && !$env.PI_NO_TITLE && !isLowSignalTitleInput(text)) {
|
|
382
385
|
this.#showTinyTitleDownloadProgress(this.ctx.settings.get("providers.tinyModel"));
|
|
383
386
|
const registry = this.ctx.session.modelRegistry;
|
|
384
387
|
generateSessionTitle(
|
|
@@ -390,7 +393,9 @@ export class InputController {
|
|
|
390
393
|
provider => this.ctx.session.agent.metadataForProvider(provider),
|
|
391
394
|
)
|
|
392
395
|
.then(async title => {
|
|
393
|
-
|
|
396
|
+
// Re-check: a concurrent attempt for an earlier message may have
|
|
397
|
+
// already named the session. Don't clobber it.
|
|
398
|
+
if (title && !this.ctx.sessionManager.getSessionName()) {
|
|
394
399
|
const applied = await this.ctx.sessionManager.setSessionName(title, "auto");
|
|
395
400
|
if (applied) {
|
|
396
401
|
setSessionTerminalTitle(
|
|
@@ -401,7 +406,13 @@ export class InputController {
|
|
|
401
406
|
}
|
|
402
407
|
}
|
|
403
408
|
})
|
|
404
|
-
.catch(
|
|
409
|
+
.catch(err => {
|
|
410
|
+
logger.warn("title-generator: uncaught auto-title error", {
|
|
411
|
+
sessionId: this.ctx.session.sessionId,
|
|
412
|
+
reason: "uncaught-auto-title-error",
|
|
413
|
+
error: err instanceof Error ? err.message : String(err),
|
|
414
|
+
});
|
|
415
|
+
});
|
|
405
416
|
}
|
|
406
417
|
|
|
407
418
|
if (this.ctx.onInputCallback) {
|
|
@@ -133,7 +133,11 @@ export class SelectorController {
|
|
|
133
133
|
const availableWidth = this.ctx.editor.getTopBorderAvailableWidth(this.ctx.ui.terminal.columns);
|
|
134
134
|
return this.ctx.statusLine.getTopBorder(availableWidth).content;
|
|
135
135
|
},
|
|
136
|
-
onPluginsChanged: () => {
|
|
136
|
+
onPluginsChanged: async () => {
|
|
137
|
+
const projectPath = await resolveActiveProjectRegistryPath(this.ctx.sessionManager.getCwd());
|
|
138
|
+
clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
|
|
139
|
+
await this.ctx.refreshSlashCommandState();
|
|
140
|
+
await this.ctx.session.refreshSshTool({ activateIfAvailable: true });
|
|
137
141
|
this.ctx.ui.requestRender();
|
|
138
142
|
},
|
|
139
143
|
onCancel: () => {
|
|
@@ -184,16 +188,19 @@ export class SelectorController {
|
|
|
184
188
|
*/
|
|
185
189
|
async showExtensionsDashboard(): Promise<void> {
|
|
186
190
|
const dashboard = await ExtensionDashboard.create(getProjectDir(), this.ctx.settings, this.ctx.ui.terminal.rows);
|
|
187
|
-
this.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
dashboard.onRequestRender = () => {
|
|
193
|
-
this.ctx.ui.requestRender();
|
|
194
|
-
};
|
|
195
|
-
return { component: dashboard, focus: dashboard };
|
|
191
|
+
const overlay = this.ctx.ui.showOverlay(dashboard, {
|
|
192
|
+
width: "100%",
|
|
193
|
+
maxHeight: "100%",
|
|
194
|
+
anchor: "top-left",
|
|
195
|
+
margin: 0,
|
|
196
196
|
});
|
|
197
|
+
dashboard.onClose = () => {
|
|
198
|
+
overlay.hide();
|
|
199
|
+
this.ctx.ui.requestRender();
|
|
200
|
+
};
|
|
201
|
+
dashboard.onRequestRender = () => {
|
|
202
|
+
this.ctx.ui.requestRender();
|
|
203
|
+
};
|
|
197
204
|
}
|
|
198
205
|
|
|
199
206
|
/**
|
|
@@ -208,16 +215,19 @@ export class SelectorController {
|
|
|
208
215
|
activeModelPattern,
|
|
209
216
|
defaultModelPattern,
|
|
210
217
|
});
|
|
211
|
-
this.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
dashboard.onRequestRender = () => {
|
|
217
|
-
this.ctx.ui.requestRender();
|
|
218
|
-
};
|
|
219
|
-
return { component: dashboard, focus: dashboard };
|
|
218
|
+
const overlay = this.ctx.ui.showOverlay(dashboard, {
|
|
219
|
+
width: "100%",
|
|
220
|
+
maxHeight: "100%",
|
|
221
|
+
anchor: "top-left",
|
|
222
|
+
margin: 0,
|
|
220
223
|
});
|
|
224
|
+
dashboard.onClose = () => {
|
|
225
|
+
overlay.hide();
|
|
226
|
+
this.ctx.ui.requestRender();
|
|
227
|
+
};
|
|
228
|
+
dashboard.onRequestRender = () => {
|
|
229
|
+
this.ctx.ui.requestRender();
|
|
230
|
+
};
|
|
221
231
|
}
|
|
222
232
|
|
|
223
233
|
/**
|
|
@@ -763,6 +773,7 @@ export class SelectorController {
|
|
|
763
773
|
loadAllSessions: () => SessionManager.listAll(),
|
|
764
774
|
allSessions,
|
|
765
775
|
startInAllScope,
|
|
776
|
+
getTerminalRows: () => this.ctx.ui.terminal.rows,
|
|
766
777
|
},
|
|
767
778
|
);
|
|
768
779
|
selector.setOnRequestRender(() => this.ctx.ui.requestRender());
|
|
@@ -607,7 +607,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
607
607
|
await this.initHooksAndCustomTools();
|
|
608
608
|
|
|
609
609
|
// Restore mode from session (e.g. plan mode on resume)
|
|
610
|
-
|
|
610
|
+
this.session.setSessionSwitchReconciler?.(() => this.#reconcileModeFromSession());
|
|
611
|
+
await this.#reconcileModeFromSession();
|
|
611
612
|
|
|
612
613
|
// Restore unsent editor draft from previous session shutdown (Ctrl+D).
|
|
613
614
|
// One-shot: consumeDraft removes the sidecar after read so the next
|
|
@@ -1370,8 +1371,42 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1370
1371
|
}
|
|
1371
1372
|
}
|
|
1372
1373
|
|
|
1373
|
-
|
|
1374
|
-
|
|
1374
|
+
async #clearTransientModeState(): Promise<void> {
|
|
1375
|
+
if (this.planModeEnabled || this.planModePaused) {
|
|
1376
|
+
if (this.#planModePreviousTools !== undefined) {
|
|
1377
|
+
await this.session.setActiveToolsByName(this.#planModePreviousTools);
|
|
1378
|
+
}
|
|
1379
|
+
this.session.setStandingResolveHandler?.(null);
|
|
1380
|
+
this.session.setPlanModeState(undefined);
|
|
1381
|
+
this.planModeEnabled = false;
|
|
1382
|
+
this.planModePaused = false;
|
|
1383
|
+
this.planModePlanFilePath = undefined;
|
|
1384
|
+
this.#planModePreviousTools = undefined;
|
|
1385
|
+
this.#planModePreviousModelState = undefined;
|
|
1386
|
+
this.#pendingModelSwitch = undefined;
|
|
1387
|
+
this.#planModeHasEntered = false;
|
|
1388
|
+
this.#updatePlanModeStatus();
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
if (this.goalModeEnabled || this.goalModePaused) {
|
|
1392
|
+
if (this.#goalModePreviousTools !== undefined) {
|
|
1393
|
+
await this.session.setActiveToolsByName(this.#goalModePreviousTools);
|
|
1394
|
+
}
|
|
1395
|
+
this.session.setGoalModeState(undefined);
|
|
1396
|
+
this.goalModeEnabled = false;
|
|
1397
|
+
this.goalModePaused = false;
|
|
1398
|
+
this.#goalModePreviousTools = undefined;
|
|
1399
|
+
this.#goalTurnHadToolCalls = false;
|
|
1400
|
+
this.#goalContinuationTurnInFlight = false;
|
|
1401
|
+
this.#goalSuppressNextContinuation = false;
|
|
1402
|
+
this.#cancelGoalContinuation();
|
|
1403
|
+
this.#updateGoalModeStatus();
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/** Reconcile mode state from session entries on resume/switch. */
|
|
1408
|
+
async #reconcileModeFromSession(): Promise<void> {
|
|
1409
|
+
await this.#clearTransientModeState();
|
|
1375
1410
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1376
1411
|
const goalEnabled = this.session.settings.get("goal.enabled");
|
|
1377
1412
|
if (!goalEnabled && (sessionContext.mode === "goal" || sessionContext.mode === "goal_paused")) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import type { OAuthProvider } from "@oh-my-pi/pi-ai/utils/oauth/types";
|
|
3
|
-
import { Input, matchesKey,
|
|
3
|
+
import { Input, matchesKey, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { getAgentDbPath } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { OAuthSelectorComponent } from "../../components/oauth-selector";
|
|
6
6
|
import { theme } from "../../theme/theme";
|
|
@@ -15,6 +15,10 @@ const CALLBACK_SERVER_PROVIDERS: Partial<Record<OAuthProvider, true>> = {
|
|
|
15
15
|
"google-antigravity": true,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
function loginUrlLink(url: string): string {
|
|
19
|
+
return `\x1b]8;;${url}\x07Open login URL\x1b]8;;\x07`;
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
interface PromptState {
|
|
19
23
|
message: string;
|
|
20
24
|
placeholder?: string;
|
|
@@ -33,6 +37,7 @@ export class SignInTab implements SetupTab {
|
|
|
33
37
|
#authStorage: AuthStorage;
|
|
34
38
|
#selector: OAuthSelectorComponent;
|
|
35
39
|
#statusLines: string[] = [];
|
|
40
|
+
#authUrl: string | undefined;
|
|
36
41
|
#prompt: PromptState | undefined;
|
|
37
42
|
#promptResolve: ((value: string) => void) | undefined;
|
|
38
43
|
#loginAbort: AbortController | undefined;
|
|
@@ -72,22 +77,31 @@ export class SignInTab implements SetupTab {
|
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
render(width: number): string[] {
|
|
75
|
-
const lines = [
|
|
80
|
+
const lines: string[] = [];
|
|
76
81
|
if (this.#loggingInProvider) {
|
|
77
|
-
lines.push(theme.bold(`Signing in to ${this.#loggingInProvider}`)
|
|
82
|
+
lines.push(theme.bold(`Signing in to ${this.#loggingInProvider}`));
|
|
78
83
|
} else {
|
|
84
|
+
lines.push(theme.fg("muted", "Pick a provider to sign in — you can connect more than one."), "");
|
|
79
85
|
lines.push(...this.#selector.render(width));
|
|
80
86
|
}
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
|
|
88
|
+
const urlLines = this.#authUrl ? wrapTextWithAnsi(theme.fg("dim", this.#authUrl), width) : [];
|
|
89
|
+
if (this.#authUrl) {
|
|
90
|
+
lines.push(theme.fg("accent", `Browser login: ${loginUrlLink(this.#authUrl)}`), ...urlLines.slice(0, 2));
|
|
83
91
|
}
|
|
84
92
|
if (this.#prompt) {
|
|
85
|
-
lines.push(
|
|
93
|
+
lines.push(theme.fg("warning", this.#prompt.message));
|
|
86
94
|
if (this.#prompt.placeholder) {
|
|
87
95
|
lines.push(theme.fg("dim", this.#prompt.placeholder));
|
|
88
96
|
}
|
|
89
97
|
lines.push(this.#prompt.input.render(width)[0] ?? "");
|
|
90
98
|
}
|
|
99
|
+
if (urlLines.length > 2) {
|
|
100
|
+
lines.push(...urlLines);
|
|
101
|
+
}
|
|
102
|
+
if (this.#statusLines.length > 0) {
|
|
103
|
+
lines.push(...this.#statusLines.flatMap(line => wrapTextWithAnsi(line, width)));
|
|
104
|
+
}
|
|
91
105
|
return lines;
|
|
92
106
|
}
|
|
93
107
|
|
|
@@ -109,6 +123,7 @@ export class SignInTab implements SetupTab {
|
|
|
109
123
|
this.#selector.stopValidation();
|
|
110
124
|
this.#loggingInProvider = providerId;
|
|
111
125
|
this.#statusLines = [theme.fg("dim", "Starting OAuth flow…")];
|
|
126
|
+
this.#authUrl = undefined;
|
|
112
127
|
this.#loginAbort = new AbortController();
|
|
113
128
|
this.host.restoreFocus();
|
|
114
129
|
this.host.requestRender();
|
|
@@ -116,7 +131,8 @@ export class SignInTab implements SetupTab {
|
|
|
116
131
|
await this.#authStorage.login(providerId as OAuthProvider, {
|
|
117
132
|
signal: this.#loginAbort.signal,
|
|
118
133
|
onAuth: info => {
|
|
119
|
-
this.#
|
|
134
|
+
this.#authUrl = info.url;
|
|
135
|
+
this.#statusLines = [];
|
|
120
136
|
if (info.instructions) {
|
|
121
137
|
this.#statusLines.push(theme.fg("warning", info.instructions));
|
|
122
138
|
}
|
|
@@ -140,6 +156,7 @@ export class SignInTab implements SetupTab {
|
|
|
140
156
|
theme.fg("success", `${theme.status.success} Signed in to ${providerId}`),
|
|
141
157
|
theme.fg("dim", `Credentials saved to ${getAgentDbPath()}`),
|
|
142
158
|
];
|
|
159
|
+
this.#authUrl = undefined;
|
|
143
160
|
this.#loggingInProvider = undefined;
|
|
144
161
|
this.#loginAbort = undefined;
|
|
145
162
|
this.#selector.stopValidation();
|
|
@@ -150,12 +167,14 @@ export class SignInTab implements SetupTab {
|
|
|
150
167
|
if (this.#disposed) return;
|
|
151
168
|
if (this.#loginAbort?.signal.aborted) {
|
|
152
169
|
this.#statusLines = [theme.fg("dim", "Login cancelled.")];
|
|
170
|
+
this.#authUrl = undefined;
|
|
153
171
|
} else {
|
|
154
172
|
const message = error instanceof Error ? error.message : String(error);
|
|
155
173
|
this.#statusLines = [
|
|
156
174
|
theme.fg("error", `Login failed: ${message}`),
|
|
157
175
|
theme.fg("dim", "Choose another provider or press Esc to continue."),
|
|
158
176
|
];
|
|
177
|
+
this.#authUrl = undefined;
|
|
159
178
|
}
|
|
160
179
|
this.#loggingInProvider = undefined;
|
|
161
180
|
this.#loginAbort = undefined;
|
|
@@ -174,6 +193,7 @@ export class SignInTab implements SetupTab {
|
|
|
174
193
|
this.#resolvePrompt(value);
|
|
175
194
|
};
|
|
176
195
|
input.onEscape = () => {
|
|
196
|
+
this.#loginAbort?.abort();
|
|
177
197
|
this.#resolvePrompt("");
|
|
178
198
|
};
|
|
179
199
|
this.host.setFocus(input);
|
|
@@ -31,6 +31,16 @@ export function matchesSelectDown(data: string): boolean {
|
|
|
31
31
|
return getKeybindings().matches(data, "tui.select.down");
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** Match the generic selector page-up keybinding. */
|
|
35
|
+
export function matchesSelectPageUp(data: string): boolean {
|
|
36
|
+
return getKeybindings().matches(data, "tui.select.pageUp");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Match the generic selector page-down keybinding. */
|
|
40
|
+
export function matchesSelectPageDown(data: string): boolean {
|
|
41
|
+
return getKeybindings().matches(data, "tui.select.pageDown");
|
|
42
|
+
}
|
|
43
|
+
|
|
34
44
|
export function matchesAppExternalEditor(data: string): boolean {
|
|
35
45
|
const keybindings = getKeybindings();
|
|
36
46
|
const externalEditorKeys = keybindings.getKeys("app.editor.external");
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Write a 20-line poem about balancing OAuth accounts across many providers.
|
|
2
|
+
|
|
3
|
+
Form:
|
|
4
|
+
- Exactly 20 lines, no title, no stanza breaks.
|
|
5
|
+
- Each line is terse and image-driven, in the spirit of haiku: 7 words or fewer, no end punctuation.
|
|
6
|
+
- Let the imagery carry the theme — tokens, scopes, refresh cycles, expiry, consent, revocation — rather than naming them literally.
|
|
7
|
+
|
|
8
|
+
Output only the 20 lines. No preamble, no commentary, no code fences.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<user_interjection>
|
|
2
|
+
The user sent this message while you were working on the current task. It takes
|
|
3
|
+
priority and supersedes your earlier plan wherever they conflict. Stop work that no
|
|
4
|
+
longer matches their intent, re-read the request below, and adjust what you are doing
|
|
5
|
+
now.
|
|
6
|
+
|
|
7
|
+
<message>
|
|
8
|
+
{{message}}
|
|
9
|
+
</message>
|
|
10
|
+
</user_interjection>
|
|
@@ -28,38 +28,13 @@ When a user describes what they want an agent to do:
|
|
|
28
28
|
- MUST clearly indicate the agent's primary function
|
|
29
29
|
- SHOULD be memorable and easy to type
|
|
30
30
|
- NEVER use generic terms like "helper" or "assistant"
|
|
31
|
-
6. Example agent descriptions
|
|
32
|
-
- In the `whenToUse` field, SHOULD include examples of when this agent SHOULD be used
|
|
33
|
-
- Format examples as:
|
|
34
|
-
```
|
|
35
|
-
<example>
|
|
36
|
-
Context: The user is creating a test-runner agent that should be called after a logical chunk of code is written.
|
|
37
|
-
user: "Please write a function that checks if a number is prime"
|
|
38
|
-
assistant: "Here is the relevant function: "
|
|
39
|
-
<function call omitted for brevity only for this example>
|
|
40
|
-
<commentary>
|
|
41
|
-
Since a significant piece of code was written, use the {{TASK_TOOL_NAME}} tool to launch the test-runner agent to run the tests.
|
|
42
|
-
</commentary>
|
|
43
|
-
assistant: "Now let me use the test-runner agent to run the tests"
|
|
44
|
-
</example>
|
|
45
|
-
<example>
|
|
46
|
-
Context: User is creating an agent to respond to the word "hello" with a friendly joke.
|
|
47
|
-
user: "Hello"
|
|
48
|
-
assistant: "I'm going to use the {{TASK_TOOL_NAME}} tool to launch the greeting-responder agent to respond with a friendly joke"
|
|
49
|
-
<commentary>
|
|
50
|
-
Since the user is greeting, use the greeting-responder agent to respond with a friendly joke.
|
|
51
|
-
</commentary>
|
|
52
|
-
</example>
|
|
53
|
-
```
|
|
54
|
-
- If the user mentioned or implied proactive use, SHOULD include proactive examples
|
|
55
|
-
- MUST ensure examples show the assistant using the Agent tool, not responding directly
|
|
56
31
|
|
|
57
32
|
Your output MUST be a valid JSON object with exactly these fields:
|
|
58
33
|
|
|
59
34
|
```json
|
|
60
35
|
{
|
|
61
36
|
"identifier": "A unique, descriptive identifier using lowercase letters, numbers, and hyphens (e.g., 'test-runner', 'api-docs-writer', 'code-formatter')",
|
|
62
|
-
"whenToUse": "A precise,
|
|
37
|
+
"whenToUse": "A precise, single-sentence trigger description starting with 'Use this agent when…' that defines the conditions and use cases. Keep it concise and self-contained — NEVER embed <example>/<commentary> blocks, multi-turn transcripts, or escaped newlines.",
|
|
63
38
|
"systemPrompt": "The complete system prompt that will govern the agent's behavior, written in second person ('You are…', 'You will…') and structured for maximum clarity and effectiveness"
|
|
64
39
|
}
|
|
65
40
|
```
|