@oh-my-pi/pi-coding-agent 15.13.3 → 16.0.1
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 +155 -133
- package/dist/cli.js +621 -530
- package/dist/types/advisor/__tests__/advisor.test.d.ts +1 -0
- package/dist/types/advisor/advise-tool.d.ts +58 -0
- package/dist/types/advisor/index.d.ts +3 -0
- package/dist/types/advisor/runtime.d.ts +52 -0
- package/dist/types/advisor/watchdog.d.ts +5 -0
- package/dist/types/config/model-roles.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +66 -5
- package/dist/types/discovery/helpers.d.ts +7 -0
- package/dist/types/eval/__tests__/prelude-agent.test.d.ts +1 -0
- package/dist/types/extensibility/plugins/runtime-config.d.ts +3 -0
- package/dist/types/modes/components/advisor-message.d.ts +9 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/controllers/command-controller.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/types.d.ts +8 -1
- package/dist/types/sdk.d.ts +3 -3
- package/dist/types/session/agent-session.d.ts +81 -2
- package/dist/types/session/session-history-format.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +4 -1
- package/dist/types/session/yield-queue.d.ts +2 -0
- package/dist/types/task/index.d.ts +21 -0
- package/dist/types/tools/github-cache.d.ts +5 -4
- package/dist/types/tools/job.d.ts +1 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/report-tool-issue.d.ts +0 -1
- package/dist/types/web/search/index.d.ts +2 -2
- package/dist/types/web/search/provider.d.ts +2 -0
- package/package.json +13 -13
- package/src/advisor/__tests__/advisor.test.ts +586 -0
- package/src/advisor/advise-tool.ts +87 -0
- package/src/advisor/index.ts +3 -0
- package/src/advisor/runtime.ts +248 -0
- package/src/advisor/watchdog.ts +83 -0
- package/src/cli/args.ts +1 -0
- package/src/collab/host.ts +1 -1
- package/src/config/model-roles.ts +13 -1
- package/src/config/settings-schema.ts +65 -6
- package/src/discovery/claude-plugins.ts +3 -42
- package/src/discovery/github.ts +101 -6
- package/src/discovery/helpers.ts +11 -0
- package/src/eval/__tests__/prelude-agent.test.ts +73 -0
- package/src/eval/js/shared/prelude.txt +12 -3
- package/src/eval/py/prelude.py +26 -2
- package/src/extensibility/custom-commands/bundled/review/index.ts +289 -80
- package/src/extensibility/plugins/loader.ts +3 -2
- package/src/extensibility/plugins/manager.ts +4 -3
- package/src/extensibility/plugins/marketplace/fetcher.ts +32 -34
- package/src/extensibility/plugins/runtime-config.ts +9 -0
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/internal-urls/issue-pr-protocol.ts +8 -4
- package/src/main.ts +9 -1
- package/src/modes/acp/acp-agent.ts +3 -3
- package/src/modes/components/advisor-message.ts +99 -0
- package/src/modes/components/agent-hub.ts +7 -0
- package/src/modes/components/assistant-message.ts +86 -0
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/status-line/segments.ts +20 -7
- package/src/modes/components/tips.txt +1 -1
- package/src/modes/controllers/command-controller.ts +69 -2
- package/src/modes/controllers/extension-ui-controller.ts +4 -3
- package/src/modes/controllers/input-controller.ts +1 -0
- package/src/modes/controllers/selector-controller.ts +7 -0
- package/src/modes/interactive-mode.ts +59 -2
- package/src/modes/rpc/rpc-mode.ts +3 -3
- package/src/modes/runtime-init.ts +2 -1
- package/src/modes/types.ts +8 -1
- package/src/modes/utils/ui-helpers.ts +9 -0
- package/src/prompts/advisor/advise-tool.md +1 -0
- package/src/prompts/advisor/system.md +31 -0
- package/src/prompts/agents/designer.md +8 -0
- package/src/prompts/review-request.md +1 -1
- package/src/prompts/system/subagent-system-prompt.md +4 -1
- package/src/prompts/tools/eval.md +13 -3
- package/src/prompts/tools/irc.md +1 -1
- package/src/sdk.ts +61 -14
- package/src/session/agent-session.ts +667 -13
- package/src/session/session-dump-format.ts +15 -131
- package/src/session/session-history-format.ts +30 -11
- package/src/session/session-manager.ts +3 -1
- package/src/session/yield-queue.ts +5 -1
- package/src/slash-commands/builtin-registry.ts +105 -4
- package/src/system-prompt.ts +1 -1
- package/src/task/executor.ts +5 -4
- package/src/task/index.ts +70 -9
- package/src/tools/github-cache.ts +32 -7
- package/src/tools/job.ts +14 -1
- package/src/tools/path-utils.ts +33 -2
- package/src/tools/report-tool-issue.ts +2 -7
- package/src/web/scrapers/docs-rs.ts +2 -3
- package/src/web/search/index.ts +2 -2
- package/src/web/search/provider.ts +14 -2
|
@@ -92,6 +92,7 @@ import { STTController, type SttState } from "../stt";
|
|
|
92
92
|
import { discoverTitleSystemPromptFile, resolvePromptInput } from "../system-prompt";
|
|
93
93
|
import { formatTaskId } from "../task/render";
|
|
94
94
|
import type { LspStartupServerInfo } from "../tools";
|
|
95
|
+
import { isImageProviderPreference, setPreferredImageProvider } from "../tools/image-gen";
|
|
95
96
|
import { normalizeLocalScheme } from "../tools/path-utils";
|
|
96
97
|
import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../tools/render-utils";
|
|
97
98
|
import { setAutoQaConsentHandler } from "../tools/report-tool-issue";
|
|
@@ -103,6 +104,12 @@ import type { EventBus } from "../utils/event-bus";
|
|
|
103
104
|
import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
104
105
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
105
106
|
import { popTerminalTitle, pushTerminalTitle, setSessionTerminalTitle } from "../utils/title-generator";
|
|
107
|
+
import {
|
|
108
|
+
isSearchProviderId,
|
|
109
|
+
isSearchProviderPreference,
|
|
110
|
+
setExcludedSearchProviders,
|
|
111
|
+
setPreferredSearchProvider,
|
|
112
|
+
} from "../web/search";
|
|
106
113
|
import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
107
114
|
import type { BashExecutionComponent } from "./components/bash-execution";
|
|
108
115
|
import { ChatBlock, type ChatBlockHost } from "./components/chat-block";
|
|
@@ -542,6 +549,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
542
549
|
if (eventBus) {
|
|
543
550
|
this.#eventBusUnsubscribers.push(
|
|
544
551
|
eventBus.on(LSP_STARTUP_EVENT_CHANNEL, data => {
|
|
552
|
+
if (this.settings.get("startup.quiet")) return;
|
|
545
553
|
this.#handleLspStartupEvent(data as LspStartupEvent);
|
|
546
554
|
}),
|
|
547
555
|
);
|
|
@@ -551,6 +559,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
551
559
|
logger.warn("Ignoring malformed mcp:connecting event", { data });
|
|
552
560
|
return;
|
|
553
561
|
}
|
|
562
|
+
if (this.settings.get("startup.quiet")) return;
|
|
554
563
|
this.showStatus(formatMCPConnectingMessage(data.serverNames));
|
|
555
564
|
}),
|
|
556
565
|
);
|
|
@@ -788,6 +797,30 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
788
797
|
this.session.setSessionSwitchReconciler?.(() => this.#reconcileModeFromSession());
|
|
789
798
|
await this.#reconcileModeFromSession();
|
|
790
799
|
|
|
800
|
+
// Brand-new sessions optionally start in plan mode when the user has made it
|
|
801
|
+
// the startup default. "Brand-new" means the resolved branch carries no
|
|
802
|
+
// conversation context (buildSessionContext().messages — covers messages,
|
|
803
|
+
// custom messages, branch summaries, and compaction summaries) and the user
|
|
804
|
+
// set no explicit `mode_change` (which #reconcileModeFromSession just
|
|
805
|
+
// restored). SDK startup metadata and extension `custom` state entries are
|
|
806
|
+
// ignored. This way `omp --continue` (or auto-resume) that finds no recent
|
|
807
|
+
// session and creates a fresh one still honors the default, while a session
|
|
808
|
+
// with restored context or an explicit mode keeps its reconciled mode. Scoped
|
|
809
|
+
// to launch (not the switch reconciler above) so /new and the plan-approval →
|
|
810
|
+
// execution handoff clear never get dragged back into plan mode. #enterPlanMode
|
|
811
|
+
// is idempotent and self-guards against an already-active plan/goal mode; it
|
|
812
|
+
// does not check plan.enabled itself.
|
|
813
|
+
const hasConversationContext = this.sessionManager.buildSessionContext().messages.length > 0;
|
|
814
|
+
const hasExplicitMode = this.sessionManager.getEntries().some(entry => entry.type === "mode_change");
|
|
815
|
+
const isFreshSession = !hasConversationContext && !hasExplicitMode;
|
|
816
|
+
if (
|
|
817
|
+
isFreshSession &&
|
|
818
|
+
this.session.settings.get("plan.defaultOnStartup") &&
|
|
819
|
+
this.session.settings.get("plan.enabled")
|
|
820
|
+
) {
|
|
821
|
+
await this.#enterPlanMode();
|
|
822
|
+
}
|
|
823
|
+
|
|
791
824
|
// Restore unsent editor draft from previous session shutdown (Ctrl+D).
|
|
792
825
|
// One-shot: consumeDraft removes the sidecar after read so the next
|
|
793
826
|
// resume does not re-restore the same text.
|
|
@@ -902,6 +935,22 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
902
935
|
// up the destination project's configuration.
|
|
903
936
|
if (isSettingsInitialized()) {
|
|
904
937
|
await settings.reloadForCwd(newCwd);
|
|
938
|
+
// Reapply provider preferences from the newly-loaded settings so the
|
|
939
|
+
// module-level search/image provider state reflects the destination
|
|
940
|
+
// project's configuration. Without this, the previous project's
|
|
941
|
+
// exclusions leak and newly-excluded providers are still used.
|
|
942
|
+
const excludedWebSearchProviders = settings.get("providers.webSearchExclude");
|
|
943
|
+
if (Array.isArray(excludedWebSearchProviders)) {
|
|
944
|
+
setExcludedSearchProviders(excludedWebSearchProviders.filter(isSearchProviderId));
|
|
945
|
+
}
|
|
946
|
+
const webSearchProvider = settings.get("providers.webSearch");
|
|
947
|
+
if (typeof webSearchProvider === "string" && isSearchProviderPreference(webSearchProvider)) {
|
|
948
|
+
setPreferredSearchProvider(webSearchProvider);
|
|
949
|
+
}
|
|
950
|
+
const imageProvider = settings.get("providers.image");
|
|
951
|
+
if (isImageProviderPreference(imageProvider)) {
|
|
952
|
+
setPreferredImageProvider(imageProvider);
|
|
953
|
+
}
|
|
905
954
|
}
|
|
906
955
|
// Re-warm plugin roots, capabilities, slash commands, and the ssh tool so
|
|
907
956
|
// the next prompt sees everything scoped to the new project directory.
|
|
@@ -3333,8 +3382,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3333
3382
|
return this.#commandController.handleExportCommand(text);
|
|
3334
3383
|
}
|
|
3335
3384
|
|
|
3336
|
-
handleDumpCommand() {
|
|
3337
|
-
return this.#commandController.handleDumpCommand();
|
|
3385
|
+
handleDumpCommand(isRaw?: boolean) {
|
|
3386
|
+
return this.#commandController.handleDumpCommand(isRaw);
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
handleAdvisorDumpCommand(isRaw?: boolean) {
|
|
3390
|
+
return this.#commandController.handleAdvisorDumpCommand(isRaw);
|
|
3338
3391
|
}
|
|
3339
3392
|
|
|
3340
3393
|
handleDebugTranscriptCommand(): Promise<void> {
|
|
@@ -3353,6 +3406,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3353
3406
|
return this.#commandController.handleSessionCommand();
|
|
3354
3407
|
}
|
|
3355
3408
|
|
|
3409
|
+
handleAdvisorStatusCommand(): Promise<void> {
|
|
3410
|
+
return this.#commandController.handleAdvisorStatusCommand();
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3356
3413
|
handleJobsCommand(): Promise<void> {
|
|
3357
3414
|
return this.#commandController.handleJobsCommand();
|
|
3358
3415
|
}
|
|
@@ -26,7 +26,7 @@ import { buildSkillPromptMessage } from "../../extensibility/skills";
|
|
|
26
26
|
import { loadSlashCommands } from "../../extensibility/slash-commands";
|
|
27
27
|
import { type Theme, theme } from "../../modes/theme/theme";
|
|
28
28
|
import type { AgentSession } from "../../session/agent-session";
|
|
29
|
-
import { SKILL_PROMPT_MESSAGE_TYPE } from "../../session/messages";
|
|
29
|
+
import { SKILL_PROMPT_MESSAGE_TYPE, USER_INTERRUPT_LABEL } from "../../session/messages";
|
|
30
30
|
import { executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins";
|
|
31
31
|
import { buildAvailableSlashCommands } from "../../slash-commands/available-commands";
|
|
32
32
|
import type { EventBus } from "../../utils/event-bus";
|
|
@@ -751,12 +751,12 @@ export async function runRpcMode(
|
|
|
751
751
|
}
|
|
752
752
|
|
|
753
753
|
case "abort": {
|
|
754
|
-
await session.abort();
|
|
754
|
+
await session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
755
755
|
return success(id, "abort");
|
|
756
756
|
}
|
|
757
757
|
|
|
758
758
|
case "abort_and_prompt": {
|
|
759
|
-
await session.abort();
|
|
759
|
+
await session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
760
760
|
session
|
|
761
761
|
.prompt(command.message, { images: command.images })
|
|
762
762
|
.catch(e => output(error(id, "abort_and_prompt", e.message)));
|
|
@@ -10,6 +10,7 @@ import { runExtensionCompact, runExtensionSetModel } from "../extensibility/exte
|
|
|
10
10
|
import { getSessionSlashCommands } from "../extensibility/extensions/get-commands-handler";
|
|
11
11
|
import type { ExtensionError, ExtensionUIContext } from "../extensibility/extensions/types";
|
|
12
12
|
import type { AgentSession } from "../session/agent-session";
|
|
13
|
+
import { USER_INTERRUPT_LABEL } from "../session/messages";
|
|
13
14
|
|
|
14
15
|
/** Action name for an extension-originated send failure. */
|
|
15
16
|
export type ExtensionSendAction = "extension_send" | "extension_send_user";
|
|
@@ -98,7 +99,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
|
|
|
98
99
|
{
|
|
99
100
|
getModel: () => session.model,
|
|
100
101
|
isIdle: () => !session.isStreaming,
|
|
101
|
-
abort: () => session.abort(),
|
|
102
|
+
abort: () => session.abort({ reason: USER_INTERRUPT_LABEL }),
|
|
102
103
|
hasPendingMessages: () => session.queuedMessageCount > 0,
|
|
103
104
|
shutdown,
|
|
104
105
|
getContextUsage: () => session.getContextUsage(),
|
package/src/modes/types.ts
CHANGED
|
@@ -53,6 +53,11 @@ export type SubmittedUserInput = {
|
|
|
53
53
|
* as a hidden agent-authored `developer` message rather than a visible user
|
|
54
54
|
* turn. Used by the `c`/`.` continue shortcut. */
|
|
55
55
|
synthetic?: boolean;
|
|
56
|
+
/** Marks this submission as a deliberate user resume (set by the `.`/`c`
|
|
57
|
+
* continue shortcut, which is also `synthetic`). Forwarded to
|
|
58
|
+
* `session.prompt({ userInitiated })` so it clears advisor auto-resume
|
|
59
|
+
* suppression even though it is synthetic. */
|
|
60
|
+
userInitiated?: boolean;
|
|
56
61
|
display?: boolean;
|
|
57
62
|
/** Queue intent if the session is (or becomes) busy when this submission is
|
|
58
63
|
* dispatched: "steer" (interrupt the active turn) or "followUp" (process after
|
|
@@ -270,13 +275,15 @@ export interface InteractiveModeContext {
|
|
|
270
275
|
handleShareCommand(): Promise<void>;
|
|
271
276
|
handleTodoCommand(args: string): Promise<void>;
|
|
272
277
|
handleSessionCommand(): Promise<void>;
|
|
278
|
+
handleAdvisorStatusCommand(): Promise<void>;
|
|
273
279
|
handleJobsCommand(): Promise<void>;
|
|
274
280
|
handleUsageCommand(reports?: UsageReport[] | null): Promise<void>;
|
|
275
281
|
handleChangelogCommand(showFull?: boolean): Promise<void>;
|
|
276
282
|
handleHotkeysCommand(): void;
|
|
277
283
|
handleToolsCommand(): void;
|
|
278
284
|
handleContextCommand(): void;
|
|
279
|
-
handleDumpCommand(): void;
|
|
285
|
+
handleDumpCommand(isRaw?: boolean): void;
|
|
286
|
+
handleAdvisorDumpCommand(isRaw?: boolean): void;
|
|
280
287
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
281
288
|
handleClearCommand(): Promise<void>;
|
|
282
289
|
handleFreshCommand(): Promise<void>;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { AssistantMessage, ImageContent, Message, Usage } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { type Component, Spacer, Text, TruncatedText } from "@oh-my-pi/pi-tui";
|
|
4
|
+
import type { AdvisorMessageDetails } from "../../advisor";
|
|
4
5
|
import { COLLAB_PROMPT_MESSAGE_TYPE, type CollabPromptDetails } from "../../collab/protocol";
|
|
5
6
|
import { settings } from "../../config/settings";
|
|
6
7
|
import { getFileSnapshotStore } from "../../edit/file-snapshot-store";
|
|
8
|
+
import { createAdvisorMessageCard } from "../../modes/components/advisor-message";
|
|
7
9
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
8
10
|
import { createBackgroundTanDispatchBlock } from "../../modes/components/background-tan-message";
|
|
9
11
|
import { BashExecutionComponent } from "../../modes/components/bash-execution";
|
|
@@ -240,6 +242,13 @@ export class UiHelpers {
|
|
|
240
242
|
this.ctx.chatContainer.addChild(card);
|
|
241
243
|
return [card];
|
|
242
244
|
}
|
|
245
|
+
if (message.customType === "advisor") {
|
|
246
|
+
const details = (message as CustomMessage<AdvisorMessageDetails>).details;
|
|
247
|
+
this.ctx.chatContainer.addChild(
|
|
248
|
+
createAdvisorMessageCard(details, () => this.ctx.toolOutputExpanded, theme),
|
|
249
|
+
);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
243
252
|
if (message.customType === BACKGROUND_TAN_DISPATCH_MESSAGE_TYPE) {
|
|
244
253
|
this.ctx.chatContainer.addChild(createBackgroundTanDispatchBlock(message as CustomMessage<unknown>));
|
|
245
254
|
break;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Send one concrete, terse piece of advice to the agent you are watching. Use sparingly; stay silent when nothing matters.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<system-conventions>
|
|
2
|
+
RFC 2119 applies to MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, OPTIONAL. `NEVER` and `AVOID` are aliases for `MUST NOT` and `SHOULD NOT`.
|
|
3
|
+
You can explore the workspace; budget is 2–3 tool calls per advise (exception: critical bugs warrant deeper verification before raising a blocker).
|
|
4
|
+
</system-conventions>
|
|
5
|
+
|
|
6
|
+
You bring a different angle.
|
|
7
|
+
The agent might not have thought about an edge case, spotted a hallucinated API, or realized a simpler approach exists.
|
|
8
|
+
Your job is to offer that view before they sink work into the wrong direction.
|
|
9
|
+
|
|
10
|
+
<workflow>
|
|
11
|
+
You receive the agent's transcript incrementally, including private thinking.
|
|
12
|
+
You have read-only access through `read`, `search`, `find` to verify your suspicions.
|
|
13
|
+
Keep exploration lean — 2–3 calls per advise unless you've spotted a critical bug and need to be absolutely certain before raising a blocker.
|
|
14
|
+
</workflow>
|
|
15
|
+
|
|
16
|
+
<communication>
|
|
17
|
+
At most one `advise` per update. Prefer silence when the agent is on track. Address the agent directly. Offer alternatives, not lectures. Never restate what they know; never explain how to use the advisor.
|
|
18
|
+
</communication>
|
|
19
|
+
|
|
20
|
+
<critical>
|
|
21
|
+
You SHOULD call `advise` when: agent might be heading the wrong way, missed an edge case, about to call a hallucinated API, going in circles, picking brittle approach over better one. Low confidence bar — "this might be wrong" is worth noting if they didn't think about it.
|
|
22
|
+
NEVER advise just to second-guess decisions the agent understands and is committed to, if you are not certain.
|
|
23
|
+
</critical>
|
|
24
|
+
|
|
25
|
+
<completeness>
|
|
26
|
+
**`nit`** — Non-urgent cleanup, refactor, style, missed opportunity. Folded at next step boundary; agent keeps working. Examples: edge cases that don't break correctness, simplifications, better approach the agent can consider.
|
|
27
|
+
**`concern`** — Agent might be heading wrong or missed something material. Offers your view; agent decides. Use when: exploring wrong code path, picking fragile approach when better exists, missing constraint, hallucinated API, going in circles, edge case about to be baked in.
|
|
28
|
+
**`blocker`** — Stop and reconsider. Use ONLY when: continuing will clearly waste the turn, produce broken output, or the path is fundamentally unsound. Verify thoroughly before raising.
|
|
29
|
+
</completeness>
|
|
30
|
+
|
|
31
|
+
You MAY suggest an approach or fix if you've explored enough to be confident. Your job is pair programming, not just bugs — offer the better designs, not just the warning.
|
|
@@ -14,6 +14,14 @@ Implement and review UI designs. Edit files, create components, run commands whe
|
|
|
14
14
|
- Responsive design, layout structure
|
|
15
15
|
</strengths>
|
|
16
16
|
|
|
17
|
+
<design-system>
|
|
18
|
+
Treat the design system as the foundation — UI built without one collapses into inconsistency. Work four phases in order:
|
|
19
|
+
1. **Token-first analysis (before any CSS/JSX/Svelte).** `search`/`read` for the design tokens (colors, spacing, typography, shadows, radii), theme files (CSS variables, Tailwind config, `theme.ts`), and shared primitives (Button, Card, Input, Layout). Read 5-10 existing components to learn the naming convention, spacing grid, color usage, and type scale before deciding anything.
|
|
20
|
+
2. **No coherent system? Build the minimal one first.** Extract what exists, then define a palette, type scale, spacing scale (4px/8px base), radii/shadows/transitions, and primitive components — THEN implement the request against it.
|
|
21
|
+
3. **Compose with the system, never around it.** Colors → tokens/CSS variables, never hardcoded hex; spacing → scale values, never arbitrary px; type → scale steps; components → extend/compose existing primitives, not one-off div soup. Need something outside the system? Add the new token to the system first, then use it — never a one-off override.
|
|
22
|
+
4. **Verify before done.** Every color a token, every spacing on the scale, every component on the existing composition pattern, zero magic numbers — a designer would see consistency across old and new. Any "no" → not done.
|
|
23
|
+
</design-system>
|
|
24
|
+
|
|
17
25
|
<procedure>
|
|
18
26
|
## Implementation
|
|
19
27
|
1. Read existing components, tokens, patterns—reuse before inventing
|
|
@@ -37,7 +37,7 @@ Group files by locality, e.g.:
|
|
|
37
37
|
Reviewer MUST:
|
|
38
38
|
1. Focus ONLY on assigned files
|
|
39
39
|
2. {{#if skipDiff}}{{diffInstruction}}{{else}}MUST use diff hunks below (NEVER re-run git diff){{/if}}
|
|
40
|
-
3.
|
|
40
|
+
3. {{contextInstruction}}
|
|
41
41
|
4. Call `report_finding` per issue
|
|
42
42
|
5. Call `yield` with verdict when done
|
|
43
43
|
|
|
@@ -41,7 +41,10 @@ You NEVER modify files outside this tree or in the original repository.
|
|
|
41
41
|
You can reach other live agents via the `irc` tool. Your id is `{{ircSelfId}}`. Currently visible peers:
|
|
42
42
|
{{ircPeers}}
|
|
43
43
|
|
|
44
|
-
Use `irc` only
|
|
44
|
+
Use `irc` only for quick coordination, never long-form content. Address peers by id or use `"all"` to broadcast.
|
|
45
|
+
- Discovery: the roster above shows each peer's role and what it is doing now; `irc` op:"list" refreshes it.
|
|
46
|
+
- Coordination: before you edit a file or start work a sibling may already own, message that peer first — overlapping edits collide.
|
|
47
|
+
- Follow-up: answer a peer's question with a short reply (set `replyTo`); use `await` only when you genuinely cannot proceed without the answer.
|
|
45
48
|
{{/if}}
|
|
46
49
|
|
|
47
50
|
COMPLETION
|
|
@@ -41,9 +41,9 @@ tool.<name>(args) → unknown
|
|
|
41
41
|
Invoke any session tool; `args` is its parameter object.
|
|
42
42
|
completion(prompt, model?="default", system?=None, schema?=None) → str | dict
|
|
43
43
|
Oneshot stateless completion (no history, no tools). `model` tier: "smol" (fast) | "default" (session model) | "slow" (most capable). JSON-Schema `schema` forces structured output, returns parsed object.
|
|
44
|
-
{{#if spawns}}agent(prompt, agent_type?="task", model?=None, label?=None, schema?=None) → str | dict
|
|
45
|
-
Run a subagent, return its final output. `agent_type`/`agentType` picks another discovered agent; `schema` as in completion(). Share background via `local://` files referenced in the prompt.
|
|
46
|
-
{{#if js}} JS: options are ONE trailing object — agent(prompt, { agentType, schema }).
|
|
44
|
+
{{#if spawns}}agent(prompt, agent_type?="task", model?=None, label?=None, schema?=None, return_handle?=False) → str | dict
|
|
45
|
+
Run a subagent, return its final output. `agent_type`/`agentType` picks another discovered agent; `schema` as in completion(). Share background via `local://` files referenced in the prompt. `return_handle`/`returnHandle` → a DAG node dict { text, output, handle: "agent://<id>", id, agent } (parsed object under `data` when `schema` set) so a downstream stage references the transcript by handle instead of re-inlining it.
|
|
46
|
+
{{#if js}} JS: options are ONE trailing object — agent(prompt, { agentType, schema, returnHandle }).
|
|
47
47
|
{{/if}}
|
|
48
48
|
{{/if}}
|
|
49
49
|
parallel(thunks) → list
|
|
@@ -58,3 +58,13 @@ budget → per-turn token budget
|
|
|
58
58
|
{{#if py}}`budget.total` (ceiling or None), `budget.spent()`, `budget.remaining()` (math.inf when no ceiling), `budget.hard` (bool).{{/if}}{{#if js}}`await budget.total()` (ceiling or null), `await budget.spent()`, `await budget.remaining()` (Infinity when no ceiling), `await budget.hard()`.{{/if}} Ceiling comes from a `+Nk` directive (advisory) or `+Nk!`/Goal Mode (hard — `agent()` refuses to spawn past it); otherwise None/null, spend still tracked across the turn.
|
|
59
59
|
```
|
|
60
60
|
</prelude>
|
|
61
|
+
{{#if spawns}}
|
|
62
|
+
<dag>
|
|
63
|
+
Build a dependency graph by piping handles through the stage helpers — ephemeral, in-session, acyclic waves:
|
|
64
|
+
- **Name nodes.** Capture each `agent(…, {{#if py}}return_handle=True{{/if}}{{#if js}}{ returnHandle: true }{{/if}})` result; it carries `handle` (`agent://<id>`) + `output`.
|
|
65
|
+
- **Wire edges by reference.** Embed an upstream node's `handle` or `output` in the dependent stage's prompt so a large transcript flows by reference, never re-inlined. For bulk artifacts, `write("local://<name>.md", …)` and pass the URI.
|
|
66
|
+
- **`pipeline(items, *stages)` = staged waves** with a barrier between stages (every item clears stage N before any enters stage N+1) — the linear spine of a DAG. **`parallel(thunks)` = one wave** of independent nodes.
|
|
67
|
+
- **Isolate failure.** A raising node re-raises the lowest-index error and aborts its wave; wrap each risky node in try/except so a failed node degrades only its dependent subtree while independent branches still finish.
|
|
68
|
+
- **Acyclic only.** A node never waits on its own descendant; cycles are an authoring bug, not a supported pattern.
|
|
69
|
+
</dag>
|
|
70
|
+
{{/if}}
|
package/src/prompts/tools/irc.md
CHANGED
|
@@ -17,7 +17,7 @@ Reach for `irc` proactively when continuing alone is wasteful or wrong; when in
|
|
|
17
17
|
- **Unexpected state** — missing file, config contradicting the assignment, API/tool behaving differently than told. DM `Main` (or your spawner) instead of guessing.
|
|
18
18
|
- **Blocked by another agent** — a peer holds the file/branch/resource or decision you need, or started the change you're about to make. DM them (or broadcast to discover who) before duplicating work.
|
|
19
19
|
- **Decision outside your scope** — a genuine fork the assignment didn't pre-decide. Ask the requester rather than picking unilaterally.
|
|
20
|
-
- **Coordination** — a peer's in-flight work
|
|
20
|
+
- **Coordination** — a peer's in-flight work overlaps yours (the roster shows each peer's role and current activity); message before editing a shared file or duplicating a sibling's change.
|
|
21
21
|
|
|
22
22
|
NEVER for: routine progress updates, things a tool call can verify, questions your assignment/repo/docs already answer.
|
|
23
23
|
</when_to_use>
|
package/src/sdk.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
type SimpleStreamOptions,
|
|
17
17
|
streamSimple,
|
|
18
18
|
} from "@oh-my-pi/pi-ai";
|
|
19
|
-
import type {
|
|
19
|
+
import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
|
|
20
20
|
import {
|
|
21
21
|
getOpenAICodexTransportDetails,
|
|
22
22
|
prewarmOpenAICodexResponses,
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
prompt,
|
|
36
36
|
Snowflake,
|
|
37
37
|
} from "@oh-my-pi/pi-utils";
|
|
38
|
+
import { ADVISOR_READONLY_TOOL_NAMES, discoverWatchdogFiles } from "./advisor";
|
|
38
39
|
import { type AsyncJob, AsyncJobManager } from "./async";
|
|
39
40
|
import { AutoLearnController, buildAutoLearnInstructions } from "./autolearn/controller";
|
|
40
41
|
import { loadCapability } from "./capability";
|
|
@@ -128,6 +129,7 @@ import {
|
|
|
128
129
|
type CustomMessage,
|
|
129
130
|
convertToLlm,
|
|
130
131
|
LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE,
|
|
132
|
+
USER_INTERRUPT_LABEL,
|
|
131
133
|
wrapSteeringForModel,
|
|
132
134
|
} from "./session/messages";
|
|
133
135
|
import { getRestorableSessionModels } from "./session/session-context";
|
|
@@ -176,6 +178,7 @@ import {
|
|
|
176
178
|
getSearchTools,
|
|
177
179
|
HIDDEN_TOOLS,
|
|
178
180
|
isImageProviderPreference,
|
|
181
|
+
isSearchProviderId,
|
|
179
182
|
isSearchProviderPreference,
|
|
180
183
|
type LspStartupServerInfo,
|
|
181
184
|
loadSshTool,
|
|
@@ -184,6 +187,7 @@ import {
|
|
|
184
187
|
renderSearchToolBm25Description,
|
|
185
188
|
SearchTool,
|
|
186
189
|
SearchToolBm25Tool,
|
|
190
|
+
setExcludedSearchProviders,
|
|
187
191
|
setPreferredImageProvider,
|
|
188
192
|
setPreferredSearchProvider,
|
|
189
193
|
type Tool,
|
|
@@ -551,12 +555,12 @@ export interface CreateAgentSessionResult {
|
|
|
551
555
|
eventBus: EventBus;
|
|
552
556
|
}
|
|
553
557
|
|
|
554
|
-
export type
|
|
558
|
+
export type DialectFormat = "auto" | "native" | Dialect;
|
|
555
559
|
|
|
556
|
-
export function
|
|
557
|
-
format:
|
|
560
|
+
export function resolveDialect(
|
|
561
|
+
format: DialectFormat,
|
|
558
562
|
model: Pick<Model, "supportsTools"> | undefined,
|
|
559
|
-
):
|
|
563
|
+
): Dialect | undefined {
|
|
560
564
|
if (format === "native") return undefined;
|
|
561
565
|
if (format === "auto") return model?.supportsTools === false ? "glm" : undefined;
|
|
562
566
|
return format;
|
|
@@ -1141,6 +1145,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1141
1145
|
? Promise.resolve(options.contextFiles)
|
|
1142
1146
|
: logger.time("discoverContextFiles", discoverContextFiles, cwd, agentDir);
|
|
1143
1147
|
contextFilesPromise.catch(() => {});
|
|
1148
|
+
const watchdogFilesPromise = logger.time("discoverWatchdogFiles", () => discoverWatchdogFiles(cwd, agentDir));
|
|
1149
|
+
watchdogFilesPromise.catch(() => {});
|
|
1144
1150
|
const promptTemplatesPromise = options.promptTemplates
|
|
1145
1151
|
? Promise.resolve(options.promptTemplates)
|
|
1146
1152
|
: logger.time("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir);
|
|
@@ -1161,6 +1167,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1161
1167
|
discoveredSkillsPromise?.catch(() => {});
|
|
1162
1168
|
|
|
1163
1169
|
// Initialize provider preferences from settings
|
|
1170
|
+
const excludedWebSearchProviders = settings.get("providers.webSearchExclude");
|
|
1171
|
+
if (Array.isArray(excludedWebSearchProviders)) {
|
|
1172
|
+
setExcludedSearchProviders(excludedWebSearchProviders.filter(isSearchProviderId));
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1164
1175
|
const webSearchProvider = settings.get("providers.webSearch");
|
|
1165
1176
|
if (typeof webSearchProvider === "string" && isSearchProviderPreference(webSearchProvider)) {
|
|
1166
1177
|
setPreferredSearchProvider(webSearchProvider);
|
|
@@ -1370,9 +1381,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1370
1381
|
}
|
|
1371
1382
|
return result;
|
|
1372
1383
|
};
|
|
1373
|
-
const [contextFiles, resolvedWorkspaceTree] = await Promise.all([
|
|
1384
|
+
const [contextFiles, resolvedWorkspaceTree, watchdogFiles] = await Promise.all([
|
|
1374
1385
|
contextFilesPromise,
|
|
1375
1386
|
raceWithDeadline("buildWorkspaceTree", workspaceTreePromise),
|
|
1387
|
+
watchdogFilesPromise,
|
|
1376
1388
|
]);
|
|
1377
1389
|
|
|
1378
1390
|
let agent: Agent;
|
|
@@ -1608,8 +1620,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1608
1620
|
let startDeferredMCPDiscovery:
|
|
1609
1621
|
| ((liveSession: AgentSession, activation: DeferredMCPActivation) => void)
|
|
1610
1622
|
| undefined;
|
|
1623
|
+
const startupQuiet = settings.get("startup.quiet");
|
|
1611
1624
|
const onMCPConnecting = (serverNames: string[]) => {
|
|
1612
|
-
if (!options.hasUI || serverNames.length === 0) return;
|
|
1625
|
+
if (!options.hasUI || startupQuiet || serverNames.length === 0) return;
|
|
1613
1626
|
eventBus.emit(MCP_CONNECTING_EVENT_CHANNEL, { serverNames } satisfies McpConnectingEvent);
|
|
1614
1627
|
};
|
|
1615
1628
|
const mcpDiscoverOptions = {
|
|
@@ -1992,7 +2005,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1992
2005
|
isIdle: () => !session.isStreaming,
|
|
1993
2006
|
hasQueuedMessages: () => session.queuedMessageCount > 0,
|
|
1994
2007
|
abort: () => {
|
|
1995
|
-
session.abort();
|
|
2008
|
+
session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
1996
2009
|
},
|
|
1997
2010
|
settings,
|
|
1998
2011
|
autoApprove: options.autoApprove ?? false,
|
|
@@ -2161,10 +2174,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2161
2174
|
}
|
|
2162
2175
|
appendPrompt = parts.join("\n\n");
|
|
2163
2176
|
}
|
|
2164
|
-
// Owned/in-band tool
|
|
2177
|
+
// Owned/in-band tool dialect (non-native) repeats the catalog as `# Tool:`
|
|
2165
2178
|
// sections; native tool calling lets the compact name list suffice.
|
|
2166
|
-
const nativeTools =
|
|
2167
|
-
resolveToolCallSyntax(settings.get("tools.format"), agent?.state.model ?? model) === undefined;
|
|
2179
|
+
const nativeTools = resolveDialect(settings.get("tools.format"), agent?.state.model ?? model) === undefined;
|
|
2168
2180
|
const defaultPrompt = await buildSystemPromptInternal({
|
|
2169
2181
|
cwd,
|
|
2170
2182
|
skills,
|
|
@@ -2506,7 +2518,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2506
2518
|
return result;
|
|
2507
2519
|
},
|
|
2508
2520
|
intentTracing: !!intentField,
|
|
2509
|
-
|
|
2521
|
+
dialect: resolveDialect(settings.get("tools.format"), model),
|
|
2510
2522
|
abortOnFabricatedToolResult: settings.get("tools.abortOnFabricatedResult"),
|
|
2511
2523
|
getToolChoice: () => session?.nextToolChoice(),
|
|
2512
2524
|
telemetry: options.telemetry,
|
|
@@ -2537,7 +2549,41 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2537
2549
|
}
|
|
2538
2550
|
}
|
|
2539
2551
|
|
|
2552
|
+
// Hard-isolated read-only toolset for the advisor (built unconditionally so
|
|
2553
|
+
// it can be toggled at runtime). Fresh ReadTool/SearchTool/FindTool bound to a
|
|
2554
|
+
// DISTINCT ToolSession so the advisor's investigative reads never touch the
|
|
2555
|
+
// primary's snapshot, seen-lines, conflict, or summary caches (all keyed on
|
|
2556
|
+
// session identity). `cwd` stays dynamic; edit/yield capabilities are off.
|
|
2557
|
+
const advisorToolSession: ToolSession = {
|
|
2558
|
+
...toolSession,
|
|
2559
|
+
get cwd() {
|
|
2560
|
+
return sessionManager.getCwd();
|
|
2561
|
+
},
|
|
2562
|
+
hasEditTool: false,
|
|
2563
|
+
requireYieldTool: false,
|
|
2564
|
+
conflictHistory: undefined,
|
|
2565
|
+
fileSnapshotStore: undefined,
|
|
2566
|
+
getSessionId: () => {
|
|
2567
|
+
const id = sessionManager.getSessionId?.();
|
|
2568
|
+
return id ? `${id}-advisor` : null;
|
|
2569
|
+
},
|
|
2570
|
+
getAgentId: () => "advisor",
|
|
2571
|
+
};
|
|
2572
|
+
const built = await Promise.all(
|
|
2573
|
+
[...ADVISOR_READONLY_TOOL_NAMES].map(name =>
|
|
2574
|
+
BUILTIN_TOOLS[name as keyof typeof BUILTIN_TOOLS](advisorToolSession),
|
|
2575
|
+
),
|
|
2576
|
+
);
|
|
2577
|
+
const advisorReadOnlyTools: Tool[] = built
|
|
2578
|
+
.filter((tool): tool is Tool => tool != null)
|
|
2579
|
+
.map(wrapToolWithMetaNotice);
|
|
2580
|
+
|
|
2581
|
+
let advisorWatchdogPrompt: string | undefined;
|
|
2582
|
+
if (watchdogFiles && watchdogFiles.length > 0) {
|
|
2583
|
+
advisorWatchdogPrompt = watchdogFiles.join("\n\n");
|
|
2584
|
+
}
|
|
2540
2585
|
session = new AgentSession({
|
|
2586
|
+
advisorWatchdogPrompt,
|
|
2541
2587
|
agent,
|
|
2542
2588
|
thinkingLevel: autoThinking ? AUTO_THINKING : effectiveThinkingLevel,
|
|
2543
2589
|
sessionManager,
|
|
@@ -2592,6 +2638,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2592
2638
|
agentKind,
|
|
2593
2639
|
providerSessionId: options.providerSessionId,
|
|
2594
2640
|
parentEvalSessionId: options.parentEvalSessionId,
|
|
2641
|
+
advisorReadOnlyTools,
|
|
2595
2642
|
});
|
|
2596
2643
|
hasSession = true;
|
|
2597
2644
|
if (asyncJobManager) {
|
|
@@ -2696,7 +2743,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2696
2743
|
type: "completed",
|
|
2697
2744
|
servers: result.servers,
|
|
2698
2745
|
};
|
|
2699
|
-
eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
2746
|
+
if (!startupQuiet) eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
2700
2747
|
} catch (error) {
|
|
2701
2748
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2702
2749
|
logger.warn("LSP server warmup failed", { cwd, error: errorMessage });
|
|
@@ -2708,7 +2755,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2708
2755
|
type: "failed",
|
|
2709
2756
|
error: errorMessage,
|
|
2710
2757
|
};
|
|
2711
|
-
eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
2758
|
+
if (!startupQuiet) eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
2712
2759
|
}
|
|
2713
2760
|
})();
|
|
2714
2761
|
}
|