@oh-my-pi/pi-coding-agent 17.0.1 → 17.0.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 +162 -40
- package/dist/cli.js +4559 -4543
- package/dist/types/advisor/config.d.ts +14 -6
- package/dist/types/advisor/runtime.d.ts +20 -11
- package/dist/types/autolearn/controller.d.ts +1 -0
- package/dist/types/config/model-discovery.d.ts +17 -0
- package/dist/types/config/model-resolver.d.ts +6 -2
- package/dist/types/config/settings-schema.d.ts +39 -10
- package/dist/types/config/settings.d.ts +50 -0
- package/dist/types/cursor.d.ts +11 -0
- package/dist/types/discovery/helpers.d.ts +5 -2
- package/dist/types/eval/agent-bridge.d.ts +7 -19
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/managed-timers.d.ts +15 -0
- package/dist/types/extensibility/extensions/runner.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +18 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +55 -1
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/extensibility/utils.d.ts +2 -2
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/lsp/types.d.ts +3 -0
- package/dist/types/mcp/transports/stdio.d.ts +42 -1
- package/dist/types/modes/components/advisor-config.d.ts +8 -1
- package/dist/types/modes/components/agent-hub.d.ts +15 -0
- package/dist/types/modes/components/hook-editor.d.ts +7 -0
- package/dist/types/modes/components/model-hub.d.ts +5 -2
- package/dist/types/modes/components/session-selector.d.ts +2 -0
- package/dist/types/modes/controllers/command-controller.d.ts +8 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/print-mode.d.ts +1 -1
- package/dist/types/modes/warp-events.d.ts +24 -0
- package/dist/types/modes/warp-events.test.d.ts +1 -0
- package/dist/types/plan-mode/model-transition.d.ts +47 -0
- package/dist/types/plan-mode/model-transition.test.d.ts +1 -0
- package/dist/types/registry/agent-lifecycle.d.ts +26 -1
- package/dist/types/registry/persisted-agents.d.ts +3 -0
- package/dist/types/sdk.d.ts +27 -5
- package/dist/types/session/agent-session.d.ts +34 -10
- package/dist/types/session/session-entries.d.ts +6 -1
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-manager.d.ts +19 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
- package/dist/types/task/executor.d.ts +26 -1
- package/dist/types/task/index.d.ts +1 -1
- package/dist/types/task/label.d.ts +1 -1
- package/dist/types/task/parallel.d.ts +14 -0
- package/dist/types/task/structured-subagent.d.ts +111 -0
- package/dist/types/task/types.d.ts +51 -0
- package/dist/types/telemetry-export.d.ts +34 -9
- package/dist/types/tools/approval.d.ts +8 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/essential-tools.d.ts +29 -0
- package/dist/types/tools/fetch.d.ts +4 -5
- package/dist/types/tools/hub/messaging.d.ts +5 -1
- package/dist/types/tools/image-gen.d.ts +2 -1
- package/dist/types/tools/index.d.ts +17 -1
- package/dist/types/tools/todo.d.ts +31 -0
- package/dist/types/tools/xdev.d.ts +11 -2
- package/dist/types/tui/tree-list.d.ts +7 -0
- package/dist/types/utils/markit.d.ts +11 -0
- package/dist/types/utils/title-generator.d.ts +2 -1
- package/dist/types/web/search/providers/kimi.d.ts +4 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +21 -16
- package/src/advisor/__tests__/advisor.test.ts +1304 -42
- package/src/advisor/__tests__/config.test.ts +58 -2
- package/src/advisor/config.ts +76 -24
- package/src/advisor/runtime.ts +445 -92
- package/src/autolearn/controller.ts +23 -28
- package/src/cli/file-processor.ts +1 -2
- package/src/cli.ts +5 -1
- package/src/config/model-discovery.ts +81 -21
- package/src/config/model-registry.ts +25 -6
- package/src/config/model-resolver.ts +14 -7
- package/src/config/settings-schema.ts +46 -9
- package/src/config/settings.ts +405 -25
- package/src/cursor.ts +20 -3
- package/src/debug/report-bundle.ts +40 -4
- package/src/discovery/helpers.ts +28 -5
- package/src/eval/__tests__/agent-bridge.test.ts +133 -47
- package/src/eval/__tests__/prelude-agent.test.ts +29 -0
- package/src/eval/agent-bridge.ts +104 -477
- package/src/eval/jl/prelude.jl +7 -6
- package/src/eval/js/shared/prelude.txt +5 -4
- package/src/eval/py/prelude.py +11 -39
- package/src/eval/rb/prelude.rb +5 -6
- package/src/exec/bash-executor.ts +14 -5
- package/src/extensibility/custom-tools/loader.ts +3 -3
- package/src/extensibility/custom-tools/wrapper.ts +2 -1
- package/src/extensibility/extensions/loader.ts +3 -3
- package/src/extensibility/extensions/managed-timers.ts +83 -0
- package/src/extensibility/extensions/runner.ts +26 -0
- package/src/extensibility/extensions/types.ts +18 -0
- package/src/extensibility/extensions/wrapper.ts +2 -1
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +149 -8
- package/src/extensibility/plugins/legacy-pi-compat.ts +225 -22
- package/src/extensibility/plugins/manager.ts +2 -2
- package/src/extensibility/shared-events.ts +6 -0
- package/src/extensibility/utils.ts +91 -25
- package/src/irc/bus.ts +22 -3
- package/src/launch/broker.ts +3 -2
- package/src/launch/client.ts +2 -2
- package/src/launch/presence.ts +2 -2
- package/src/lsp/client.ts +58 -1
- package/src/lsp/index.ts +62 -6
- package/src/lsp/types.ts +3 -0
- package/src/main.ts +11 -8
- package/src/mcp/manager.ts +9 -3
- package/src/mcp/oauth-flow.ts +20 -0
- package/src/mcp/transports/stdio.test.ts +269 -1
- package/src/mcp/transports/stdio.ts +255 -24
- package/src/modes/components/advisor-config.ts +65 -3
- package/src/modes/components/agent-hub.ts +1 -72
- package/src/modes/components/ask-dialog.ts +1 -1
- package/src/modes/components/bash-execution.ts +7 -3
- package/src/modes/components/eval-execution.ts +3 -1
- package/src/modes/components/hook-editor.ts +18 -3
- package/src/modes/components/model-hub.ts +138 -42
- package/src/modes/components/session-selector.ts +4 -0
- package/src/modes/components/status-line/component.test.ts +1 -0
- package/src/modes/components/status-line/segments.ts +21 -6
- package/src/modes/controllers/command-controller.ts +167 -47
- package/src/modes/controllers/event-controller.ts +5 -0
- package/src/modes/controllers/extension-ui-controller.ts +4 -22
- package/src/modes/controllers/input-controller.ts +12 -12
- package/src/modes/controllers/selector-controller.ts +191 -31
- package/src/modes/interactive-mode.ts +169 -62
- package/src/modes/print-mode.ts +3 -3
- package/src/modes/rpc/host-tools.ts +2 -1
- package/src/modes/rpc/rpc-mode.ts +19 -4
- package/src/modes/warp-events.test.ts +794 -0
- package/src/modes/warp-events.ts +232 -0
- package/src/plan-mode/model-transition.test.ts +60 -0
- package/src/plan-mode/model-transition.ts +51 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/tools/eval.md +5 -2
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/task.md +12 -8
- package/src/prompts/tools/write.md +1 -1
- package/src/registry/agent-lifecycle.ts +133 -18
- package/src/registry/persisted-agents.ts +74 -0
- package/src/sdk.ts +343 -122
- package/src/session/agent-session.ts +1359 -368
- package/src/session/session-entries.ts +6 -1
- package/src/session/session-history-format.ts +20 -5
- package/src/session/session-manager.ts +57 -0
- package/src/session/streaming-output.ts +41 -1
- package/src/slash-commands/builtin-registry.ts +7 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
- package/src/system-prompt.ts +7 -2
- package/src/task/executor.ts +100 -22
- package/src/task/index.ts +258 -429
- package/src/task/label.ts +2 -0
- package/src/task/parallel.ts +43 -0
- package/src/task/persisted-revive.ts +19 -7
- package/src/task/structured-subagent.ts +642 -0
- package/src/task/types.ts +58 -0
- package/src/telemetry-export.ts +453 -97
- package/src/tools/__tests__/eval-description.test.ts +1 -1
- package/src/tools/approval.ts +11 -0
- package/src/tools/bash.ts +71 -38
- package/src/tools/essential-tools.ts +45 -0
- package/src/tools/fetch.ts +28 -105
- package/src/tools/gh.ts +169 -2
- package/src/tools/hub/messaging.ts +16 -3
- package/src/tools/image-gen.ts +69 -7
- package/src/tools/index.ts +50 -15
- package/src/tools/path-utils.ts +1 -0
- package/src/tools/read.ts +62 -29
- package/src/tools/todo.ts +126 -13
- package/src/tools/write.ts +22 -4
- package/src/tools/xdev.ts +14 -3
- package/src/tui/tree-list.ts +39 -0
- package/src/utils/markit.ts +12 -0
- package/src/utils/title-generator.ts +15 -4
- package/src/utils/zip.ts +16 -1
- package/src/web/search/providers/kimi.ts +18 -12
- package/src/web/search/types.ts +6 -1
package/src/discovery/helpers.ts
CHANGED
|
@@ -741,13 +741,16 @@ export function buildExtensionModuleItems(
|
|
|
741
741
|
* Entry for an installed Claude Code plugin.
|
|
742
742
|
*/
|
|
743
743
|
export interface ClaudePluginEntry {
|
|
744
|
-
scope
|
|
744
|
+
/** Claude registry scope; local entries are restricted to their project path. */
|
|
745
|
+
scope?: "user" | "project" | "local";
|
|
745
746
|
installPath: string;
|
|
746
747
|
version: string;
|
|
747
748
|
installedAt: string;
|
|
748
749
|
lastUpdated: string;
|
|
749
750
|
gitCommitSha?: string;
|
|
750
751
|
enabled?: boolean;
|
|
752
|
+
/** Project root recorded by Claude for a local installation. */
|
|
753
|
+
projectPath?: string;
|
|
751
754
|
}
|
|
752
755
|
|
|
753
756
|
/**
|
|
@@ -862,6 +865,14 @@ export async function resolveOrDefaultProjectRegistryPath(cwd: string): Promise<
|
|
|
862
865
|
return path.join(cwd, getConfigDirName(), "plugins", "installed_plugins.json");
|
|
863
866
|
}
|
|
864
867
|
|
|
868
|
+
async function canonicalClaudeProjectPath(projectPath: string): Promise<string | null> {
|
|
869
|
+
try {
|
|
870
|
+
return await fs.promises.realpath(path.resolve(projectPath));
|
|
871
|
+
} catch {
|
|
872
|
+
return null;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
865
876
|
const pluginRootsCache = new Map<string, { roots: ClaudePluginRoot[]; warnings: string[] }>();
|
|
866
877
|
|
|
867
878
|
const pluginCacheInvalidators = new Set<() => void>();
|
|
@@ -876,20 +887,23 @@ export function registerPluginCacheInvalidator(invalidator: () => void): void {
|
|
|
876
887
|
* Reads ~/.claude/plugins/installed_plugins.json and ~/.omp/plugins/installed_plugins.json,
|
|
877
888
|
* and optionally the nearest project-scoped registry resolved from `cwd`.
|
|
878
889
|
*
|
|
879
|
-
* Results are cached per
|
|
890
|
+
* Results are cached per home, project registry, and canonical active project.
|
|
880
891
|
*/
|
|
881
892
|
export async function listClaudePluginRoots(
|
|
882
893
|
home: string,
|
|
883
894
|
cwd?: string,
|
|
884
895
|
): Promise<{ roots: ClaudePluginRoot[]; warnings: string[] }> {
|
|
885
896
|
const resolvedProjectPath = cwd ? await resolveActiveProjectRegistryPath(cwd) : null;
|
|
886
|
-
const
|
|
897
|
+
const projectRoot = resolvedProjectPath ? path.dirname(path.dirname(path.dirname(resolvedProjectPath))) : cwd;
|
|
898
|
+
const activeClaudeProjectPath = projectRoot ? await canonicalClaudeProjectPath(projectRoot) : null;
|
|
899
|
+
const cacheKey = `${home}:${resolvedProjectPath ?? ""}:${activeClaudeProjectPath ?? ""}`;
|
|
887
900
|
const cached = pluginRootsCache.get(cacheKey);
|
|
888
901
|
if (cached) return cached;
|
|
889
902
|
|
|
890
903
|
const roots: ClaudePluginRoot[] = [];
|
|
891
904
|
const warnings: string[] = [];
|
|
892
905
|
const projectRoots: ClaudePluginRoot[] = [];
|
|
906
|
+
const canonicalClaudeProjectPaths = new Map<string, string | null>();
|
|
893
907
|
|
|
894
908
|
// ── Claude Code registry ──────────────────────────────────────────────────
|
|
895
909
|
const registryPath = path.join(home, ".claude", "plugins", "installed_plugins.json");
|
|
@@ -921,6 +935,15 @@ export async function listClaudePluginRoots(
|
|
|
921
935
|
continue;
|
|
922
936
|
}
|
|
923
937
|
if (entry.enabled === false) continue;
|
|
938
|
+
if (entry.scope === "local") {
|
|
939
|
+
if (!entry.projectPath || !activeClaudeProjectPath) continue;
|
|
940
|
+
let entryProjectPath = canonicalClaudeProjectPaths.get(entry.projectPath);
|
|
941
|
+
if (entryProjectPath === undefined) {
|
|
942
|
+
entryProjectPath = await canonicalClaudeProjectPath(entry.projectPath);
|
|
943
|
+
canonicalClaudeProjectPaths.set(entry.projectPath, entryProjectPath);
|
|
944
|
+
}
|
|
945
|
+
if (entryProjectPath !== activeClaudeProjectPath) continue;
|
|
946
|
+
}
|
|
924
947
|
|
|
925
948
|
roots.push({
|
|
926
949
|
id: pluginId,
|
|
@@ -928,7 +951,7 @@ export async function listClaudePluginRoots(
|
|
|
928
951
|
plugin: pluginName,
|
|
929
952
|
version: entry.version || "unknown",
|
|
930
953
|
path: entry.installPath,
|
|
931
|
-
scope: entry.scope || "user",
|
|
954
|
+
scope: entry.scope === "local" ? "project" : entry.scope || "user",
|
|
932
955
|
});
|
|
933
956
|
}
|
|
934
957
|
}
|
|
@@ -976,7 +999,7 @@ export async function listClaudePluginRoots(
|
|
|
976
999
|
plugin: pluginName,
|
|
977
1000
|
version: entry.version || "unknown",
|
|
978
1001
|
path: entry.installPath,
|
|
979
|
-
scope: entry.scope || "user",
|
|
1002
|
+
scope: entry.scope === "local" ? "project" : entry.scope || "user",
|
|
980
1003
|
});
|
|
981
1004
|
}
|
|
982
1005
|
}
|
|
@@ -13,9 +13,9 @@ import type { ExecutorOptions } from "../../task/executor";
|
|
|
13
13
|
import * as taskExecutor from "../../task/executor";
|
|
14
14
|
import * as isolationRunner from "../../task/isolation-runner";
|
|
15
15
|
import { AgentOutputManager } from "../../task/output-manager";
|
|
16
|
-
import type { AgentDefinition, AgentProgress, SingleResult } from "../../task/types";
|
|
16
|
+
import type { AgentDefinition, AgentProgress, SingleResult, StructuredSubagentOutput } from "../../task/types";
|
|
17
17
|
import type { ToolSession } from "../../tools";
|
|
18
|
-
import {
|
|
18
|
+
import { runEvalAgent } from "../agent-bridge";
|
|
19
19
|
import { EVAL_TIMEOUT_PAUSE_OP, EVAL_TIMEOUT_RESUME_OP } from "../bridge-timeout";
|
|
20
20
|
import { IdleTimeout } from "../idle-timeout";
|
|
21
21
|
import { disposeAllVmContexts } from "../js/context-manager";
|
|
@@ -51,6 +51,7 @@ interface SessionOptions {
|
|
|
51
51
|
settings?: Settings;
|
|
52
52
|
outputManager?: AgentOutputManager;
|
|
53
53
|
planMode?: boolean;
|
|
54
|
+
outputSchema?: unknown;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
function makeSession(options: SessionOptions = {}): ToolSession {
|
|
@@ -76,6 +77,7 @@ function makeSession(options: SessionOptions = {}): ToolSession {
|
|
|
76
77
|
getArtifactsDir: () => artifactsDir,
|
|
77
78
|
getSessionId: () => "test-session",
|
|
78
79
|
getEvalSessionId: () => "test-eval-session",
|
|
80
|
+
outputSchema: options.outputSchema,
|
|
79
81
|
getPlanModeState: options.planMode
|
|
80
82
|
? () =>
|
|
81
83
|
({
|
|
@@ -189,7 +191,7 @@ describe("runEvalAgent", () => {
|
|
|
189
191
|
);
|
|
190
192
|
});
|
|
191
193
|
|
|
192
|
-
it("enforces spawn restrictions
|
|
194
|
+
it("enforces shared spawn restrictions", async () => {
|
|
193
195
|
mockAgents();
|
|
194
196
|
const runSpy = vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => singleResult(options));
|
|
195
197
|
|
|
@@ -199,9 +201,6 @@ describe("runEvalAgent", () => {
|
|
|
199
201
|
await expect(
|
|
200
202
|
runEvalAgent({ prompt: "hello", agent: "task" }, { session: makeSession({ spawns: "reviewer" }) }),
|
|
201
203
|
).rejects.toThrow("Allowed: reviewer");
|
|
202
|
-
await expect(
|
|
203
|
-
runEvalAgent({ prompt: "hello" }, { session: makeSession({ depth: EVAL_AGENT_MAX_DEPTH }) }),
|
|
204
|
-
).rejects.toThrow("maximum depth");
|
|
205
204
|
expect(runSpy).not.toHaveBeenCalled();
|
|
206
205
|
});
|
|
207
206
|
|
|
@@ -219,12 +218,10 @@ describe("runEvalAgent", () => {
|
|
|
219
218
|
expect(runSpy.mock.calls[0]?.[0].agent.name).toBe("reviewer");
|
|
220
219
|
});
|
|
221
220
|
|
|
222
|
-
it("honors task.maxRecursionDepth
|
|
221
|
+
it("honors task.maxRecursionDepth without an eval-specific ceiling", async () => {
|
|
223
222
|
mockAgents();
|
|
224
223
|
const runSpy = vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => singleResult(options));
|
|
225
224
|
|
|
226
|
-
// task.maxRecursionDepth=0 means "no spawning at all" — even depth 0 (the
|
|
227
|
-
// top-level agent) must be blocked, matching canSpawnAtDepth().
|
|
228
225
|
await expect(
|
|
229
226
|
runEvalAgent(
|
|
230
227
|
{ prompt: "hello" },
|
|
@@ -240,35 +237,38 @@ describe("runEvalAgent", () => {
|
|
|
240
237
|
),
|
|
241
238
|
).rejects.toThrow("maximum depth is 0");
|
|
242
239
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
"async.enabled": false,
|
|
253
|
-
"task.isolation.mode": "none",
|
|
254
|
-
"task.maxRecursionDepth": 1,
|
|
255
|
-
}),
|
|
240
|
+
await runEvalAgent(
|
|
241
|
+
{ prompt: "hello" },
|
|
242
|
+
{
|
|
243
|
+
session: makeSession({
|
|
244
|
+
depth: 3,
|
|
245
|
+
settings: Settings.isolated({
|
|
246
|
+
"async.enabled": false,
|
|
247
|
+
"task.isolation.mode": "none",
|
|
248
|
+
"task.maxRecursionDepth": -1,
|
|
256
249
|
}),
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
expect(runSpy).not.toHaveBeenCalled();
|
|
250
|
+
}),
|
|
251
|
+
},
|
|
252
|
+
);
|
|
253
|
+
expect(runSpy).toHaveBeenCalledTimes(1);
|
|
262
254
|
});
|
|
263
255
|
|
|
264
|
-
it("
|
|
265
|
-
mockAgents();
|
|
256
|
+
it("runs plan-mode eval agents with an attenuated policy", async () => {
|
|
257
|
+
mockAgents([{ ...taskAgent, tools: ["ast_grep", "write"] }]);
|
|
266
258
|
const runSpy = vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => singleResult(options));
|
|
267
259
|
|
|
268
|
-
await expect(
|
|
269
|
-
"
|
|
270
|
-
)
|
|
271
|
-
|
|
260
|
+
await expect(
|
|
261
|
+
runEvalAgent({ prompt: "hello" }, { session: makeSession({ planMode: true }) }),
|
|
262
|
+
).resolves.toMatchObject({
|
|
263
|
+
text: "ok",
|
|
264
|
+
});
|
|
265
|
+
expect(runSpy).toHaveBeenCalledTimes(1);
|
|
266
|
+
expect(runSpy.mock.calls[0]?.[0].agent.tools).toEqual(["read", "grep", "glob", "web_search", "ast_grep"]);
|
|
267
|
+
expect(runSpy.mock.calls[0]?.[0].agent.spawns).toBeUndefined();
|
|
268
|
+
await expect(
|
|
269
|
+
runEvalAgent({ prompt: "unsafe", isolated: true }, { session: makeSession({ planMode: true }) }),
|
|
270
|
+
).rejects.toThrow("isolation, apply, and merge controls are unavailable in plan mode");
|
|
271
|
+
expect(runSpy).toHaveBeenCalledTimes(1);
|
|
272
272
|
});
|
|
273
273
|
|
|
274
274
|
it("passes parent execution options and only sets outputSchema when schema is supplied", async () => {
|
|
@@ -310,8 +310,52 @@ describe("runEvalAgent", () => {
|
|
|
310
310
|
expect(secondOptions.outputSchema).toBeUndefined();
|
|
311
311
|
expect(secondOptions.outputSchemaOverridesAgent).toBeUndefined();
|
|
312
312
|
});
|
|
313
|
+
it("returns host-parsed data for caller, agent, and inherited schemas", async () => {
|
|
314
|
+
const agentSchema = { type: "object" };
|
|
315
|
+
const sessionSchema = { type: "object" };
|
|
316
|
+
const callerSchema = { type: "object" };
|
|
317
|
+
const frontmatterAgent = { ...reviewerAgent, name: "structured", output: agentSchema };
|
|
318
|
+
mockAgents([taskAgent, frontmatterAgent]);
|
|
319
|
+
const runSpy = vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => {
|
|
320
|
+
const source = options.outputSchemaOverridesAgent
|
|
321
|
+
? "caller"
|
|
322
|
+
: options.agent.name === "structured"
|
|
323
|
+
? "agent"
|
|
324
|
+
: "session";
|
|
325
|
+
const structuredOutput: StructuredSubagentOutput = {
|
|
326
|
+
source,
|
|
327
|
+
mode: options.outputSchemaMode ?? "permissive",
|
|
328
|
+
status: "valid",
|
|
329
|
+
data: { source },
|
|
330
|
+
};
|
|
331
|
+
return singleResult(options, { output: "not JSON", structuredOutput });
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const caller = await runEvalAgent(
|
|
335
|
+
{ prompt: "caller", schema: callerSchema, schemaMode: "strict" },
|
|
336
|
+
{ session: makeSession({ outputSchema: sessionSchema }) },
|
|
337
|
+
);
|
|
338
|
+
const frontmatter = await runEvalAgent(
|
|
339
|
+
{ prompt: "agent", agent: "structured" },
|
|
340
|
+
{ session: makeSession({ outputSchema: sessionSchema }) },
|
|
341
|
+
);
|
|
342
|
+
const inherited = await runEvalAgent(
|
|
343
|
+
{ prompt: "session" },
|
|
344
|
+
{ session: makeSession({ outputSchema: sessionSchema }) },
|
|
345
|
+
);
|
|
313
346
|
|
|
314
|
-
|
|
347
|
+
expect(caller.data).toEqual({ source: "caller" });
|
|
348
|
+
expect(caller.details).toMatchObject({ schemaSource: "caller", schemaMode: "strict", schemaStatus: "valid" });
|
|
349
|
+
expect(frontmatter.data).toEqual({ source: "agent" });
|
|
350
|
+
expect(inherited.data).toEqual({ source: "session" });
|
|
351
|
+
expect(runSpy.mock.calls.map(([options]) => options.outputSchema)).toEqual([
|
|
352
|
+
callerSchema,
|
|
353
|
+
agentSchema,
|
|
354
|
+
sessionSchema,
|
|
355
|
+
]);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("inherits non-plan LSP and IRC policy for bridge subagents", async () => {
|
|
315
359
|
mockAgents();
|
|
316
360
|
const runSpy = vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => singleResult(options));
|
|
317
361
|
// makeSession() defaults to enableLsp: true and task.enableLsp: true.
|
|
@@ -321,7 +365,8 @@ describe("runEvalAgent", () => {
|
|
|
321
365
|
|
|
322
366
|
const options = runSpy.mock.calls[0]?.[0];
|
|
323
367
|
if (!options) throw new Error("runSubprocess was not called");
|
|
324
|
-
expect(options.enableLsp).toBe(
|
|
368
|
+
expect(options.enableLsp).toBe(true);
|
|
369
|
+
expect(options.enableIrc).toBe(true);
|
|
325
370
|
expect(options.keepAlive).toBe(false);
|
|
326
371
|
});
|
|
327
372
|
|
|
@@ -485,16 +530,30 @@ describe("agent() through eval runtimes", () => {
|
|
|
485
530
|
vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options =>
|
|
486
531
|
singleResult(options, {
|
|
487
532
|
output: options.outputSchema ? '{"ok":true,"n":3}' : "hello from agent",
|
|
533
|
+
...(options.outputSchema
|
|
534
|
+
? {
|
|
535
|
+
structuredOutput: {
|
|
536
|
+
source: "caller",
|
|
537
|
+
mode: options.outputSchemaMode ?? "permissive",
|
|
538
|
+
status: "valid",
|
|
539
|
+
data: { ok: true, n: 3 },
|
|
540
|
+
} satisfies StructuredSubagentOutput,
|
|
541
|
+
}
|
|
542
|
+
: {}),
|
|
488
543
|
}),
|
|
489
544
|
);
|
|
490
545
|
|
|
491
546
|
const result = await executeJs(
|
|
492
|
-
'const text = await agent("hi"); const data = await agent("json", { schema: { type: "object" } }); return JSON.stringify(
|
|
547
|
+
'const text = await agent("hi"); const data = await agent("json", { schema: { type: "object" } }); const node = await agent("handle", { schema: { type: "object" }, handle: true }); return JSON.stringify({ text, data, node });',
|
|
493
548
|
{ cwd: tempDir.path(), sessionId: sharedJsSessionId, session, sessionFile },
|
|
494
549
|
);
|
|
495
550
|
|
|
496
551
|
expect(result.exitCode).toBe(0);
|
|
497
|
-
|
|
552
|
+
const output = JSON.parse(result.output.trim());
|
|
553
|
+
expect(output.text).toBe("hello from agent");
|
|
554
|
+
expect(output.data).toEqual({ ok: true, n: 3 });
|
|
555
|
+
expect(output.node.data).toEqual({ ok: true, n: 3 });
|
|
556
|
+
expect(output.node.handle).toBe(`agent://${output.node.id}`);
|
|
498
557
|
});
|
|
499
558
|
|
|
500
559
|
it("bounds JavaScript parallel() by the task.maxConcurrency setting while preserving order", async () => {
|
|
@@ -547,23 +606,43 @@ describe("agent() through eval runtimes", () => {
|
|
|
547
606
|
const { session, sessionFile, sessionId } = makeEvalSession(tempDir, "py-agent");
|
|
548
607
|
mockAgents();
|
|
549
608
|
vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options =>
|
|
550
|
-
singleResult(options, {
|
|
609
|
+
singleResult(options, {
|
|
610
|
+
output: options.outputSchema ? "not JSON" : "hello from python",
|
|
611
|
+
...(options.outputSchema
|
|
612
|
+
? {
|
|
613
|
+
structuredOutput: {
|
|
614
|
+
source: "caller",
|
|
615
|
+
mode: options.outputSchemaMode ?? "permissive",
|
|
616
|
+
status: "valid",
|
|
617
|
+
data: { ok: true },
|
|
618
|
+
} satisfies StructuredSubagentOutput,
|
|
619
|
+
}
|
|
620
|
+
: {}),
|
|
621
|
+
}),
|
|
551
622
|
);
|
|
552
623
|
|
|
553
|
-
const result = await executePython(
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
624
|
+
const result = await executePython(
|
|
625
|
+
'import json\nprint(agent("hi"))\nprint(json.dumps(agent("structured", schema={"type": "object"})))\nnode = agent("handle", schema={"type": "object"}, handle=True)\nprint(json.dumps({"data": node["data"], "handle": node["handle"], "id": node["id"]}))',
|
|
626
|
+
{
|
|
627
|
+
cwd: tempDir.path(),
|
|
628
|
+
sessionId,
|
|
629
|
+
sessionFile,
|
|
630
|
+
kernelMode: "per-call",
|
|
631
|
+
toolSession: session,
|
|
632
|
+
},
|
|
633
|
+
);
|
|
560
634
|
if (result.exitCode === undefined && result.cancelled) {
|
|
561
635
|
expect(result.output).toBe("");
|
|
562
636
|
return; // kernel unavailable in this environment
|
|
563
637
|
}
|
|
564
638
|
|
|
565
639
|
expect(result.exitCode).toBe(0);
|
|
566
|
-
|
|
640
|
+
const lines = result.output.trim().split("\n");
|
|
641
|
+
expect(lines[0]).toBe("hello from python");
|
|
642
|
+
expect(JSON.parse(lines[1] ?? "")).toEqual({ ok: true });
|
|
643
|
+
const node = JSON.parse(lines[2] ?? "");
|
|
644
|
+
expect(node.data).toEqual({ ok: true });
|
|
645
|
+
expect(node.handle).toBe(`agent://${node.id}`);
|
|
567
646
|
});
|
|
568
647
|
|
|
569
648
|
it("bounds Python parallel() by the task.maxConcurrency setting while preserving order", async () => {
|
|
@@ -772,7 +851,11 @@ describe("agent() through eval runtimes", () => {
|
|
|
772
851
|
|
|
773
852
|
it("pauses the idle watchdog while a quiet agent() runs past the budget", async () => {
|
|
774
853
|
using tempDir = TempDir.createSync("@omp-eval-agent-timeout-pause-");
|
|
775
|
-
const { session } = makeEvalSession(
|
|
854
|
+
const { session } = makeEvalSession(
|
|
855
|
+
tempDir,
|
|
856
|
+
"js-agent-timeout-pause",
|
|
857
|
+
Settings.isolated({ "task.maxRuntimeMs": 1 }),
|
|
858
|
+
);
|
|
776
859
|
mockAgents();
|
|
777
860
|
|
|
778
861
|
// runSubprocess runs far past the eval timeout budget and emits NO progress
|
|
@@ -788,7 +871,9 @@ describe("agent() through eval runtimes", () => {
|
|
|
788
871
|
const inFlight = new Promise<void>(resolve => {
|
|
789
872
|
markInFlight = resolve;
|
|
790
873
|
});
|
|
874
|
+
let observedMaxRuntimeMs: number | undefined;
|
|
791
875
|
vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => {
|
|
876
|
+
observedMaxRuntimeMs = options.maxRuntimeMs;
|
|
792
877
|
markInFlight?.();
|
|
793
878
|
await released;
|
|
794
879
|
return singleResult(options, { output: "done" });
|
|
@@ -812,6 +897,7 @@ describe("agent() through eval runtimes", () => {
|
|
|
812
897
|
|
|
813
898
|
// The bridge paused the watchdog; the subprocess is now blocked in flight.
|
|
814
899
|
await inFlight;
|
|
900
|
+
expect(observedMaxRuntimeMs).toBe(0);
|
|
815
901
|
// Burn far more than the 20ms budget while paused: the watchdog stays armed-off.
|
|
816
902
|
vi.advanceTimersByTime(1_000);
|
|
817
903
|
expect(idle.signal.aborted).toBe(false);
|
|
@@ -53,6 +53,35 @@ describe("eval js agent() handle", () => {
|
|
|
53
53
|
expect(out).toBe("hello world");
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
it("keeps positional isolation controls stable while appending schemaMode", async () => {
|
|
57
|
+
let seenArgs: Record<string, unknown> | undefined;
|
|
58
|
+
const sandbox = loadPrelude(async (_name, args) => {
|
|
59
|
+
seenArgs = args as Record<string, unknown>;
|
|
60
|
+
return { text: '{"ok":true}', details: { agent: "task", id: "legacy", structured: false } };
|
|
61
|
+
});
|
|
62
|
+
const positionalAgent = sandbox.agent as (
|
|
63
|
+
prompt: string,
|
|
64
|
+
options?: unknown,
|
|
65
|
+
...rest: unknown[]
|
|
66
|
+
) => Promise<unknown>;
|
|
67
|
+
const schema = { type: "object", properties: { ok: { type: "boolean" } } };
|
|
68
|
+
|
|
69
|
+
await positionalAgent("scout", "reviewer", "p/model", "Legacy", schema, true, false, true, "strict");
|
|
70
|
+
|
|
71
|
+
expect(seenArgs).toEqual({
|
|
72
|
+
prompt: "scout",
|
|
73
|
+
agent: "reviewer",
|
|
74
|
+
model: "p/model",
|
|
75
|
+
label: "Legacy",
|
|
76
|
+
schema,
|
|
77
|
+
isolated: true,
|
|
78
|
+
apply: false,
|
|
79
|
+
merge: true,
|
|
80
|
+
schemaMode: "strict",
|
|
81
|
+
handle: false,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
56
85
|
it("carries the parsed object under data when schema and handle combine", async () => {
|
|
57
86
|
const payload = JSON.stringify({ k: 1 });
|
|
58
87
|
const sandbox = loadPrelude(async () => ({
|