@desplega.ai/agent-swarm 1.93.0 → 1.94.0
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/README.md +2 -2
- package/openapi.json +180 -1
- package/package.json +1 -1
- package/src/be/db.ts +63 -7
- package/src/be/migrations/090_model_tiers.sql +2 -0
- package/src/be/migrations/091_seed_swarm_operations_metrics.sql +12 -0
- package/src/be/migrations/092_metrics_dashboard_combobox_filters.sql +68 -0
- package/src/be/migrations/093_slack_message_tracking.sql +6 -0
- package/src/be/migrations/runner.ts +52 -0
- package/src/be/modelsdev-cache.json +2060 -198
- package/src/be/scripts/boot-reembed.ts +74 -0
- package/src/be/scripts/db.ts +19 -3
- package/src/be/seed/index.ts +1 -1
- package/src/be/seed/registry.ts +2 -2
- package/src/be/seed/runner.ts +5 -5
- package/src/be/seed/types.ts +6 -1
- package/src/be/seed-pricing.ts +1 -0
- package/src/be/seed-scripts/index.ts +3 -2
- package/src/commands/runner.ts +83 -13
- package/src/http/index.ts +13 -2
- package/src/http/metrics.ts +55 -6
- package/src/http/schedules.ts +16 -15
- package/src/http/script-runs.ts +7 -1
- package/src/http/scripts.ts +147 -1
- package/src/http/tasks.ts +7 -0
- package/src/model-tiers.ts +140 -0
- package/src/providers/claude-managed-models.ts +9 -0
- package/src/providers/opencode-adapter.ts +1 -0
- package/src/providers/pi-mono-adapter.ts +78 -6
- package/src/scheduler/scheduler.ts +22 -34
- package/src/server-user.ts +8 -2
- package/src/slack/responses.ts +39 -11
- package/src/slack/watcher.ts +121 -8
- package/src/tests/agents-list-model-display.test.ts +13 -0
- package/src/tests/aws-error-classifier.test.ts +148 -0
- package/src/tests/claude-managed-adapter.test.ts +12 -0
- package/src/tests/context-window.test.ts +7 -0
- package/src/tests/http-api-integration.test.ts +19 -0
- package/src/tests/metrics-http.test.ts +137 -3
- package/src/tests/migration-046-budgets.test.ts +33 -0
- package/src/tests/migration-runner-regressions.test.ts +69 -0
- package/src/tests/model-control.test.ts +162 -46
- package/src/tests/opencode-adapter.test.ts +9 -0
- package/src/tests/pi-mono-adapter.test.ts +319 -0
- package/src/tests/providers/pi-cost.test.ts +9 -0
- package/src/tests/runner-fallback-output.test.ts +50 -0
- package/src/tests/scripts-boot-reembed.test.ts +163 -0
- package/src/tests/scripts-embeddings.test.ts +90 -0
- package/src/tests/seed.test.ts +26 -1
- package/src/tests/session-costs-model-key-normalize.test.ts +2 -0
- package/src/tests/slack-watcher.test.ts +66 -0
- package/src/tests/workflow-agent-task.test.ts +5 -2
- package/src/tests/workflow-validation-port-routing.test.ts +181 -0
- package/src/tools/memory-get.ts +11 -0
- package/src/tools/memory-search.ts +18 -0
- package/src/tools/schedules/create-schedule.ts +71 -70
- package/src/tools/schedules/update-schedule.ts +43 -31
- package/src/tools/send-task.ts +16 -5
- package/src/tools/task-action.ts +11 -3
- package/src/types.ts +29 -0
- package/src/utils/aws-error-classifier.ts +97 -0
- package/src/utils/context-window.ts +2 -0
- package/src/utils/credentials.test.ts +68 -0
- package/src/utils/credentials.ts +44 -3
- package/src/utils/pretty-print.ts +25 -10
- package/src/workflows/engine.ts +3 -2
- package/src/workflows/executors/agent-task.ts +3 -1
package/src/http/script-runs.ts
CHANGED
|
@@ -51,6 +51,7 @@ const createScriptRunBodySchema = z.object({
|
|
|
51
51
|
const listScriptRunsQuerySchema = z.object({
|
|
52
52
|
status: ScriptRunStatusSchema.optional(),
|
|
53
53
|
agentId: z.string().optional(),
|
|
54
|
+
scriptName: z.string().optional(),
|
|
54
55
|
limit: z.coerce.number().int().min(1).max(500).optional(),
|
|
55
56
|
offset: z.coerce.number().int().min(0).optional(),
|
|
56
57
|
});
|
|
@@ -367,12 +368,17 @@ export async function handleScriptRuns(
|
|
|
367
368
|
const opts = {
|
|
368
369
|
status: parsed.query.status,
|
|
369
370
|
agentId: parsed.query.agentId,
|
|
371
|
+
scriptName: parsed.query.scriptName,
|
|
370
372
|
limit: parsed.query.limit ?? 50,
|
|
371
373
|
offset: parsed.query.offset ?? 0,
|
|
372
374
|
};
|
|
373
375
|
json(res, {
|
|
374
376
|
runs: listScriptRuns(opts),
|
|
375
|
-
total: countScriptRuns({
|
|
377
|
+
total: countScriptRuns({
|
|
378
|
+
status: opts.status,
|
|
379
|
+
agentId: opts.agentId,
|
|
380
|
+
scriptName: opts.scriptName,
|
|
381
|
+
}),
|
|
376
382
|
});
|
|
377
383
|
return true;
|
|
378
384
|
}
|
package/src/http/scripts.ts
CHANGED
|
@@ -2,14 +2,23 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getAgentById, recordInlineScriptRun, upsertKv } from "../be/db";
|
|
4
4
|
import { createEvent } from "../be/events";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
deleteScript,
|
|
7
|
+
getScript,
|
|
8
|
+
getScriptById,
|
|
9
|
+
listScripts,
|
|
10
|
+
listScriptVersions,
|
|
11
|
+
upsertScriptByName,
|
|
12
|
+
} from "../be/scripts/db";
|
|
6
13
|
import { searchScripts } from "../be/scripts/embeddings";
|
|
7
14
|
import { extractArgsJsonSchema } from "../be/scripts/extract-schema";
|
|
8
15
|
import { SCRIPT_SDK_TYPES, SCRIPT_STDLIB_TYPES, typecheckScript } from "../be/scripts/typecheck";
|
|
9
16
|
import { extractScriptSignature } from "../scripts-runtime/extract-signature";
|
|
10
17
|
import { runScript } from "../scripts-runtime/loader";
|
|
11
18
|
import {
|
|
19
|
+
type ScriptDetail,
|
|
12
20
|
ScriptFsModeSchema,
|
|
21
|
+
type ScriptListItem,
|
|
13
22
|
type ScriptRecord,
|
|
14
23
|
type ScriptScope,
|
|
15
24
|
ScriptScopeSchema,
|
|
@@ -52,6 +61,11 @@ const searchBodySchema = z.object({
|
|
|
52
61
|
const nameParamsSchema = z.object({ name: scriptNameSchema });
|
|
53
62
|
const scopeQuerySchema = z.object({ scope: ScriptScopeSchema.default("agent") });
|
|
54
63
|
const optionalScopeQuerySchema = z.object({ scope: ScriptScopeSchema.optional() });
|
|
64
|
+
const idParamsSchema = z.object({ id: z.string().uuid() });
|
|
65
|
+
const listScriptsQuerySchema = z.object({
|
|
66
|
+
scope: ScriptScopeSchema.optional(),
|
|
67
|
+
includeScratch: z.enum(["true", "false"]).optional(),
|
|
68
|
+
});
|
|
55
69
|
|
|
56
70
|
const upsertRoute = route({
|
|
57
71
|
method: "post",
|
|
@@ -133,6 +147,74 @@ const typesRoute = route({
|
|
|
133
147
|
},
|
|
134
148
|
});
|
|
135
149
|
|
|
150
|
+
// ── Dashboard read routes ──
|
|
151
|
+
// The worker-facing routes above resolve scripts relative to the calling agent
|
|
152
|
+
// and therefore requireAgent (X-Agent-ID). The routes below are cross-scope
|
|
153
|
+
// admin reads for the dashboard: API-key auth only, no agent identity — the
|
|
154
|
+
// same model as /api/script-runs.
|
|
155
|
+
|
|
156
|
+
const listScriptsRoute = route({
|
|
157
|
+
method: "get",
|
|
158
|
+
path: "/api/scripts",
|
|
159
|
+
pattern: ["api", "scripts"],
|
|
160
|
+
operationId: "scripts_list",
|
|
161
|
+
summary: "List saved scripts",
|
|
162
|
+
description:
|
|
163
|
+
"Dashboard read: lean projection without source. Scratch scripts are excluded unless includeScratch=true.",
|
|
164
|
+
tags: ["Scripts"],
|
|
165
|
+
query: listScriptsQuerySchema,
|
|
166
|
+
responses: {
|
|
167
|
+
200: { description: "Saved scripts" },
|
|
168
|
+
400: { description: "Validation error" },
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Declared (and matched) BEFORE the by-id route: the by-id pattern
|
|
173
|
+
// ["api", "scripts", null] matches any single segment, so the literal
|
|
174
|
+
// "type-defs" segment must win first.
|
|
175
|
+
const typeDefsRoute = route({
|
|
176
|
+
method: "get",
|
|
177
|
+
path: "/api/scripts/type-defs",
|
|
178
|
+
pattern: ["api", "scripts", "type-defs"],
|
|
179
|
+
operationId: "scripts_type_defs",
|
|
180
|
+
summary: "Get script SDK and stdlib type definitions",
|
|
181
|
+
description: "Static .d.ts blobs for editor integration (e.g. Monaco extraLibs). Cacheable.",
|
|
182
|
+
tags: ["Scripts"],
|
|
183
|
+
responses: {
|
|
184
|
+
200: { description: "SDK and stdlib type definition blobs" },
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const getScriptByIdRoute = route({
|
|
189
|
+
method: "get",
|
|
190
|
+
path: "/api/scripts/{id}",
|
|
191
|
+
pattern: ["api", "scripts", null],
|
|
192
|
+
operationId: "scripts_get",
|
|
193
|
+
summary: "Get a saved script by id",
|
|
194
|
+
description: "Dashboard read: full record including source and parsed signature.",
|
|
195
|
+
tags: ["Scripts"],
|
|
196
|
+
params: idParamsSchema,
|
|
197
|
+
responses: {
|
|
198
|
+
200: { description: "Script detail" },
|
|
199
|
+
404: { description: "Script not found" },
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const listVersionsRoute = route({
|
|
204
|
+
method: "get",
|
|
205
|
+
path: "/api/scripts/{id}/versions",
|
|
206
|
+
pattern: ["api", "scripts", null, "versions"],
|
|
207
|
+
operationId: "scripts_versions",
|
|
208
|
+
summary: "List versions of a saved script",
|
|
209
|
+
description: "Dashboard read: version history, newest first.",
|
|
210
|
+
tags: ["Scripts"],
|
|
211
|
+
params: idParamsSchema,
|
|
212
|
+
responses: {
|
|
213
|
+
200: { description: "Script versions" },
|
|
214
|
+
404: { description: "Script not found" },
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
|
|
136
218
|
function requireAgent(res: ServerResponse, agentId: string | undefined) {
|
|
137
219
|
if (!agentId) {
|
|
138
220
|
jsonError(res, "X-Agent-ID required for scripts API", 400);
|
|
@@ -413,6 +495,70 @@ export async function handleScripts(
|
|
|
413
495
|
return true;
|
|
414
496
|
}
|
|
415
497
|
|
|
498
|
+
// ── Dashboard reads (no requireAgent — API-key auth only, like /api/script-runs) ──
|
|
499
|
+
|
|
500
|
+
if (listScriptsRoute.match(req.method, pathSegments)) {
|
|
501
|
+
const parsed = await listScriptsRoute.parse(req, res, pathSegments, queryParams);
|
|
502
|
+
if (!parsed) return true;
|
|
503
|
+
const scripts: ScriptListItem[] = listScripts({
|
|
504
|
+
scope: parsed.query.scope,
|
|
505
|
+
includeScratch: parsed.query.includeScratch === "true",
|
|
506
|
+
}).map((script) => ({
|
|
507
|
+
id: script.id,
|
|
508
|
+
name: script.name,
|
|
509
|
+
scope: script.scope,
|
|
510
|
+
scopeId: script.scopeId,
|
|
511
|
+
description: script.description,
|
|
512
|
+
intent: script.intent,
|
|
513
|
+
version: script.version,
|
|
514
|
+
isScratch: script.isScratch,
|
|
515
|
+
typeChecked: script.typeChecked,
|
|
516
|
+
fsMode: script.fsMode,
|
|
517
|
+
createdByAgentId: script.createdByAgentId,
|
|
518
|
+
createdAt: script.createdAt,
|
|
519
|
+
updatedAt: script.updatedAt,
|
|
520
|
+
}));
|
|
521
|
+
json(res, { scripts });
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Must be matched before getScriptByIdRoute — its ["api", "scripts", null]
|
|
526
|
+
// pattern would otherwise swallow the literal "type-defs" segment.
|
|
527
|
+
if (typeDefsRoute.match(req.method, pathSegments)) {
|
|
528
|
+
json(res, { sdkTypes: SCRIPT_SDK_TYPES, stdlibTypes: SCRIPT_STDLIB_TYPES });
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (getScriptByIdRoute.match(req.method, pathSegments)) {
|
|
533
|
+
const parsed = await getScriptByIdRoute.parse(req, res, pathSegments, queryParams);
|
|
534
|
+
if (!parsed) return true;
|
|
535
|
+
const script = getScriptById(parsed.params.id);
|
|
536
|
+
if (!script) {
|
|
537
|
+
jsonError(res, "Script not found", 404);
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
// `source` is author-supplied TS (same trust surface as script_runs.source,
|
|
541
|
+
// already served raw by GET /api/script-runs/{id}) — no env/secret material.
|
|
542
|
+
const detail: ScriptDetail = {
|
|
543
|
+
...script,
|
|
544
|
+
signature: JSON.parse(script.signatureJson) as unknown,
|
|
545
|
+
argsJsonSchema: script.argsJsonSchema ? (JSON.parse(script.argsJsonSchema) as unknown) : null,
|
|
546
|
+
};
|
|
547
|
+
json(res, { script: detail });
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (listVersionsRoute.match(req.method, pathSegments)) {
|
|
552
|
+
const parsed = await listVersionsRoute.parse(req, res, pathSegments, queryParams);
|
|
553
|
+
if (!parsed) return true;
|
|
554
|
+
if (!getScriptById(parsed.params.id)) {
|
|
555
|
+
jsonError(res, "Script not found", 404);
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
json(res, { versions: listScriptVersions(parsed.params.id) });
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
|
|
416
562
|
if (typesRoute.match(req.method, pathSegments)) {
|
|
417
563
|
const parsed = await typesRoute.parse(req, res, pathSegments, queryParams);
|
|
418
564
|
if (!parsed) return true;
|
package/src/http/tasks.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
updateTaskProgress,
|
|
24
24
|
updateTaskVcs,
|
|
25
25
|
} from "../be/db";
|
|
26
|
+
import { ModelTierSchema, splitLegacyModelAlias } from "../model-tiers";
|
|
26
27
|
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
27
28
|
import { createResumeFollowUp, createWorkerTaskFollowUp } from "../tasks/worker-follow-up";
|
|
28
29
|
import { telemetry } from "../telemetry";
|
|
@@ -91,6 +92,8 @@ const createTask = route({
|
|
|
91
92
|
outputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
92
93
|
contextKey: z.string().optional(),
|
|
93
94
|
requestedByUserId: z.string().optional(),
|
|
95
|
+
model: z.string().optional(),
|
|
96
|
+
modelTier: ModelTierSchema.optional(),
|
|
94
97
|
}),
|
|
95
98
|
responses: {
|
|
96
99
|
201: { description: "Task created" },
|
|
@@ -395,6 +398,10 @@ export async function handleTasks(
|
|
|
395
398
|
outputSchema: parsed.body.outputSchema || undefined,
|
|
396
399
|
contextKey: parsed.body.contextKey || undefined,
|
|
397
400
|
requestedByUserId,
|
|
401
|
+
...splitLegacyModelAlias({
|
|
402
|
+
model: parsed.body.model,
|
|
403
|
+
modelTier: parsed.body.modelTier,
|
|
404
|
+
}),
|
|
398
405
|
});
|
|
399
406
|
|
|
400
407
|
ensure({
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ProviderName } from "./types";
|
|
3
|
+
|
|
4
|
+
export const ModelTierSchema = z.enum(["smol", "regular", "smart", "ultra"]);
|
|
5
|
+
export type ModelTier = z.infer<typeof ModelTierSchema>;
|
|
6
|
+
|
|
7
|
+
export const MODEL_TIERS = ModelTierSchema.options;
|
|
8
|
+
|
|
9
|
+
export const LEGACY_MODEL_TO_TIER: Record<string, ModelTier> = {
|
|
10
|
+
haiku: "smol",
|
|
11
|
+
sonnet: "regular",
|
|
12
|
+
opus: "smart",
|
|
13
|
+
fable: "ultra",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const MODEL_TIER_LABELS: Record<ModelTier, string> = {
|
|
17
|
+
smol: "Smol",
|
|
18
|
+
regular: "Regular",
|
|
19
|
+
smart: "Smart",
|
|
20
|
+
ultra: "Ultra",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_MODEL_TIER_MAP: Record<ProviderName, Record<ModelTier, string>> = {
|
|
24
|
+
claude: {
|
|
25
|
+
smol: "haiku",
|
|
26
|
+
regular: "sonnet",
|
|
27
|
+
smart: "opus",
|
|
28
|
+
ultra: "fable",
|
|
29
|
+
},
|
|
30
|
+
"claude-managed": {
|
|
31
|
+
smol: "claude-haiku-4-5",
|
|
32
|
+
regular: "claude-sonnet-4-6",
|
|
33
|
+
smart: "claude-opus-4-8",
|
|
34
|
+
ultra: "claude-fable-5",
|
|
35
|
+
},
|
|
36
|
+
codex: {
|
|
37
|
+
smol: "gpt-5.4-mini",
|
|
38
|
+
regular: "gpt-5.4",
|
|
39
|
+
smart: "gpt-5.5",
|
|
40
|
+
ultra: "gpt-5.5",
|
|
41
|
+
},
|
|
42
|
+
pi: {
|
|
43
|
+
smol: "openrouter/deepseek/deepseek-v4-flash",
|
|
44
|
+
regular: "openrouter/deepseek/deepseek-v4-flash",
|
|
45
|
+
smart: "openrouter/deepseek/deepseek-v4-pro",
|
|
46
|
+
ultra: "openrouter/anthropic/claude-opus-4.8",
|
|
47
|
+
},
|
|
48
|
+
opencode: {
|
|
49
|
+
smol: "openrouter/deepseek/deepseek-v4-flash",
|
|
50
|
+
regular: "openrouter/deepseek/deepseek-v4-flash",
|
|
51
|
+
smart: "openrouter/deepseek/deepseek-v4-pro",
|
|
52
|
+
ultra: "openrouter/anthropic/claude-opus-4.8",
|
|
53
|
+
},
|
|
54
|
+
devin: {
|
|
55
|
+
smol: "devin",
|
|
56
|
+
regular: "devin",
|
|
57
|
+
smart: "devin",
|
|
58
|
+
ultra: "devin",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export function parseModelTier(value: string | null | undefined): ModelTier | undefined {
|
|
63
|
+
if (!value) return undefined;
|
|
64
|
+
const normalized = value.trim().toLowerCase();
|
|
65
|
+
return ModelTierSchema.safeParse(normalized).success
|
|
66
|
+
? (normalized as ModelTier)
|
|
67
|
+
: LEGACY_MODEL_TO_TIER[normalized];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function splitLegacyModelAlias(input: {
|
|
71
|
+
model?: string | null;
|
|
72
|
+
modelTier?: string | null;
|
|
73
|
+
}): { model?: string; modelTier?: ModelTier } {
|
|
74
|
+
const explicitTier = parseModelTier(input.modelTier);
|
|
75
|
+
const model = input.model?.trim();
|
|
76
|
+
if (!model) return { modelTier: explicitTier };
|
|
77
|
+
|
|
78
|
+
const legacyTier = parseModelTier(model);
|
|
79
|
+
if (legacyTier && !explicitTier) {
|
|
80
|
+
return { modelTier: legacyTier };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
model,
|
|
85
|
+
modelTier: explicitTier,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseTierMapJson(value: string | undefined): Partial<Record<ModelTier, string>> {
|
|
90
|
+
if (!value) return {};
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(value) as unknown;
|
|
93
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
94
|
+
const result: Partial<Record<ModelTier, string>> = {};
|
|
95
|
+
for (const tier of MODEL_TIERS) {
|
|
96
|
+
const model = (parsed as Record<string, unknown>)[tier];
|
|
97
|
+
if (typeof model === "string" && model.trim()) result[tier] = model.trim();
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
} catch {
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function resolveModelTier(opts: {
|
|
106
|
+
tier?: string | null;
|
|
107
|
+
harnessProvider: ProviderName;
|
|
108
|
+
env?: Record<string, string | undefined>;
|
|
109
|
+
}): string | undefined {
|
|
110
|
+
const tier = parseModelTier(opts.tier);
|
|
111
|
+
if (!tier) return undefined;
|
|
112
|
+
|
|
113
|
+
const env = opts.env ?? {};
|
|
114
|
+
const jsonOverrides = parseTierMapJson(env.MODEL_TIER_MAP);
|
|
115
|
+
const envKey = `MODEL_TIER_${tier.toUpperCase()}`;
|
|
116
|
+
const directOverride = env[envKey]?.trim();
|
|
117
|
+
if (directOverride) return directOverride;
|
|
118
|
+
if (jsonOverrides[tier]) return jsonOverrides[tier];
|
|
119
|
+
|
|
120
|
+
return DEFAULT_MODEL_TIER_MAP[opts.harnessProvider]?.[tier];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function resolveTaskModelSelection(opts: {
|
|
124
|
+
model?: string | null;
|
|
125
|
+
modelTier?: string | null;
|
|
126
|
+
harnessProvider: ProviderName;
|
|
127
|
+
env?: Record<string, string | undefined>;
|
|
128
|
+
}): { model?: string; source: "model" | "modelTier" | "none" } {
|
|
129
|
+
const model = opts.model?.trim();
|
|
130
|
+
if (model) return { model, source: "model" };
|
|
131
|
+
|
|
132
|
+
const tierModel = resolveModelTier({
|
|
133
|
+
tier: opts.modelTier,
|
|
134
|
+
harnessProvider: opts.harnessProvider,
|
|
135
|
+
env: opts.env,
|
|
136
|
+
});
|
|
137
|
+
if (tierModel) return { model: tierModel, source: "modelTier" };
|
|
138
|
+
|
|
139
|
+
return { source: "none" };
|
|
140
|
+
}
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
/** Models supported by the managed-agents surface for the swarm worker. */
|
|
27
27
|
export const CLAUDE_MANAGED_MODELS = [
|
|
28
28
|
"claude-fable-5",
|
|
29
|
+
"claude-mythos-5",
|
|
29
30
|
"claude-sonnet-4-6",
|
|
30
31
|
"claude-opus-4-8",
|
|
31
32
|
"claude-opus-4-7",
|
|
@@ -51,6 +52,8 @@ export interface ClaudeManagedModelPricing {
|
|
|
51
52
|
* Anthropic public list pricing. Source:
|
|
52
53
|
* https://platform.claude.com/docs/en/about-claude/pricing
|
|
53
54
|
*
|
|
55
|
+
* - claude-fable-5: $10 / $50 / $1.00 / $12.50 (verified 2026-06-10)
|
|
56
|
+
* - claude-mythos-5: $10 / $50 / $1.00 / $12.50 (limited availability, verified 2026-06-10)
|
|
54
57
|
* - claude-sonnet-4-6: $3 / $15 / $0.30 / $3.75 (in / out / cache-read / cache-write)
|
|
55
58
|
* - claude-opus-4-8: $5 / $25 / $0.50 / $6.25 (verified 2026-05-28)
|
|
56
59
|
* - claude-opus-4-7: $15 / $75 / $1.50 / $18.75 (STALE — was correct at launch, Anthropic has since dropped Opus to $5/$25)
|
|
@@ -64,6 +67,12 @@ export const CLAUDE_MANAGED_MODEL_PRICING: Record<ClaudeManagedModel, ClaudeMana
|
|
|
64
67
|
cacheReadPerMillion: 1.0,
|
|
65
68
|
cacheWritePerMillion: 12.5,
|
|
66
69
|
},
|
|
70
|
+
"claude-mythos-5": {
|
|
71
|
+
inputPerMillion: 10.0,
|
|
72
|
+
outputPerMillion: 50.0,
|
|
73
|
+
cacheReadPerMillion: 1.0,
|
|
74
|
+
cacheWritePerMillion: 12.5,
|
|
75
|
+
},
|
|
67
76
|
"claude-sonnet-4-6": {
|
|
68
77
|
inputPerMillion: 3.0,
|
|
69
78
|
outputPerMillion: 15.0,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
SessionManager,
|
|
26
26
|
} from "@earendil-works/pi-coding-agent";
|
|
27
27
|
import { type TSchema, Type } from "typebox";
|
|
28
|
+
import { classifyAwsSdkError } from "../utils/aws-error-classifier";
|
|
28
29
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
29
30
|
import { readPkgVersion } from "./harness-version";
|
|
30
31
|
import { createSwarmHooksExtension } from "./pi-mono-extension";
|
|
@@ -361,6 +362,18 @@ export class PiMonoSession implements ProviderSession {
|
|
|
361
362
|
* surface it directly.
|
|
362
363
|
*/
|
|
363
364
|
private prevOutputTokens = 0;
|
|
365
|
+
/**
|
|
366
|
+
* Terminal error message captured from structured pi-coding-agent events.
|
|
367
|
+
*
|
|
368
|
+
* Set by `message_end` (assistant turn with `stopReason==='error'` — covers
|
|
369
|
+
* NON-retryable failures, including AWS auth which never enters pi's retry
|
|
370
|
+
* loop) and by `auto_retry_end` with `success:false` (the definitive terminal
|
|
371
|
+
* failure after the retryable class — throttle / 5xx / timeout — exhausts).
|
|
372
|
+
* Cleared on recovery: a successful `message_end` or an `auto_retry_end` with
|
|
373
|
+
* `success:true` resets it to null, so a recovered error never surfaces as a
|
|
374
|
+
* false failure. Evaluated once at session end in `runSession()`.
|
|
375
|
+
*/
|
|
376
|
+
private terminalError: string | null = null;
|
|
364
377
|
|
|
365
378
|
constructor(agentSession: AgentSession, config: ProviderSessionConfig, createdSymlink: boolean) {
|
|
366
379
|
this.agentSession = agentSession;
|
|
@@ -376,6 +389,7 @@ export class PiMonoSession implements ProviderSession {
|
|
|
376
389
|
type: "session_init",
|
|
377
390
|
sessionId: this._sessionId,
|
|
378
391
|
provider: "pi",
|
|
392
|
+
harnessVariant: "stock",
|
|
379
393
|
...(piVersion ? { harnessVariantMeta: { version: piVersion } } : {}),
|
|
380
394
|
});
|
|
381
395
|
|
|
@@ -424,6 +438,25 @@ export class PiMonoSession implements ProviderSession {
|
|
|
424
438
|
switch (event.type) {
|
|
425
439
|
case "message_end": {
|
|
426
440
|
// Pi emits message_end for user, assistant, and tool-result messages.
|
|
441
|
+
// An assistant turn that ended in `stopReason==='error'` is a failed
|
|
442
|
+
// turn — track it as the (so far) terminal error. This is the ONLY
|
|
443
|
+
// structured signal for NON-retryable failures (AWS auth: ExpiredToken
|
|
444
|
+
// / CredentialsProviderError), which never enter pi's retry loop.
|
|
445
|
+
const endMsg = event.message as {
|
|
446
|
+
role?: string;
|
|
447
|
+
stopReason?: string;
|
|
448
|
+
errorMessage?: string;
|
|
449
|
+
};
|
|
450
|
+
if (endMsg.role === "assistant") {
|
|
451
|
+
if (endMsg.stopReason === "error") {
|
|
452
|
+
// Candidate terminal failure. May still be cleared by a successful
|
|
453
|
+
// retry (auto_retry_end success / a later good message_end).
|
|
454
|
+
this.terminalError = endMsg.errorMessage ?? this.terminalError ?? "Unknown error";
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
// A successful assistant turn means any prior error has recovered.
|
|
458
|
+
this.terminalError = null;
|
|
459
|
+
}
|
|
427
460
|
// Only assistant text should be printed or used as fallback output.
|
|
428
461
|
const text = extractPiAssistantText(event.message);
|
|
429
462
|
if (text) {
|
|
@@ -517,12 +550,18 @@ export class PiMonoSession implements ProviderSession {
|
|
|
517
550
|
result: event.result,
|
|
518
551
|
});
|
|
519
552
|
break;
|
|
520
|
-
case "
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
553
|
+
case "auto_retry_end": {
|
|
554
|
+
// Definitive terminal signal for the RETRYABLE error class
|
|
555
|
+
// (throttle / 5xx / timeout). pi-coding-agent emits success:false with
|
|
556
|
+
// `finalError` only after every retry attempt is exhausted; success:true
|
|
557
|
+
// means the turn recovered, so clear any tracked error.
|
|
558
|
+
if (event.success) {
|
|
559
|
+
this.terminalError = null;
|
|
560
|
+
} else {
|
|
561
|
+
this.terminalError = event.finalError ?? this.terminalError ?? "Unknown error";
|
|
562
|
+
}
|
|
525
563
|
break;
|
|
564
|
+
}
|
|
526
565
|
}
|
|
527
566
|
}
|
|
528
567
|
|
|
@@ -540,6 +579,26 @@ export class PiMonoSession implements ProviderSession {
|
|
|
540
579
|
const stats = this.agentSession.getSessionStats();
|
|
541
580
|
const cost = this.buildCostData(stats);
|
|
542
581
|
|
|
582
|
+
// A structured terminal error from pi-coding-agent events is failure by
|
|
583
|
+
// definition (the agent already exhausted retries or hit a non-retryable
|
|
584
|
+
// error). Surface it so the session-chat red box fires and the task fails,
|
|
585
|
+
// exactly like sibling adapters. AWS errors get a categorized, actionable
|
|
586
|
+
// message; anything else surfaces its raw error text.
|
|
587
|
+
if (this.terminalError) {
|
|
588
|
+
const classification = classifyAwsSdkError(this.terminalError);
|
|
589
|
+
const message = classification?.message ?? this.terminalError;
|
|
590
|
+
const category = classification?.category;
|
|
591
|
+
this.emit({ type: "error", message, category });
|
|
592
|
+
return {
|
|
593
|
+
exitCode: 1,
|
|
594
|
+
sessionId: this._sessionId,
|
|
595
|
+
cost,
|
|
596
|
+
isError: true,
|
|
597
|
+
errorCategory: category,
|
|
598
|
+
failureReason: message,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
543
602
|
this.emit({
|
|
544
603
|
type: "result",
|
|
545
604
|
cost,
|
|
@@ -555,13 +614,26 @@ export class PiMonoSession implements ProviderSession {
|
|
|
555
614
|
};
|
|
556
615
|
} catch (err) {
|
|
557
616
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
617
|
+
// Defense-in-depth: AWS SDK failures surface as structured events (handled
|
|
618
|
+
// above in runSession), not thrown exceptions, so this catch is for genuine
|
|
619
|
+
// unexpected throws (MCP / transport / etc). Still classify in case an AWS
|
|
620
|
+
// signature ever reaches here, so the red box fires like sibling adapters.
|
|
621
|
+
const awsCatchError = classifyAwsSdkError(errorMessage);
|
|
622
|
+
if (awsCatchError) {
|
|
623
|
+
this.emit({
|
|
624
|
+
type: "error",
|
|
625
|
+
message: awsCatchError.message,
|
|
626
|
+
category: awsCatchError.category,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
558
629
|
this.emit({ type: "raw_stderr", content: `[pi-mono] Error: ${errorMessage}\n` });
|
|
559
630
|
|
|
560
631
|
return {
|
|
561
632
|
exitCode: 1,
|
|
562
633
|
sessionId: this._sessionId,
|
|
563
634
|
isError: true,
|
|
564
|
-
|
|
635
|
+
errorCategory: awsCatchError?.category,
|
|
636
|
+
failureReason: awsCatchError?.message ?? errorMessage,
|
|
565
637
|
};
|
|
566
638
|
} finally {
|
|
567
639
|
await this.logFileHandle.end();
|
|
@@ -3,7 +3,7 @@ import { CronExpressionParser } from "cron-parser";
|
|
|
3
3
|
import { getDb, getDueScheduledTasks, getScheduledTaskById, updateScheduledTask } from "@/be/db";
|
|
4
4
|
import { scheduleContextKey } from "@/tasks/context-key";
|
|
5
5
|
import { createTaskWithSiblingAwareness } from "@/tasks/sibling-awareness";
|
|
6
|
-
import type { ScheduledTask } from "@/types";
|
|
6
|
+
import type { AgentTask, ScheduledTask } from "@/types";
|
|
7
7
|
import type { ExecutorRegistry } from "@/workflows/executors/registry";
|
|
8
8
|
import { handleScheduleTrigger } from "@/workflows/triggers";
|
|
9
9
|
|
|
@@ -11,6 +11,24 @@ let schedulerInterval: ReturnType<typeof setInterval> | null = null;
|
|
|
11
11
|
let isProcessing = false;
|
|
12
12
|
let executorRegistry: ExecutorRegistry | null = null;
|
|
13
13
|
|
|
14
|
+
export function createStandaloneScheduleTask(
|
|
15
|
+
schedule: ScheduledTask,
|
|
16
|
+
extraTags: string[] = [],
|
|
17
|
+
): AgentTask {
|
|
18
|
+
return createTaskWithSiblingAwareness(schedule.taskTemplate, {
|
|
19
|
+
creatorAgentId: schedule.createdByAgentId,
|
|
20
|
+
taskType: schedule.taskType,
|
|
21
|
+
tags: [...schedule.tags, "scheduled", `schedule:${schedule.name}`, ...extraTags],
|
|
22
|
+
priority: schedule.priority,
|
|
23
|
+
agentId: schedule.targetAgentId,
|
|
24
|
+
model: schedule.model,
|
|
25
|
+
modelTier: schedule.modelTier,
|
|
26
|
+
scheduleId: schedule.id,
|
|
27
|
+
source: "schedule",
|
|
28
|
+
contextKey: scheduleContextKey({ scheduleId: schedule.id }),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
14
32
|
/**
|
|
15
33
|
* Recover missed scheduled task runs from downtime.
|
|
16
34
|
* Fires ONE catch-up run per schedule (not N missed runs).
|
|
@@ -45,17 +63,7 @@ async function recoverMissedSchedules(): Promise<void> {
|
|
|
45
63
|
|
|
46
64
|
if (!triggeredWorkflows) {
|
|
47
65
|
const tx = getDb().transaction(() => {
|
|
48
|
-
|
|
49
|
-
creatorAgentId: schedule.createdByAgentId,
|
|
50
|
-
taskType: schedule.taskType,
|
|
51
|
-
tags: [...schedule.tags, "scheduled", `schedule:${schedule.name}`, "recovered"],
|
|
52
|
-
priority: schedule.priority,
|
|
53
|
-
agentId: schedule.targetAgentId,
|
|
54
|
-
model: schedule.model,
|
|
55
|
-
scheduleId: schedule.id,
|
|
56
|
-
source: "schedule",
|
|
57
|
-
contextKey: scheduleContextKey({ scheduleId: schedule.id }),
|
|
58
|
-
});
|
|
66
|
+
createStandaloneScheduleTask(schedule, ["recovered"]);
|
|
59
67
|
});
|
|
60
68
|
tx();
|
|
61
69
|
}
|
|
@@ -150,17 +158,7 @@ async function executeSchedule(schedule: ScheduledTask): Promise<void> {
|
|
|
150
158
|
if (!triggeredWorkflows) {
|
|
151
159
|
// No workflows linked — create standalone task (existing behavior)
|
|
152
160
|
getDb().transaction(() => {
|
|
153
|
-
|
|
154
|
-
creatorAgentId: schedule.createdByAgentId,
|
|
155
|
-
taskType: schedule.taskType,
|
|
156
|
-
tags: [...schedule.tags, "scheduled", `schedule:${schedule.name}`],
|
|
157
|
-
priority: schedule.priority,
|
|
158
|
-
agentId: schedule.targetAgentId,
|
|
159
|
-
model: schedule.model,
|
|
160
|
-
scheduleId: schedule.id,
|
|
161
|
-
source: "schedule",
|
|
162
|
-
contextKey: scheduleContextKey({ scheduleId: schedule.id }),
|
|
163
|
-
});
|
|
161
|
+
createStandaloneScheduleTask(schedule);
|
|
164
162
|
})();
|
|
165
163
|
}
|
|
166
164
|
|
|
@@ -341,17 +339,7 @@ export async function runScheduleNow(scheduleId: string): Promise<void> {
|
|
|
341
339
|
if (!triggeredWorkflows) {
|
|
342
340
|
// No workflows linked — create standalone task (existing behavior)
|
|
343
341
|
getDb().transaction(() => {
|
|
344
|
-
|
|
345
|
-
creatorAgentId: schedule.createdByAgentId,
|
|
346
|
-
taskType: schedule.taskType,
|
|
347
|
-
tags: [...schedule.tags, "scheduled", `schedule:${schedule.name}`, "manual-run"],
|
|
348
|
-
priority: schedule.priority,
|
|
349
|
-
agentId: schedule.targetAgentId,
|
|
350
|
-
model: schedule.model,
|
|
351
|
-
scheduleId: schedule.id,
|
|
352
|
-
source: "schedule",
|
|
353
|
-
contextKey: scheduleContextKey({ scheduleId: schedule.id }),
|
|
354
|
-
});
|
|
342
|
+
createStandaloneScheduleTask(schedule, ["manual-run"]);
|
|
355
343
|
})();
|
|
356
344
|
}
|
|
357
345
|
|
package/src/server-user.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import pkg from "../package.json";
|
|
4
|
+
import { ModelTierSchema } from "./model-tiers";
|
|
4
5
|
import {
|
|
5
6
|
cancelTaskHandler,
|
|
6
7
|
cancelTaskInputSchema,
|
|
@@ -28,9 +29,14 @@ const userSendTaskInputSchema = z.object({
|
|
|
28
29
|
tags: z.array(z.string()).optional().describe("Tags for filtering (e.g., ['urgent'])."),
|
|
29
30
|
priority: z.number().int().min(0).max(100).optional().describe("Priority 0-100 (default: 50)."),
|
|
30
31
|
model: z
|
|
31
|
-
.
|
|
32
|
+
.string()
|
|
33
|
+
.trim()
|
|
34
|
+
.min(1)
|
|
32
35
|
.optional()
|
|
33
|
-
.describe("
|
|
36
|
+
.describe("Concrete model override interpreted by the assignee's harness/provider."),
|
|
37
|
+
modelTier: ModelTierSchema.optional().describe(
|
|
38
|
+
"Portable model tier: 'smol', 'regular', 'smart', or 'ultra'. Resolved by the assignee's harness/provider.",
|
|
39
|
+
),
|
|
34
40
|
});
|
|
35
41
|
|
|
36
42
|
export function createUserServer(user: User): McpServer {
|