@oh-my-pi/pi-coding-agent 16.2.8 → 16.2.9
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 +30 -0
- package/dist/cli.js +3445 -3382
- package/dist/types/config/settings-schema.d.ts +41 -13
- package/dist/types/modes/components/mcp-add-wizard.d.ts +8 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +9 -0
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/stt/index.d.ts +1 -0
- package/dist/types/stt/stt-controller.d.ts +2 -0
- package/dist/types/stt/submit-trigger.d.ts +30 -0
- package/dist/types/task/index.d.ts +1 -1
- package/dist/types/task/types.d.ts +6 -6
- package/dist/types/tiny/models.d.ts +22 -8
- package/package.json +12 -12
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/agentic/prompts/system.md +1 -1
- package/src/commit/agentic/tools/analyze-file.ts +2 -2
- package/src/config/settings-schema.ts +15 -1
- package/src/debug/profiler.ts +7 -1
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/mcp/oauth-flow.ts +35 -8
- package/src/modes/components/mcp-add-wizard.ts +43 -3
- package/src/modes/components/model-selector.ts +21 -9
- package/src/modes/controllers/event-controller.ts +9 -0
- package/src/modes/controllers/mcp-command-controller.ts +84 -3
- package/src/modes/interactive-mode.ts +5 -4
- package/src/prompts/agents/tester.md +107 -0
- package/src/prompts/system/orchestrate-notice.md +2 -2
- package/src/prompts/system/system-prompt.md +2 -5
- package/src/prompts/system/thinking-loop-redirect.md +10 -0
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/task.md +2 -9
- package/src/session/agent-session.ts +53 -18
- package/src/stt/index.ts +1 -0
- package/src/stt/stt-controller.ts +31 -2
- package/src/stt/submit-trigger.ts +74 -0
- package/src/task/agents.ts +4 -4
- package/src/task/executor.ts +1 -1
- package/src/task/index.ts +18 -5
- package/src/task/types.ts +5 -5
- package/src/tiny/models.ts +10 -0
- package/src/prompts/agents/oracle.md +0 -54
|
@@ -7,11 +7,10 @@ Execution blocks your turn: the call only returns once the work is completely fi
|
|
|
7
7
|
- **Sequence only when necessary:** The only reason to run A before B is if B strictly requires A's output to function (e.g., a core API contract or schema migration). {{#if ircEnabled}}If the missing piece is small, run them in parallel and have B ask A via `irc`!{{/if}}
|
|
8
8
|
- **Role matching:** Assign each subagent a specific `role` (e.g. "Security Reviewer", "DB Migrator"). Do not spawn generic workers.
|
|
9
9
|
- **No overhead:** Each assignment MUST instruct its agent to skip formatters, linters, and project-wide test suites. You will run those once at the end.
|
|
10
|
-
- **Do your own thinking:** NEVER assign reasoning, architecture, or design to `quick_task` or `explore`. They are for mechanical lookups only. Keep hard decisions in your own context or use `task`, `plan`, or `oracle`.
|
|
11
10
|
- **One-pass agents:** Prefer agents that investigate **and** edit in a single pass; only spin a read-only discovery step (e.g. `explore`) when the affected files are genuinely unknown.
|
|
12
11
|
|
|
13
12
|
# Inputs
|
|
14
|
-
- `agent
|
|
13
|
+
- `agent` (optional): The base agent type to use (e.g., `explore`, `reviewer`). Defaults to `task` (the general-purpose worker) — omit it for the default worker instead of passing `agent: "task"`.
|
|
15
14
|
{{#if batchEnabled}}
|
|
16
15
|
- `context`: Shared project state, constraints, and contracts. Applies to the entire batch; do not duplicate this background into individual tasks.
|
|
17
16
|
- `tasks[]`: Array of subagents to spawn.
|
|
@@ -37,13 +36,7 @@ Subagents start blank. They have no access to your conversation history.
|
|
|
37
36
|
{{#if batchEnabled}}
|
|
38
37
|
- Pass large payloads using `local://<path>` URIs, never inline text.
|
|
39
38
|
{{else}}
|
|
40
|
-
-
|
|
41
|
-
{{/if}}
|
|
42
|
-
{{#if ircEnabled}}
|
|
43
|
-
- Once spawned, coordinate with live agents via `irc` using their IDs. If task B depends on task A, B SHOULD message A directly.
|
|
44
|
-
{{/if}}
|
|
45
|
-
{{#if asyncEnabled}}
|
|
46
|
-
- If you run out of things to do and are genuinely blocked waiting for a subagent, use `job poll`. Use `job cancel` only for stalled work.
|
|
39
|
+
- Write shared project state ONCE to a `local://` file (e.g., `local://ctx.md`) and reference that URL in your assignments.
|
|
47
40
|
{{/if}}
|
|
48
41
|
|
|
49
42
|
# Format Contracts
|
|
@@ -250,6 +250,7 @@ import planModeToolDecisionReminderPrompt from "../prompts/system/plan-mode-tool
|
|
|
250
250
|
type: "text",
|
|
251
251
|
};
|
|
252
252
|
import sideChannelNoToolsReminder from "../prompts/system/side-channel-no-tools.md" with { type: "text" };
|
|
253
|
+
import thinkingLoopRedirectTemplate from "../prompts/system/thinking-loop-redirect.md" with { type: "text" };
|
|
253
254
|
import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { type: "text" };
|
|
254
255
|
import ttsrToolReminderTemplate from "../prompts/system/ttsr-tool-reminder.md" with { type: "text" };
|
|
255
256
|
import unexpectedStopRetryTemplate from "../prompts/system/unexpected-stop-retry.md" with { type: "text" };
|
|
@@ -346,6 +347,9 @@ const SESSION_STOP_CONTINUATION_CAP = 8;
|
|
|
346
347
|
const GEMINI_HEADER_INTERRUPT_REASON = "Interrupted: emit a tool call instead of more planning";
|
|
347
348
|
/** `customType` for the hidden tool-call reminder injected after the interrupt. */
|
|
348
349
|
const GEMINI_TOOL_REMINDER_TYPE = "gemini-tool-call-reminder";
|
|
350
|
+
/** `customType` for the hidden redirect notice injected into a turn retried after a
|
|
351
|
+
* thinking/response loop. Steers the model off the repeated content; never displayed. */
|
|
352
|
+
const THINKING_LOOP_REDIRECT_TYPE = "thinking-loop-redirect";
|
|
349
353
|
|
|
350
354
|
// A side-channel assistant response is signed for the hidden prompt/history that
|
|
351
355
|
// produced it. If we persist that response under a different user turn, native
|
|
@@ -2882,10 +2886,10 @@ export class AgentSession {
|
|
|
2882
2886
|
* the mid-run-compaction planner can ask "is this turn message already on
|
|
2883
2887
|
* the branch?" in O(1) instead of re-walking the branch per check.
|
|
2884
2888
|
*
|
|
2885
|
-
* The
|
|
2886
|
-
*
|
|
2887
|
-
*
|
|
2888
|
-
*
|
|
2889
|
+
* The mid-run ordering check uses key identity alone: same-key content
|
|
2890
|
+
* variants are one logical message at this boundary, because otherwise a
|
|
2891
|
+
* display-side rewrite can make the assistant look missing after its tool
|
|
2892
|
+
* results have already persisted.
|
|
2889
2893
|
*
|
|
2890
2894
|
* Pre-#3629 the equivalent was `sessionManager.getBranch()` called twice
|
|
2891
2895
|
* per turn message, each call rebuilding the path via O(n²) `unshift` and
|
|
@@ -2893,17 +2897,14 @@ export class AgentSession {
|
|
|
2893
2897
|
* per `onTurnEnd` on a long session and the load-bearing source of the
|
|
2894
2898
|
* `ui.loop-blocked` warnings in the bug report.
|
|
2895
2899
|
*/
|
|
2896
|
-
#
|
|
2897
|
-
const
|
|
2900
|
+
#indexPersistedMessageKeys(): Set<string> {
|
|
2901
|
+
const keys = new Set<string>();
|
|
2898
2902
|
for (const entry of this.sessionManager.getBranch()) {
|
|
2899
2903
|
if (entry.type !== "message") continue;
|
|
2900
2904
|
const key = sessionMessagePersistenceKey(entry.message);
|
|
2901
|
-
if (key
|
|
2902
|
-
const existing = index.get(key);
|
|
2903
|
-
if (existing) existing.push(entry.message);
|
|
2904
|
-
else index.set(key, [entry.message]);
|
|
2905
|
+
if (key !== undefined) keys.add(key);
|
|
2905
2906
|
}
|
|
2906
|
-
return
|
|
2907
|
+
return keys;
|
|
2907
2908
|
}
|
|
2908
2909
|
|
|
2909
2910
|
/**
|
|
@@ -3003,17 +3004,17 @@ export class AgentSession {
|
|
|
3003
3004
|
// JSON-compared every entry per turn message, which on long sessions
|
|
3004
3005
|
// turned each `onTurnEnd` into a seconds-long sync block (the
|
|
3005
3006
|
// `ui.loop-blocked` warnings tagged `subagent:*` in the bug report).
|
|
3006
|
-
const
|
|
3007
|
+
const branchKeys = this.#indexPersistedMessageKeys();
|
|
3007
3008
|
const turnKeys = turnMessages.map(sessionMessagePersistenceKey);
|
|
3008
3009
|
const persistedKeys = new Set<string>();
|
|
3009
3010
|
for (let index = 0; index < turnMessages.length; index++) {
|
|
3010
3011
|
const key = turnKeys[index];
|
|
3011
3012
|
if (key === undefined) continue;
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
//
|
|
3015
|
-
//
|
|
3016
|
-
if (
|
|
3013
|
+
// Mid-run ordering is keyed by logical identity. A persisted display
|
|
3014
|
+
// variant (for example, redacted/deobfuscated content) must still count;
|
|
3015
|
+
// otherwise the assistant can look missing while later tool results are
|
|
3016
|
+
// present, producing a false out-of-order skip.
|
|
3017
|
+
if (branchKeys.has(key)) {
|
|
3017
3018
|
persistedKeys.add(key);
|
|
3018
3019
|
}
|
|
3019
3020
|
}
|
|
@@ -8087,7 +8088,7 @@ export class AgentSession {
|
|
|
8087
8088
|
*/
|
|
8088
8089
|
async setModelTemporary(
|
|
8089
8090
|
model: Model,
|
|
8090
|
-
thinkingLevel?:
|
|
8091
|
+
thinkingLevel?: ConfiguredThinkingLevel,
|
|
8091
8092
|
options?: { ephemeral?: boolean },
|
|
8092
8093
|
): Promise<void> {
|
|
8093
8094
|
const previousEditMode = this.#resolveActiveEditMode();
|
|
@@ -12582,6 +12583,11 @@ export class AgentSession {
|
|
|
12582
12583
|
// Remove the failed assistant message from active context before retrying.
|
|
12583
12584
|
this.#removeAssistantMessageFromActiveContext(message);
|
|
12584
12585
|
|
|
12586
|
+
// A thinking/response loop retried into identical context loops again. Inject a
|
|
12587
|
+
// hidden redirect so the retried turn sees a directive to break the repeated
|
|
12588
|
+
// pattern instead of re-sampling the same stalled reasoning.
|
|
12589
|
+
this.#maybeInjectThinkingLoopRedirect(id);
|
|
12590
|
+
|
|
12585
12591
|
// Wait with exponential backoff (abortable).
|
|
12586
12592
|
const retryAbortController = new AbortController();
|
|
12587
12593
|
this.#retryAbortController?.abort();
|
|
@@ -12615,6 +12621,35 @@ export class AgentSession {
|
|
|
12615
12621
|
return true;
|
|
12616
12622
|
}
|
|
12617
12623
|
|
|
12624
|
+
/**
|
|
12625
|
+
* Inject a hidden redirect notice when a thinking/response loop is being retried, so
|
|
12626
|
+
* the retried turn carries an instruction to break the repeated pattern instead of
|
|
12627
|
+
* re-sampling the same stalled context. Injected on every {@link AIError.Flag.ThinkingLoop}
|
|
12628
|
+
* retry (the failed assistant is dropped each attempt, so the notice does not accumulate
|
|
12629
|
+
* unboundedly). No-op unless `id` carries the ThinkingLoop flag and the loop guard is
|
|
12630
|
+
* enabled. The notice is generic on purpose — the detector's detail can quote raw model
|
|
12631
|
+
* text, which must not be interpolated into a higher-priority developer message.
|
|
12632
|
+
*/
|
|
12633
|
+
#maybeInjectThinkingLoopRedirect(id: number): void {
|
|
12634
|
+
if (!AIError.is(id, AIError.Flag.ThinkingLoop)) return;
|
|
12635
|
+
if (this.settings.get("model.loopGuard.enabled") !== true) return;
|
|
12636
|
+
this.agent.appendMessage({
|
|
12637
|
+
role: "custom",
|
|
12638
|
+
customType: THINKING_LOOP_REDIRECT_TYPE,
|
|
12639
|
+
content: thinkingLoopRedirectTemplate,
|
|
12640
|
+
display: false,
|
|
12641
|
+
attribution: "agent",
|
|
12642
|
+
timestamp: Date.now(),
|
|
12643
|
+
});
|
|
12644
|
+
this.sessionManager.appendCustomMessageEntry(
|
|
12645
|
+
THINKING_LOOP_REDIRECT_TYPE,
|
|
12646
|
+
thinkingLoopRedirectTemplate,
|
|
12647
|
+
false,
|
|
12648
|
+
undefined,
|
|
12649
|
+
"agent",
|
|
12650
|
+
);
|
|
12651
|
+
}
|
|
12652
|
+
|
|
12618
12653
|
/**
|
|
12619
12654
|
* Cancel in-progress retry.
|
|
12620
12655
|
*/
|
package/src/stt/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
startStreamingRecording,
|
|
16
16
|
verifyRecordingFile,
|
|
17
17
|
} from "./recorder";
|
|
18
|
+
import { evaluateSubmitTrigger } from "./submit-trigger";
|
|
18
19
|
import { transcribe } from "./transcriber";
|
|
19
20
|
|
|
20
21
|
export type SttState = "idle" | "recording" | "transcribing";
|
|
@@ -33,6 +34,8 @@ interface Editor {
|
|
|
33
34
|
setVolatileText(text: string): void;
|
|
34
35
|
clearVolatileText(): void;
|
|
35
36
|
commitVolatileText(text: string): void;
|
|
37
|
+
submit(): void;
|
|
38
|
+
deleteBeforeCursor(count: number): void;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
export class STTController {
|
|
@@ -53,6 +56,7 @@ export class STTController {
|
|
|
53
56
|
#streamEditor: Editor | null = null;
|
|
54
57
|
#streamCommitted = false;
|
|
55
58
|
#streamAbort: AbortController | null = null;
|
|
59
|
+
#streamUtterance = "";
|
|
56
60
|
|
|
57
61
|
get state(): SttState {
|
|
58
62
|
return this.#state;
|
|
@@ -190,6 +194,7 @@ export class STTController {
|
|
|
190
194
|
const language = settings.get("stt.language") as string | undefined;
|
|
191
195
|
this.#streamEditor = editor;
|
|
192
196
|
this.#streamCommitted = false;
|
|
197
|
+
this.#streamUtterance = "";
|
|
193
198
|
this.#streamAbort = new AbortController();
|
|
194
199
|
const stream = sttClient.startStream(modelKey, {
|
|
195
200
|
language: language || undefined,
|
|
@@ -205,6 +210,7 @@ export class STTController {
|
|
|
205
210
|
if (prefixed) {
|
|
206
211
|
this.#streamEditor?.commitVolatileText(prefixed);
|
|
207
212
|
this.#streamCommitted = true;
|
|
213
|
+
this.#streamUtterance += prefixed;
|
|
208
214
|
} else {
|
|
209
215
|
this.#streamEditor?.clearVolatileText();
|
|
210
216
|
}
|
|
@@ -266,13 +272,27 @@ export class STTController {
|
|
|
266
272
|
return;
|
|
267
273
|
}
|
|
268
274
|
if (!this.#streamCommitted && finalText) {
|
|
269
|
-
this.#
|
|
275
|
+
const prefixed = this.#prefixed(finalText);
|
|
276
|
+
this.#streamEditor?.commitVolatileText(prefixed);
|
|
270
277
|
this.#streamCommitted = true;
|
|
278
|
+
this.#streamUtterance = prefixed;
|
|
271
279
|
} else {
|
|
272
280
|
this.#streamEditor?.clearVolatileText();
|
|
273
281
|
}
|
|
274
282
|
options.requestRender?.();
|
|
275
283
|
if (!failed) options.showStatus(this.#streamCommitted ? "" : "No speech detected.");
|
|
284
|
+
|
|
285
|
+
if (this.#streamCommitted && !failed && this.#streamEditor) {
|
|
286
|
+
const trigger = settings.get("stt.submitTrigger");
|
|
287
|
+
const { submit, trimTrailing } = evaluateSubmitTrigger(this.#streamUtterance, trigger);
|
|
288
|
+
if (trimTrailing > 0) {
|
|
289
|
+
this.#streamEditor.deleteBeforeCursor(trimTrailing);
|
|
290
|
+
}
|
|
291
|
+
if (submit) {
|
|
292
|
+
this.#streamEditor.submit();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
276
296
|
this.#cleanupStream();
|
|
277
297
|
this.#setState("idle", options);
|
|
278
298
|
}
|
|
@@ -283,6 +303,7 @@ export class STTController {
|
|
|
283
303
|
this.#streamEditor = null;
|
|
284
304
|
this.#streamCommitted = false;
|
|
285
305
|
this.#streamAbort = null;
|
|
306
|
+
this.#streamUtterance = "";
|
|
286
307
|
}
|
|
287
308
|
|
|
288
309
|
// ── Batch (single-shot) ─────────────────────────────────────────
|
|
@@ -327,8 +348,16 @@ export class STTController {
|
|
|
327
348
|
this.#transcriptionAbort = null;
|
|
328
349
|
if (this.#disposed) return;
|
|
329
350
|
if (text.length > 0) {
|
|
330
|
-
|
|
351
|
+
const trigger = settings.get("stt.submitTrigger");
|
|
352
|
+
const { submit, trimTrailing } = evaluateSubmitTrigger(text, trigger);
|
|
353
|
+
const textToInsert = trimTrailing > 0 ? text.slice(0, -trimTrailing) : text;
|
|
354
|
+
if (textToInsert.length > 0) {
|
|
355
|
+
editor.insertText(textToInsert);
|
|
356
|
+
}
|
|
331
357
|
options.showStatus("");
|
|
358
|
+
if (submit) {
|
|
359
|
+
editor.submit();
|
|
360
|
+
}
|
|
332
361
|
} else {
|
|
333
362
|
options.showStatus("No speech detected.");
|
|
334
363
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TTS/STT Submit Trigger options and evaluation logic.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const STT_SUBMIT_TRIGGER_VALUES = ["never", "release", "release-complete", "say-submit"] as const;
|
|
6
|
+
|
|
7
|
+
export type SttSubmitTrigger = (typeof STT_SUBMIT_TRIGGER_VALUES)[number];
|
|
8
|
+
|
|
9
|
+
export const STT_SUBMIT_TRIGGER_OPTIONS = [
|
|
10
|
+
{
|
|
11
|
+
value: "never",
|
|
12
|
+
label: "Never",
|
|
13
|
+
description: "Never automatically submit; insert dictation and remain in editor.",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
value: "release",
|
|
17
|
+
label: "Release",
|
|
18
|
+
description: "Submit on release if the utterance has 2+ words to avoid accidental sends.",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
value: "release-complete",
|
|
22
|
+
label: "Release with complete sentence",
|
|
23
|
+
description: "Submit on release if the utterance ends with sentence-terminal punctuation (. ? ! etc.).",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
value: "say-submit",
|
|
27
|
+
label: "When I Say Submit",
|
|
28
|
+
description: "Submit if the utterance ends with a word containing 'submit' (strips that word before submitting).",
|
|
29
|
+
},
|
|
30
|
+
] satisfies ReadonlyArray<{ value: SttSubmitTrigger; label: string; description: string }>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Evaluate the submit trigger against a transcribed utterance.
|
|
34
|
+
* Returns whether to submit, and the number of characters to trim from the end of the utterance.
|
|
35
|
+
*/
|
|
36
|
+
export function evaluateSubmitTrigger(
|
|
37
|
+
utterance: string,
|
|
38
|
+
trigger: SttSubmitTrigger,
|
|
39
|
+
): { submit: boolean; trimTrailing: number } {
|
|
40
|
+
const trimmed = utterance.trim();
|
|
41
|
+
if (!trimmed) {
|
|
42
|
+
return { submit: false, trimTrailing: 0 };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (trigger === "never") {
|
|
46
|
+
return { submit: false, trimTrailing: 0 };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (trigger === "release") {
|
|
50
|
+
// Split by whitespace and count words
|
|
51
|
+
const words = trimmed.split(/\s+/).filter(Boolean);
|
|
52
|
+
const submit = words.length >= 2;
|
|
53
|
+
return { submit, trimTrailing: 0 };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (trigger === "release-complete") {
|
|
57
|
+
// Matches typical sentence terminators: . ? ! ... or full-width equivalents, optionally followed by space
|
|
58
|
+
const hasTerminalPunctuation = /[.?!…。?!]\s*$/.test(trimmed);
|
|
59
|
+
return { submit: hasTerminalPunctuation, trimTrailing: 0 };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (trigger === "say-submit") {
|
|
63
|
+
// Matches space followed by any word containing "submit" (case-insensitive), optionally followed by punctuation/spaces
|
|
64
|
+
// Also handles the case where "submit" is the only word in the utterance (no leading space)
|
|
65
|
+
const match = utterance.match(/(?:^|\s+)(\S*submit\S*)[.?!…。?!]*\s*$/i);
|
|
66
|
+
if (match && match.index !== undefined) {
|
|
67
|
+
const trimTrailing = utterance.length - match.index;
|
|
68
|
+
return { submit: true, trimTrailing };
|
|
69
|
+
}
|
|
70
|
+
return { submit: false, trimTrailing: 0 };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { submit: false, trimTrailing: 0 };
|
|
74
|
+
}
|
package/src/task/agents.ts
CHANGED
|
@@ -11,11 +11,11 @@ import exploreMd from "../prompts/agents/explore.md" with { type: "text" };
|
|
|
11
11
|
// Embed agent markdown files at build time
|
|
12
12
|
import agentFrontmatterTemplate from "../prompts/agents/frontmatter.md" with { type: "text" };
|
|
13
13
|
import librarianMd from "../prompts/agents/librarian.md" with { type: "text" };
|
|
14
|
-
import oracleMd from "../prompts/agents/oracle.md" with { type: "text" };
|
|
15
14
|
|
|
16
15
|
import planMd from "../prompts/agents/plan.md" with { type: "text" };
|
|
17
16
|
import reviewerMd from "../prompts/agents/reviewer.md" with { type: "text" };
|
|
18
17
|
import taskMd from "../prompts/agents/task.md" with { type: "text" };
|
|
18
|
+
import testerMd from "../prompts/agents/tester.md" with { type: "text" };
|
|
19
19
|
|
|
20
20
|
import type { AgentDefinition, AgentSource } from "./types";
|
|
21
21
|
|
|
@@ -47,7 +47,7 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
|
|
|
47
47
|
{ fileName: "designer.md", template: designerMd },
|
|
48
48
|
{ fileName: "reviewer.md", template: reviewerMd },
|
|
49
49
|
{ fileName: "librarian.md", template: librarianMd },
|
|
50
|
-
{ fileName: "
|
|
50
|
+
{ fileName: "tester.md", template: testerMd },
|
|
51
51
|
{
|
|
52
52
|
fileName: "task.md",
|
|
53
53
|
frontmatter: {
|
|
@@ -59,9 +59,9 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
|
|
|
59
59
|
template: taskMd,
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
|
-
fileName: "
|
|
62
|
+
fileName: "sonic.md",
|
|
63
63
|
frontmatter: {
|
|
64
|
-
name: "
|
|
64
|
+
name: "sonic",
|
|
65
65
|
description: "Low-reasoning agent for strictly mechanical updates or data collection only",
|
|
66
66
|
model: "pi/smol",
|
|
67
67
|
thinkingLevel: Effort.Medium,
|
package/src/task/executor.ts
CHANGED
package/src/task/index.ts
CHANGED
|
@@ -243,8 +243,11 @@ function validateShapeParams(batchEnabled: boolean, params: TaskParams): string
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
/**
|
|
246
|
-
* Validate the spawn parameter contract against the wire shapes. `agent`
|
|
247
|
-
*
|
|
246
|
+
* Validate the spawn parameter contract against the wire shapes. `agent`
|
|
247
|
+
* defaults to `task` (the schema default; `execute` normalizes the same way for
|
|
248
|
+
* direct callers), so the missing-`agent` guard only fires for callers that
|
|
249
|
+
* invoke this validator with an unnormalized blank agent. With `task.batch` the
|
|
250
|
+
* model-facing shape is
|
|
248
251
|
* `{ agent, context, tasks[] }` — `tasks` non-empty with per-item assignments
|
|
249
252
|
* and unique ids, `context` non-empty, no top-level `assignment` alongside.
|
|
250
253
|
* The flat `{ agent, ...item }` form stays accepted at runtime under either
|
|
@@ -328,14 +331,17 @@ function spawnParamsFor(params: TaskParams, item: TaskItem): TaskParams {
|
|
|
328
331
|
return spawn;
|
|
329
332
|
}
|
|
330
333
|
|
|
334
|
+
/** Agent type spawned when a `task` call omits `agent`; mirrors the schema default in `getTaskSchema`. */
|
|
335
|
+
const DEFAULT_TASK_AGENT = "task";
|
|
336
|
+
|
|
331
337
|
/** Generic worker agents whose output sharpens with a tailored `role` rather than the bare type. */
|
|
332
|
-
const GENERIC_SPAWN_AGENTS: ReadonlySet<string> = new Set(["task", "
|
|
338
|
+
const GENERIC_SPAWN_AGENTS: ReadonlySet<string> = new Set(["task", "sonic"]);
|
|
333
339
|
|
|
334
340
|
/**
|
|
335
341
|
* Advisory — never a rejection — nudging the spawner toward tailored
|
|
336
342
|
* specialists when it spawns generic role-less workers and still holds spawn
|
|
337
343
|
* capacity (DepthCapacity: it currently has the `task` tool). Fires when a
|
|
338
|
-
* generic `task`/`
|
|
344
|
+
* generic `task`/`sonic` spawn carries no `role`, or when one call clones
|
|
339
345
|
* the same agent ≥2× all without roles. Returns undefined when no nudge applies.
|
|
340
346
|
*/
|
|
341
347
|
export function buildSpecializationAdvisory(
|
|
@@ -559,7 +565,14 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
559
565
|
signal?: AbortSignal,
|
|
560
566
|
onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
|
|
561
567
|
): Promise<AgentToolResult<TaskToolDetails>> {
|
|
562
|
-
const
|
|
568
|
+
const repaired = repairTaskParams(rawParams as TaskParams);
|
|
569
|
+
// The schema defaults `agent` to `task` for model calls, but internal
|
|
570
|
+
// callers and stale transcripts build params directly and bypass arktype.
|
|
571
|
+
// Normalize once here so every downstream path sees the resolved agent.
|
|
572
|
+
const params =
|
|
573
|
+
typeof repaired.agent === "string" && repaired.agent.trim() !== ""
|
|
574
|
+
? repaired
|
|
575
|
+
: { ...repaired, agent: DEFAULT_TASK_AGENT };
|
|
563
576
|
const batchEnabled = this.#isBatchEnabled();
|
|
564
577
|
const validationError = validateShapeParams(batchEnabled, params) ?? validateSpawnParams(params, batchEnabled);
|
|
565
578
|
if (validationError) {
|
package/src/task/types.ts
CHANGED
|
@@ -111,7 +111,7 @@ export interface TaskItem {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
export const taskSchema = type({
|
|
114
|
-
agent: "string",
|
|
114
|
+
agent: "string = 'task'",
|
|
115
115
|
"id?": "string",
|
|
116
116
|
"description?": "string",
|
|
117
117
|
"role?": ROLE_INPUT_SCHEMA,
|
|
@@ -120,7 +120,7 @@ export const taskSchema = type({
|
|
|
120
120
|
"+": "delete",
|
|
121
121
|
});
|
|
122
122
|
const taskSchemaNoIsolation = type({
|
|
123
|
-
agent: "string",
|
|
123
|
+
agent: "string = 'task'",
|
|
124
124
|
"id?": "string",
|
|
125
125
|
"description?": "string",
|
|
126
126
|
"role?": ROLE_INPUT_SCHEMA,
|
|
@@ -128,13 +128,13 @@ const taskSchemaNoIsolation = type({
|
|
|
128
128
|
"+": "delete",
|
|
129
129
|
});
|
|
130
130
|
const taskSchemaBatch = type({
|
|
131
|
-
agent: "string",
|
|
131
|
+
agent: "string = 'task'",
|
|
132
132
|
context: "string",
|
|
133
133
|
tasks: taskItemSchemaIsolated.array(),
|
|
134
134
|
"+": "delete",
|
|
135
135
|
});
|
|
136
136
|
const taskSchemaBatchNoIsolation = type({
|
|
137
|
-
agent: "string",
|
|
137
|
+
agent: "string = 'task'",
|
|
138
138
|
context: "string",
|
|
139
139
|
tasks: taskItemSchema.array(),
|
|
140
140
|
"+": "delete",
|
|
@@ -160,7 +160,7 @@ export function getTaskSchema(options: { isolationEnabled: boolean; batchEnabled
|
|
|
160
160
|
* transcripts using the flat form keep working under either setting.
|
|
161
161
|
*/
|
|
162
162
|
export interface TaskParams {
|
|
163
|
-
/** Agent type;
|
|
163
|
+
/** Agent type to spawn; defaults to `"task"` (the general-purpose worker) when omitted. */
|
|
164
164
|
agent?: string;
|
|
165
165
|
/** Stable agent id (flat form); default = generated AdjectiveNoun. */
|
|
166
166
|
id?: string;
|
package/src/tiny/models.ts
CHANGED
|
@@ -132,6 +132,15 @@ export const TINY_MEMORY_LOCAL_MODELS = [
|
|
|
132
132
|
unsupportedReason:
|
|
133
133
|
"onnxruntime-node does not support Qwen3 RotaryEmbedding cache updates in onnx-community/Qwen3-1.7B-ONNX",
|
|
134
134
|
},
|
|
135
|
+
{
|
|
136
|
+
key: "llama3.2:3b",
|
|
137
|
+
repo: "onnx-community/Llama-3.2-3B-Instruct-ONNX",
|
|
138
|
+
dtype: "q4",
|
|
139
|
+
label: "Llama 3.2 3B",
|
|
140
|
+
description:
|
|
141
|
+
"Larger Llama 3.2 option for local memory/classifier tasks; higher quality potential at higher disk/RAM/latency cost.",
|
|
142
|
+
contextNote: "Use when larger model capacity is preferred over faster load times.",
|
|
143
|
+
},
|
|
135
144
|
{
|
|
136
145
|
key: "gemma-3-1b",
|
|
137
146
|
repo: "onnx-community/gemma-3-1b-it-ONNX",
|
|
@@ -161,6 +170,7 @@ export const TINY_MEMORY_LOCAL_MODELS = [
|
|
|
161
170
|
export const TINY_MEMORY_MODEL_VALUES = [
|
|
162
171
|
ONLINE_MEMORY_MODEL_KEY,
|
|
163
172
|
"qwen3-1.7b",
|
|
173
|
+
"llama3.2:3b",
|
|
164
174
|
"gemma-3-1b",
|
|
165
175
|
"qwen2.5-1.5b",
|
|
166
176
|
"lfm2-1.2b",
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: oracle
|
|
3
|
-
description: Wise senior engineer to consult or delegate work to — debugging, architecture, second opinions, and hands-on implementation when asked.
|
|
4
|
-
spawns: explore
|
|
5
|
-
model: pi/slow
|
|
6
|
-
thinking-level: xhigh
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
You are the wise guy on the team — a senior engineer with deep judgment that other agents consult when they are stuck, uncertain, or need a second opinion. You also take direct delegation: if the caller hands you work, you do it, including reads, writes, edits, and running commands.
|
|
10
|
-
|
|
11
|
-
You diagnose, decide, and execute. You match the mode to the ask:
|
|
12
|
-
- **Consult**: explain the root cause, lay out tradeoffs, recommend a path.
|
|
13
|
-
- **Delegate**: carry the work to completion — modify files, run verification, deliver a finished change.
|
|
14
|
-
|
|
15
|
-
<directives>
|
|
16
|
-
- You MUST reason from first principles. The caller already tried the obvious.
|
|
17
|
-
- You MUST use tools to verify claims. You NEVER speculate about code behavior — read it.
|
|
18
|
-
- You MUST identify root causes, not symptoms. If the caller says "X is broken", determine *why* X is broken.
|
|
19
|
-
- You MUST surface hidden assumptions — in the code, in the caller's framing, in the environment.
|
|
20
|
-
- You SHOULD consider at least two hypotheses before converging on one.
|
|
21
|
-
- You SHOULD invoke tools in parallel when investigating multiple hypotheses.
|
|
22
|
-
- When the problem is architectural, you MUST weigh tradeoffs explicitly: what does each option cost, what does it buy, what does it foreclose.
|
|
23
|
-
- When delegated implementation work, you MUST finish it: edit the files, run the relevant tests/checks, and report exactly what changed.
|
|
24
|
-
</directives>
|
|
25
|
-
|
|
26
|
-
<decision-framework>
|
|
27
|
-
Apply pragmatic minimalism:
|
|
28
|
-
- **Bias toward simplicity**: The right solution is the least complex one that fulfills actual requirements. Resist hypothetical future needs.
|
|
29
|
-
- **Leverage what exists**: Favor modifications to current code and established patterns over introducing new components. New dependencies or infrastructure require explicit justification.
|
|
30
|
-
- **One clear path**: Present a single primary recommendation. Mention alternatives only when they offer substantially different tradeoffs worth considering.
|
|
31
|
-
- **Match depth to complexity**: Quick questions get quick answers. Reserve thorough analysis for genuinely complex problems.
|
|
32
|
-
- **Signal the investment**: Tag recommendations with estimated effort — Quick (<1h), Short (1-4h), Medium (1-2d), Large (3d+).
|
|
33
|
-
</decision-framework>
|
|
34
|
-
|
|
35
|
-
<procedure>
|
|
36
|
-
1. Read the problem statement carefully. Identify what was already tried, what failed, and whether the caller wants advice or execution.
|
|
37
|
-
2. Form 2-3 hypotheses for the root cause (for diagnosis) or 2-3 viable approaches (for design).
|
|
38
|
-
3. Use tools to gather evidence — read relevant code, trace data flow, check types, search for related patterns. Parallelize independent reads.
|
|
39
|
-
4. Eliminate hypotheses based on evidence. Narrow to the most likely cause or best approach.
|
|
40
|
-
5. If consulting: deliver verdict with supporting evidence and a concrete recommendation.
|
|
41
|
-
6. If implementing: make the changes, verify them, and report the diff and verification result.
|
|
42
|
-
</procedure>
|
|
43
|
-
|
|
44
|
-
<scope-discipline>
|
|
45
|
-
- Do ONLY what was asked. No unsolicited refactors or improvements.
|
|
46
|
-
- If you notice other issues, list at most 2 as "Optional future considerations" at the end.
|
|
47
|
-
- You NEVER expand the problem surface beyond the original request.
|
|
48
|
-
- Exhaust provided context before reaching for tools. External lookups fill genuine gaps, not curiosity.
|
|
49
|
-
</scope-discipline>
|
|
50
|
-
|
|
51
|
-
<critical>
|
|
52
|
-
You MUST keep going until the problem is solved or the work is finished. Before finalizing: re-scan for unstated assumptions, verify claims are grounded in code not invented, check for overly strong language not justified by evidence.
|
|
53
|
-
The caller came to you because they trust your judgment. Get it right.
|
|
54
|
-
</critical>
|