@desplega.ai/agent-swarm 1.102.0 → 1.103.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/dist/{actions-j9399ktm.js → actions-7txktrdj.js} +6 -6
- package/dist/{app-tnq1pz27.js → app-xm3xsdye.js} +3 -3
- package/dist/{assistant-c7y92gkw.js → assistant-qpv63wk6.js} +9 -9
- package/dist/{boot-reembed-k7e6fc1p.js → boot-reembed-9cxqka3e.js} +3 -3
- package/dist/{boot-reembed-b97r699t.js → boot-reembed-chxhrwv8.js} +4 -4
- package/dist/{boot-scrub-logs-14kbc61y.js → boot-scrub-logs-8qtfkcz8.js} +2 -2
- package/dist/{claude-managed-adapter-g07dbz3b.js → claude-managed-adapter-z29hjgzz.js} +3 -3
- package/dist/{cli-tsz1q7gk.js → cli-2307phk1.js} +375 -23
- package/dist/{cli-g5px3d7x.js → cli-2957pqpj.js} +24 -1
- package/dist/{cli-9qascwj7.js → cli-2qynerth.js} +1 -1
- package/dist/{cli-k1ghyj8b.js → cli-4fnp2tc3.js} +1 -1
- package/dist/{cli-hc4rd6sv.js → cli-85pf4z38.js} +1 -1
- package/dist/{cli-zk4gn40w.js → cli-8hyv6c2v.js} +6 -14
- package/dist/{cli-k81y1cjb.js → cli-8w95v19d.js} +1 -1
- package/dist/{cli-7xdq3jg3.js → cli-92fqyjcv.js} +1 -1
- package/dist/{cli-zctkpdmd.js → cli-dz3d6zjg.js} +1 -1
- package/dist/{cli-d0t4854c.js → cli-fedn86nz.js} +213 -476
- package/dist/{cli-g861fwjp.js → cli-fwxdt7kt.js} +3 -3
- package/dist/{cli-2aaa9mqr.js → cli-fygaw191.js} +2 -2
- package/dist/{cli-qbc6mncp.js → cli-gzepjz54.js} +2 -2
- package/dist/{cli-r68m8arr.js → cli-jt9d9fjw.js} +1 -1
- package/dist/{cli-axtg1zaa.js → cli-khe4x17a.js} +1 -1
- package/dist/{cli-sygq3fhn.js → cli-kk51f3tf.js} +5 -5
- package/dist/{cli-dgab5x2h.js → cli-krvxq8bc.js} +1 -1
- package/dist/{cli-xde8vhhp.js → cli-ksq38x4n.js} +2 -2
- package/dist/{cli-stqqf5kh.js → cli-m8f6qzck.js} +1 -1
- package/dist/{cli-ap4md0gc.js → cli-ns0r7mkr.js} +30 -5
- package/dist/{cli-t2jk33ta.js → cli-rttgde5f.js} +18 -6
- package/dist/{cli-mhj07xbq.js → cli-rvae030h.js} +3 -3
- package/dist/{cli-q6jzg6wj.js → cli-s14sb64w.js} +4 -4
- package/dist/{cli-zmf7refz.js → cli-v9283nma.js} +1 -1
- package/dist/{cli-fjx4x7nn.js → cli-xmtsan9k.js} +1 -1
- package/dist/{cli-vgck9mqq.js → cli-yeemwevj.js} +155 -37
- package/dist/{cli-5bqa41fd.js → cli-yfwwjeft.js} +1 -1
- package/dist/{cli-s8jw5bmg.js → cli-yp2krx5r.js} +2 -2
- package/dist/cli.js +9 -9
- package/dist/{codex-adapter-wf119mnw.js → codex-adapter-w0xharsg.js} +3 -3
- package/dist/{codex-session-runner-1gknw7mh.js → codex-session-runner-kzbjcm2k.js} +3 -3
- package/dist/{commands-2wyn3swb.js → commands-aqn63mcw.js} +2 -2
- package/dist/{db-567ntpxv.js → db-azrvwsnj.js} +2 -2
- package/dist/{handlers-wvwpk2kj.js → handlers-hf61har3.js} +9 -9
- package/dist/{hook-fatkywc0.js → hook-306p3yz4.js} +2 -2
- package/dist/{http-eq6wxve6.js → http-zts4haq7.js} +81 -62
- package/dist/{index-400x6p3j.js → index-29vg51x6.js} +6 -6
- package/dist/{index-9wqh5352.js → index-3c651yfk.js} +10 -10
- package/dist/{index-6aj8966p.js → index-bfh9hgek.js} +5 -5
- package/dist/{index-d1rm4ea3.js → index-h2yqjyk0.js} +8 -8
- package/dist/{keepalive-6x9nx92g.js → keepalive-a0dg5pds.js} +4 -4
- package/dist/{lead-5fgqwe13.js → lead-7mcxedkc.js} +21 -21
- package/dist/{maintenance-eajx5k1k.js → maintenance-mrzqd53t.js} +4 -4
- package/dist/{onboard-jc1w1edg.js → onboard-4dkpv9wa.js} +2 -2
- package/dist/{otel-impl-8k375amf.js → otel-impl-jt7gpyp9.js} +1 -1
- package/dist/{pi-mono-adapter-hf8sw7hs.js → pi-mono-adapter-zf7dt1wg.js} +1 -1
- package/dist/{pricing-refresh-zed6916t.js → pricing-refresh-xqnzh8dt.js} +4 -4
- package/dist/{seed-pricing-5805qzkz.js → seed-pricing-56tjm1fj.js} +3 -3
- package/dist/{setup-wz05vxh0.js → setup-tbwk8j10.js} +2 -2
- package/dist/{worker-j359graf.js → worker-e03za92t.js} +21 -21
- package/openapi.json +70 -1
- package/package.json +1 -1
- package/src/be/db.ts +83 -13
- package/src/be/migrations/098_repo_hooks_config.sql +3 -0
- package/src/be/task-lifecycle-events.ts +42 -0
- package/src/commands/runner.ts +174 -25
- package/src/github/task-reactions.ts +21 -0
- package/src/hooks/tool-loop-detection.test.ts +40 -0
- package/src/hooks/tool-loop-detection.ts +37 -1
- package/src/http/agents.ts +7 -0
- package/src/http/index.ts +16 -2
- package/src/http/prompt-templates.ts +1 -1
- package/src/http/repos.ts +5 -1
- package/src/http/schedules.ts +1 -1
- package/src/http/session-data.ts +2 -0
- package/src/http/tasks.ts +2 -1
- package/src/http/workflows.ts +1 -0
- package/src/prompts/resolver.ts +1 -1
- package/src/scheduler/scheduler.ts +25 -2
- package/src/server-runtime-counters.ts +12 -0
- package/src/server-user.ts +1 -2
- package/src/server.ts +5 -0
- package/src/telemetry.ts +18 -1
- package/src/tests/http-api-integration.test.ts +4 -0
- package/src/tests/model-control.test.ts +6 -6
- package/src/tests/swarm-repos.test.ts +25 -2
- package/src/tests/task-lifecycle-events.test.ts +53 -0
- package/src/tests/task-lifecycle-telemetry.test.ts +37 -1
- package/src/tests/telemetry-init.test.ts +32 -0
- package/src/tools/prompt-templates/preview.ts +1 -1
- package/src/tools/repos/update-repo.ts +6 -1
- package/src/tools/schedules/create-schedule.ts +1 -1
- package/src/tools/schedules/update-schedule.ts +1 -1
- package/src/tools/send-task.ts +7 -2
- package/src/tools/task-action.ts +6 -2
- package/src/types.ts +155 -1
- package/src/utils/template.ts +127 -0
- package/src/workflows/engine.ts +7 -0
- package/src/workflows/executors/agent-task.ts +1 -2
- package/src/workflows/template.ts +12 -121
- package/src/workflows/triggers.ts +6 -2
- package/templates/community/code-health-reports/PLAYBOOK.md +355 -0
- package/templates/community/code-health-reports/README.md +38 -0
- package/templates/community/code-health-reports/lead-prompt.md +52 -0
- package/templates/community/code-health-reports/report.mjs +378 -0
- package/templates/community/code-health-reports/run.sh +143 -0
- package/templates/schedules/weekly-code-health-reports/config.json +13 -0
- package/templates/schedules/weekly-code-health-reports/content.md +60 -0
- package/src/model-tiers.ts +0 -140
- package/templates/community/.gitkeep +0 -0
package/src/commands/runner.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { existsSync, statSync } from "node:fs";
|
|
|
2
2
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { ensure, initialize } from "@desplega.ai/business-use";
|
|
4
4
|
import type { TemplateResponse } from "../../templates/schema.ts";
|
|
5
|
-
import { resolveTaskModelSelection } from "../model-tiers.ts";
|
|
6
5
|
import {
|
|
7
6
|
type Attributes,
|
|
8
7
|
initOtel,
|
|
@@ -34,7 +33,7 @@ import {
|
|
|
34
33
|
type ProviderSessionConfig,
|
|
35
34
|
} from "../providers/index.ts";
|
|
36
35
|
import { initTelemetry, telemetry } from "../telemetry.ts";
|
|
37
|
-
import type
|
|
36
|
+
import { type ProviderName, type RepoGuidelines, resolveTaskModelSelection } from "../types.ts";
|
|
38
37
|
import { getApiKey } from "../utils/api-key.ts";
|
|
39
38
|
import { computeBudgetBackoffMs } from "../utils/budget-backoff.ts";
|
|
40
39
|
import { getMcpBaseUrl } from "../utils/constants.ts";
|
|
@@ -52,9 +51,9 @@ import { resolveHarnessProvider } from "../utils/harness-provider.ts";
|
|
|
52
51
|
import { prettyPrintLine, prettyPrintStderr } from "../utils/pretty-print.ts";
|
|
53
52
|
import { scrubSecrets } from "../utils/secret-scrubber.ts";
|
|
54
53
|
import { refreshSkillsIfChanged } from "../utils/skills-refresh.ts";
|
|
54
|
+
import { interpolate } from "../utils/template.ts";
|
|
55
55
|
import { detectVcsProvider } from "../vcs/index.ts";
|
|
56
56
|
import { validateJsonSchema } from "../workflows/json-schema-validator.ts";
|
|
57
|
-
import { interpolate } from "../workflows/template.ts";
|
|
58
57
|
import { buildContextPreamble, buildResumeContextPreamble } from "./context-preamble.ts";
|
|
59
58
|
import { awaitCredentials, BootMaxWaitExceededError, EX_CONFIG } from "./credential-wait.ts";
|
|
60
59
|
import {
|
|
@@ -131,6 +130,7 @@ async function fetchRepoConfig(
|
|
|
131
130
|
name: string;
|
|
132
131
|
clonePath: string;
|
|
133
132
|
defaultBranch: string;
|
|
133
|
+
hooks?: { enabled: boolean } | null;
|
|
134
134
|
guidelines?: RepoGuidelines | null;
|
|
135
135
|
} | null> {
|
|
136
136
|
try {
|
|
@@ -145,6 +145,7 @@ async function fetchRepoConfig(
|
|
|
145
145
|
name: string;
|
|
146
146
|
clonePath: string;
|
|
147
147
|
defaultBranch: string;
|
|
148
|
+
hooks?: { enabled: boolean } | null;
|
|
148
149
|
guidelines?: RepoGuidelines | null;
|
|
149
150
|
}>;
|
|
150
151
|
};
|
|
@@ -235,12 +236,40 @@ async function refreshExistingRepoForTask(
|
|
|
235
236
|
}
|
|
236
237
|
}
|
|
237
238
|
|
|
239
|
+
function getInstallRepoHooksScriptPath(): string {
|
|
240
|
+
const imagePath = "/usr/local/bin/install-repo-hooks.sh";
|
|
241
|
+
if (existsSync(imagePath)) return imagePath;
|
|
242
|
+
return `${process.cwd()}/scripts/install-repo-hooks.sh`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function installRepoHooksForTask(
|
|
246
|
+
repoConfig: { name: string; clonePath: string; hooks?: { enabled: boolean } | null },
|
|
247
|
+
role: string,
|
|
248
|
+
): Promise<void> {
|
|
249
|
+
if (repoConfig.hooks?.enabled !== true) return;
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
const scriptPath = getInstallRepoHooksScriptPath();
|
|
253
|
+
await Bun.$`bash ${scriptPath} ${repoConfig.clonePath} ${repoConfig.name}`;
|
|
254
|
+
console.log(`[${role}] Installed git hooks for ${repoConfig.name}`);
|
|
255
|
+
} catch (err) {
|
|
256
|
+
const errorMsg = scrubSecrets((err as Error).message);
|
|
257
|
+
console.warn(`[${role}] Could not install git hooks for ${repoConfig.name}: ${errorMsg}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
238
261
|
/**
|
|
239
262
|
* Ensure a repo is cloned and up-to-date for a task.
|
|
240
263
|
* Returns { clonePath, claudeMd, warning }.
|
|
241
264
|
*/
|
|
242
265
|
export async function ensureRepoForTask(
|
|
243
|
-
repoConfig: {
|
|
266
|
+
repoConfig: {
|
|
267
|
+
url: string;
|
|
268
|
+
name: string;
|
|
269
|
+
clonePath: string;
|
|
270
|
+
defaultBranch: string;
|
|
271
|
+
hooks?: { enabled: boolean } | null;
|
|
272
|
+
},
|
|
244
273
|
role: string,
|
|
245
274
|
): Promise<{
|
|
246
275
|
clonePath: string;
|
|
@@ -275,6 +304,8 @@ export async function ensureRepoForTask(
|
|
|
275
304
|
warning = await refreshExistingRepoForTask({ name, clonePath, defaultBranch }, role);
|
|
276
305
|
}
|
|
277
306
|
|
|
307
|
+
await installRepoHooksForTask(repoConfig, role);
|
|
308
|
+
|
|
278
309
|
const claudeMd = await readClaudeMd(clonePath, role);
|
|
279
310
|
const autoStashes = await listSwarmAutostashes(clonePath, role);
|
|
280
311
|
return { clonePath, claudeMd, warning, autoStashes };
|
|
@@ -1484,7 +1515,11 @@ function setupShutdownHandlers(
|
|
|
1484
1515
|
}
|
|
1485
1516
|
|
|
1486
1517
|
if (apiConfig) {
|
|
1487
|
-
telemetry.session("ended", {
|
|
1518
|
+
telemetry.session("ended", {
|
|
1519
|
+
agentId: apiConfig.agentId,
|
|
1520
|
+
durationMs: state ? Date.now() - state.startedAt : undefined,
|
|
1521
|
+
tasksProcessed: state?.tasksProcessed ?? 0,
|
|
1522
|
+
});
|
|
1488
1523
|
await closeAgent(apiConfig, role);
|
|
1489
1524
|
}
|
|
1490
1525
|
await savePm2State(role);
|
|
@@ -1554,12 +1589,18 @@ interface RunningTask {
|
|
|
1554
1589
|
hasLocalEnvironment: boolean;
|
|
1555
1590
|
/** Harness variant captured on session_init (e.g. "bridge" or "stock") */
|
|
1556
1591
|
harnessVariant?: string;
|
|
1592
|
+
/** Harness metadata captured on session_init (currently includes provider package version) */
|
|
1593
|
+
harnessVariantMeta?: Record<string, unknown>;
|
|
1594
|
+
/** Resolved model used for the provider session, when configured */
|
|
1595
|
+
model?: string;
|
|
1557
1596
|
}
|
|
1558
1597
|
|
|
1559
1598
|
/** Runner state for tracking concurrent tasks */
|
|
1560
1599
|
interface RunnerState {
|
|
1561
1600
|
activeTasks: Map<string, RunningTask>;
|
|
1562
1601
|
maxConcurrent: number;
|
|
1602
|
+
startedAt: number;
|
|
1603
|
+
tasksProcessed: number;
|
|
1563
1604
|
/**
|
|
1564
1605
|
* Effective harness provider for this worker boot session — resolved
|
|
1565
1606
|
* from `swarm_config` (overlay) > `process.env.HARNESS_PROVIDER` > "claude".
|
|
@@ -2542,6 +2583,19 @@ function providerEventAttributes(event: ProviderEvent): Attributes {
|
|
|
2542
2583
|
}
|
|
2543
2584
|
}
|
|
2544
2585
|
|
|
2586
|
+
function normalizeSessionErrorCategory(category: string | undefined): string {
|
|
2587
|
+
switch (category) {
|
|
2588
|
+
case "rate_limit":
|
|
2589
|
+
case "api_error":
|
|
2590
|
+
case "context_overflow":
|
|
2591
|
+
case "timeout":
|
|
2592
|
+
case "provider_error":
|
|
2593
|
+
return category;
|
|
2594
|
+
default:
|
|
2595
|
+
return "unknown";
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2545
2599
|
/**
|
|
2546
2600
|
* Entry shape for the `activeToolSpans` map maintained by `runWithSession`.
|
|
2547
2601
|
* Exported for unit tests that exercise `implicitCloseActiveToolSpans`.
|
|
@@ -2799,6 +2853,9 @@ async function spawnProviderProcess(
|
|
|
2799
2853
|
const eventFlushTimer = setInterval(flushEvents, EVENT_FLUSH_INTERVAL_MS);
|
|
2800
2854
|
const sessionStartTime = Date.now();
|
|
2801
2855
|
let providerSessionId = session.sessionId;
|
|
2856
|
+
let pendingHarnessVariant: string | undefined;
|
|
2857
|
+
let pendingHarnessVariantMeta: Record<string, unknown> | undefined;
|
|
2858
|
+
let runningTaskForSessionInit: RunningTask | undefined;
|
|
2802
2859
|
const activeToolSpans = new Map<
|
|
2803
2860
|
string,
|
|
2804
2861
|
{
|
|
@@ -2823,12 +2880,37 @@ async function spawnProviderProcess(
|
|
|
2823
2880
|
// snapshots carry a real `cumulativeOutputTokens` (was 0 until completion).
|
|
2824
2881
|
let cumulativeProgressOutputTokens = 0;
|
|
2825
2882
|
|
|
2883
|
+
function bufferSessionLogLine(content: string) {
|
|
2884
|
+
if (!shouldStream) return;
|
|
2885
|
+
logBuffer.lines.push(content);
|
|
2886
|
+
const shouldFlush =
|
|
2887
|
+
logBuffer.lines.length >= LOG_BUFFER_SIZE ||
|
|
2888
|
+
Date.now() - logBuffer.lastFlush >= LOG_FLUSH_INTERVAL_MS;
|
|
2889
|
+
if (shouldFlush) {
|
|
2890
|
+
flushLogBuffer(logBuffer, {
|
|
2891
|
+
apiUrl: opts.apiUrl,
|
|
2892
|
+
apiKey: opts.apiKey,
|
|
2893
|
+
agentId: opts.agentId,
|
|
2894
|
+
sessionId: opts.runnerSessionId,
|
|
2895
|
+
iteration: opts.iteration,
|
|
2896
|
+
taskId: effectiveTaskId,
|
|
2897
|
+
cli: adapter.name,
|
|
2898
|
+
}).catch(() => {});
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2826
2902
|
session.onEvent((event) =>
|
|
2827
2903
|
withSpanContext(sessionSpan, () => {
|
|
2828
2904
|
sessionSpan.addEvent(`provider.${event.type}`, providerEventAttributes(event));
|
|
2829
2905
|
switch (event.type) {
|
|
2830
2906
|
case "session_init":
|
|
2831
2907
|
providerSessionId = event.sessionId;
|
|
2908
|
+
pendingHarnessVariant = event.harnessVariant;
|
|
2909
|
+
pendingHarnessVariantMeta = event.harnessVariantMeta;
|
|
2910
|
+
if (runningTaskForSessionInit) {
|
|
2911
|
+
runningTaskForSessionInit.harnessVariant = event.harnessVariant;
|
|
2912
|
+
runningTaskForSessionInit.harnessVariantMeta = event.harnessVariantMeta;
|
|
2913
|
+
}
|
|
2832
2914
|
sessionSpan.setAttributes({
|
|
2833
2915
|
"agentswarm.provider.session_id": providerSessionId,
|
|
2834
2916
|
"agentswarm.provider.name": event.provider,
|
|
@@ -3074,6 +3156,11 @@ async function spawnProviderProcess(
|
|
|
3074
3156
|
}
|
|
3075
3157
|
case "compaction": {
|
|
3076
3158
|
// Always record compaction events (no throttle)
|
|
3159
|
+
telemetry.compaction("triggered", {
|
|
3160
|
+
compactTrigger: event.compactTrigger,
|
|
3161
|
+
preCompactTokens: event.preCompactTokens,
|
|
3162
|
+
contextTotalTokens: event.contextTotalTokens,
|
|
3163
|
+
});
|
|
3077
3164
|
fetch(`${opts.apiUrl}/api/tasks/${realTaskId}/context`, {
|
|
3078
3165
|
method: "POST",
|
|
3079
3166
|
headers: {
|
|
@@ -3093,26 +3180,17 @@ async function spawnProviderProcess(
|
|
|
3093
3180
|
}
|
|
3094
3181
|
case "raw_log":
|
|
3095
3182
|
prettyPrintLine(event.content, opts.role);
|
|
3096
|
-
|
|
3097
|
-
logBuffer.lines.push(event.content);
|
|
3098
|
-
const shouldFlush =
|
|
3099
|
-
logBuffer.lines.length >= LOG_BUFFER_SIZE ||
|
|
3100
|
-
Date.now() - logBuffer.lastFlush >= LOG_FLUSH_INTERVAL_MS;
|
|
3101
|
-
if (shouldFlush) {
|
|
3102
|
-
flushLogBuffer(logBuffer, {
|
|
3103
|
-
apiUrl: opts.apiUrl,
|
|
3104
|
-
apiKey: opts.apiKey,
|
|
3105
|
-
agentId: opts.agentId,
|
|
3106
|
-
sessionId: opts.runnerSessionId,
|
|
3107
|
-
iteration: opts.iteration,
|
|
3108
|
-
taskId: effectiveTaskId,
|
|
3109
|
-
cli: adapter.name,
|
|
3110
|
-
}).catch(() => {});
|
|
3111
|
-
}
|
|
3112
|
-
}
|
|
3183
|
+
bufferSessionLogLine(event.content);
|
|
3113
3184
|
break;
|
|
3114
3185
|
case "raw_stderr":
|
|
3115
3186
|
prettyPrintStderr(event.content, opts.role);
|
|
3187
|
+
bufferSessionLogLine(
|
|
3188
|
+
JSON.stringify({
|
|
3189
|
+
type: "stderr",
|
|
3190
|
+
content: event.content,
|
|
3191
|
+
timestamp: new Date().toISOString(),
|
|
3192
|
+
}),
|
|
3193
|
+
);
|
|
3116
3194
|
break;
|
|
3117
3195
|
|
|
3118
3196
|
case "progress": {
|
|
@@ -3202,6 +3280,21 @@ async function spawnProviderProcess(
|
|
|
3202
3280
|
"gen_ai.usage.output_tokens": result.cost.outputTokens ?? 0,
|
|
3203
3281
|
"agentswarm.cost.total_usd": result.cost.totalCostUsd ?? 0,
|
|
3204
3282
|
});
|
|
3283
|
+
telemetry.session("cost", {
|
|
3284
|
+
agentId: opts.agentId,
|
|
3285
|
+
model: result.cost.model,
|
|
3286
|
+
provider: result.cost.provider ?? opts.harnessProvider,
|
|
3287
|
+
inputTokens: result.cost.inputTokens ?? 0,
|
|
3288
|
+
outputTokens: result.cost.outputTokens ?? 0,
|
|
3289
|
+
cacheReadTokens: result.cost.cacheReadTokens,
|
|
3290
|
+
cacheWriteTokens: result.cost.cacheWriteTokens,
|
|
3291
|
+
reasoningOutputTokens: result.cost.reasoningOutputTokens,
|
|
3292
|
+
thinkingTokens: result.cost.thinkingTokens,
|
|
3293
|
+
totalCostUsd: result.cost.totalCostUsd,
|
|
3294
|
+
durationMs: result.cost.durationMs,
|
|
3295
|
+
numTurns: result.cost.numTurns,
|
|
3296
|
+
isError: result.cost.isError,
|
|
3297
|
+
});
|
|
3205
3298
|
try {
|
|
3206
3299
|
await saveCostData(
|
|
3207
3300
|
{ ...result.cost, taskId: realTaskId, sessionId: opts.runnerSessionId },
|
|
@@ -3303,7 +3396,15 @@ async function spawnProviderProcess(
|
|
|
3303
3396
|
// swap that mutates the global RunnerState (review finding 2).
|
|
3304
3397
|
harnessProvider: opts.harnessProvider,
|
|
3305
3398
|
hasLocalEnvironment: adapter.traits.hasLocalEnvironment,
|
|
3399
|
+
model: model || undefined,
|
|
3306
3400
|
};
|
|
3401
|
+
runningTaskForSessionInit = runningTask;
|
|
3402
|
+
if (pendingHarnessVariant !== undefined) {
|
|
3403
|
+
runningTask.harnessVariant = pendingHarnessVariant;
|
|
3404
|
+
}
|
|
3405
|
+
if (pendingHarnessVariantMeta !== undefined) {
|
|
3406
|
+
runningTask.harnessVariantMeta = pendingHarnessVariantMeta;
|
|
3407
|
+
}
|
|
3307
3408
|
|
|
3308
3409
|
// Non-blocking completion tracking
|
|
3309
3410
|
promise
|
|
@@ -3333,6 +3434,10 @@ async function checkCompletedProcesses(
|
|
|
3333
3434
|
credentialInfo?: RunningTask["credentialInfo"];
|
|
3334
3435
|
harnessProvider: ProviderName;
|
|
3335
3436
|
hasLocalEnvironment: boolean;
|
|
3437
|
+
harnessVariant?: string;
|
|
3438
|
+
harnessVariantMeta?: Record<string, unknown>;
|
|
3439
|
+
model?: string;
|
|
3440
|
+
durationMs: number;
|
|
3336
3441
|
}> = [];
|
|
3337
3442
|
|
|
3338
3443
|
for (const [taskId, task] of state.activeTasks) {
|
|
@@ -3350,6 +3455,10 @@ async function checkCompletedProcesses(
|
|
|
3350
3455
|
credentialInfo: task.credentialInfo,
|
|
3351
3456
|
harnessProvider: task.harnessProvider,
|
|
3352
3457
|
hasLocalEnvironment: task.hasLocalEnvironment,
|
|
3458
|
+
harnessVariant: task.harnessVariant,
|
|
3459
|
+
harnessVariantMeta: task.harnessVariantMeta,
|
|
3460
|
+
model: task.model,
|
|
3461
|
+
durationMs: Date.now() - task.startTime.getTime(),
|
|
3353
3462
|
});
|
|
3354
3463
|
}
|
|
3355
3464
|
}
|
|
@@ -3362,6 +3471,10 @@ async function checkCompletedProcesses(
|
|
|
3362
3471
|
workingDir,
|
|
3363
3472
|
credentialInfo,
|
|
3364
3473
|
harnessProvider,
|
|
3474
|
+
harnessVariant,
|
|
3475
|
+
harnessVariantMeta,
|
|
3476
|
+
model,
|
|
3477
|
+
durationMs,
|
|
3365
3478
|
} of completedTasks) {
|
|
3366
3479
|
state.activeTasks.delete(taskId);
|
|
3367
3480
|
vcsDetectedTasks.delete(taskId);
|
|
@@ -3482,6 +3595,33 @@ async function checkCompletedProcesses(
|
|
|
3482
3595
|
bridgeFailureDiagnostics,
|
|
3483
3596
|
);
|
|
3484
3597
|
|
|
3598
|
+
telemetry.taskEvent("session_completed", {
|
|
3599
|
+
taskId,
|
|
3600
|
+
agentId: apiConfig.agentId,
|
|
3601
|
+
provider: result.cost?.provider ?? harnessProvider,
|
|
3602
|
+
model: result.cost?.model ?? model,
|
|
3603
|
+
harnessVariant,
|
|
3604
|
+
harnessVersion:
|
|
3605
|
+
typeof harnessVariantMeta?.version === "string" ||
|
|
3606
|
+
typeof harnessVariantMeta?.version === "number"
|
|
3607
|
+
? String(harnessVariantMeta.version)
|
|
3608
|
+
: undefined,
|
|
3609
|
+
exitCode: result.exitCode,
|
|
3610
|
+
isError: result.exitCode !== 0,
|
|
3611
|
+
durationMs,
|
|
3612
|
+
});
|
|
3613
|
+
if (result.exitCode !== 0 || result.isError) {
|
|
3614
|
+
telemetry.session("failure", {
|
|
3615
|
+
agentId: apiConfig.agentId,
|
|
3616
|
+
errorCategory: normalizeSessionErrorCategory(result.errorCategory),
|
|
3617
|
+
provider: result.cost?.provider ?? harnessProvider,
|
|
3618
|
+
model: result.cost?.model ?? model,
|
|
3619
|
+
durationMs: result.cost?.durationMs ?? durationMs,
|
|
3620
|
+
wasRateLimited: result.rateLimitResetAt != null,
|
|
3621
|
+
});
|
|
3622
|
+
}
|
|
3623
|
+
state.tasksProcessed += 1;
|
|
3624
|
+
|
|
3485
3625
|
if (result.exitCode === 0 && credentialInfo) {
|
|
3486
3626
|
reportKeyClearRateLimit(
|
|
3487
3627
|
apiConfig.apiUrl,
|
|
@@ -3660,6 +3800,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
3660
3800
|
// Failures (network, API down, malformed value) fall back to env then "claude"
|
|
3661
3801
|
// so a swarm_config outage cannot wedge boot.
|
|
3662
3802
|
let bootProvider: ProviderName;
|
|
3803
|
+
let bootModel = process.env.MODEL_OVERRIDE || "";
|
|
3663
3804
|
// Codex credits-exhausted cooldown is sourced solely from the global swarm_config
|
|
3664
3805
|
// (key `CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS`). Initialize to the default constant
|
|
3665
3806
|
// for the case where it is unset, then apply the swarm_config value below; on a
|
|
@@ -3669,6 +3810,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
3669
3810
|
try {
|
|
3670
3811
|
const bootEnv = await fetchResolvedEnv(apiUrl, apiKey, agentId);
|
|
3671
3812
|
bootProvider = bootEnv.resolvedProvider;
|
|
3813
|
+
bootModel = (bootEnv.env.MODEL_OVERRIDE as string | undefined) || bootModel;
|
|
3672
3814
|
bootCooldownMs = resolveCodexCreditsExhaustedCooldownMs(
|
|
3673
3815
|
bootEnv.env.CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS,
|
|
3674
3816
|
);
|
|
@@ -3733,7 +3875,12 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
3733
3875
|
},
|
|
3734
3876
|
);
|
|
3735
3877
|
}
|
|
3736
|
-
telemetry.session("started", {
|
|
3878
|
+
telemetry.session("started", {
|
|
3879
|
+
agentId,
|
|
3880
|
+
harnessProvider: bootProvider,
|
|
3881
|
+
model: bootModel || undefined,
|
|
3882
|
+
role,
|
|
3883
|
+
});
|
|
3737
3884
|
|
|
3738
3885
|
let capabilities = config.capabilities;
|
|
3739
3886
|
|
|
@@ -3878,6 +4025,8 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
3878
4025
|
const state: RunnerState = {
|
|
3879
4026
|
activeTasks: new Map(),
|
|
3880
4027
|
maxConcurrent,
|
|
4028
|
+
startedAt: Date.now(),
|
|
4029
|
+
tasksProcessed: 0,
|
|
3881
4030
|
harnessProvider: bootProvider,
|
|
3882
4031
|
codexCreditsExhaustedCooldownMs: bootCooldownMs,
|
|
3883
4032
|
};
|
|
@@ -4506,7 +4655,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
4506
4655
|
const effectiveConfig = repoConfig ?? {
|
|
4507
4656
|
url: task.vcsRepo,
|
|
4508
4657
|
name: task.vcsRepo.split("/").pop() || task.vcsRepo,
|
|
4509
|
-
clonePath: `/workspace/repos/${task.vcsRepo.split("/").pop() || task.vcsRepo}`,
|
|
4658
|
+
clonePath: `/workspace/personal/repos/${task.vcsRepo.split("/").pop() || task.vcsRepo}`,
|
|
4510
4659
|
defaultBranch: "main",
|
|
4511
4660
|
};
|
|
4512
4661
|
const repoContext = await ensureRepoForTask(effectiveConfig, role);
|
|
@@ -4897,7 +5046,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
4897
5046
|
const effectiveConfig = repoConfig ?? {
|
|
4898
5047
|
url: taskVcsRepo,
|
|
4899
5048
|
name: taskVcsRepo.split("/").pop() || taskVcsRepo,
|
|
4900
|
-
clonePath: `/workspace/repos/${taskVcsRepo.split("/").pop() || taskVcsRepo}`,
|
|
5049
|
+
clonePath: `/workspace/personal/repos/${taskVcsRepo.split("/").pop() || taskVcsRepo}`,
|
|
4901
5050
|
defaultBranch: "main",
|
|
4902
5051
|
};
|
|
4903
5052
|
const repoResult = await ensureRepoForTask(effectiveConfig, role);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { onTaskStarted } from "../be/task-lifecycle-events";
|
|
1
2
|
import type { AgentTask } from "../types";
|
|
2
3
|
import {
|
|
3
4
|
addGraphQLReaction,
|
|
@@ -64,3 +65,23 @@ export async function addEyesReactionOnTaskStart(task: AgentTask): Promise<void>
|
|
|
64
65
|
console.error("[GitHub] Failed to add eyes reaction on task start:", error);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
68
|
+
|
|
69
|
+
let registered = false;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Subscribe the GitHub eyes-reaction handler to the task-started lifecycle event.
|
|
73
|
+
*
|
|
74
|
+
* Call this once at API-server boot (from `createServer`). It is the API-side
|
|
75
|
+
* inverse of the old `be/db` → `github/task-reactions` import: the data layer now
|
|
76
|
+
* emits `task-started` and this integration reacts. Idempotent — `createServer`
|
|
77
|
+
* may run more than once per process, so repeated calls register only once.
|
|
78
|
+
*
|
|
79
|
+
* API-server only — never wire this on the worker side.
|
|
80
|
+
*/
|
|
81
|
+
export function registerGithubTaskReactions(): void {
|
|
82
|
+
if (registered) return;
|
|
83
|
+
registered = true;
|
|
84
|
+
onTaskStarted((task) => {
|
|
85
|
+
addEyesReactionOnTaskStart(task).catch(() => {});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -62,6 +62,46 @@ describe("tool-loop-detection", () => {
|
|
|
62
62
|
expect(result!.reason).toContain("stuck in a loop");
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
+
test("returns critical/blocked at 15 identical fully-specified edits", async () => {
|
|
66
|
+
let result: Awaited<ReturnType<typeof checkToolLoop>> | undefined;
|
|
67
|
+
for (let i = 0; i < 15; i++) {
|
|
68
|
+
result = await checkToolLoop(SESSION_KEY, "Edit", {
|
|
69
|
+
file_path: "/same.ts",
|
|
70
|
+
old_string: "before",
|
|
71
|
+
new_string: "after",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
expect(result!.blocked).toBe(true);
|
|
75
|
+
expect(result!.severity).toBe("critical");
|
|
76
|
+
expect(result!.reason).toContain("15 times");
|
|
77
|
+
expect(result!.reason).toContain("Edit");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("does not block normal codex file_change edits to the same file at 15 calls", async () => {
|
|
81
|
+
let result: Awaited<ReturnType<typeof checkToolLoop>> | undefined;
|
|
82
|
+
for (let i = 0; i < 15; i++) {
|
|
83
|
+
result = await checkToolLoop(SESSION_KEY, "Edit", {
|
|
84
|
+
changes: [{ path: "src/be/db.ts", kind: "update" }],
|
|
85
|
+
});
|
|
86
|
+
expect(result.blocked).toBe(false);
|
|
87
|
+
}
|
|
88
|
+
expect(result!.severity).toBe("warning");
|
|
89
|
+
expect(result!.reason).toContain("15 times");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("still blocks prolonged codex file_change repetition", async () => {
|
|
93
|
+
let result: Awaited<ReturnType<typeof checkToolLoop>> | undefined;
|
|
94
|
+
for (let i = 0; i < 24; i++) {
|
|
95
|
+
result = await checkToolLoop(SESSION_KEY, "Edit", {
|
|
96
|
+
changes: [{ path: "src/be/db.ts", kind: "update" }],
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
expect(result!.blocked).toBe(true);
|
|
100
|
+
expect(result!.severity).toBe("critical");
|
|
101
|
+
expect(result!.reason).toContain("24 times");
|
|
102
|
+
expect(result!.reason).toContain("Edit");
|
|
103
|
+
});
|
|
104
|
+
|
|
65
105
|
test("does not trigger for different args on same tool", async () => {
|
|
66
106
|
for (let i = 0; i < 20; i++) {
|
|
67
107
|
const result = await checkToolLoop(SESSION_KEY, "Read", {
|
|
@@ -22,6 +22,7 @@ const HISTORY_DIR = "/tmp/agent-swarm-tool-history";
|
|
|
22
22
|
const MAX_HISTORY = 30; // Sliding window size
|
|
23
23
|
const REPEAT_WARNING_THRESHOLD = 8;
|
|
24
24
|
const REPEAT_CRITICAL_THRESHOLD = 15;
|
|
25
|
+
const LOW_CARDINALITY_FILE_CHANGE_CRITICAL_THRESHOLD = 24;
|
|
25
26
|
const PINGPONG_WARNING_THRESHOLD = 6;
|
|
26
27
|
const PINGPONG_CRITICAL_THRESHOLD = 12;
|
|
27
28
|
|
|
@@ -40,6 +41,38 @@ function hashArgs(args: Record<string, unknown>): string {
|
|
|
40
41
|
return hash.toString(36);
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
function isCodexFileChangeArgs(toolName: string, toolInput: Record<string, unknown>): boolean {
|
|
45
|
+
if (toolName !== "Edit" && toolName !== "Write" && toolName !== "Delete") {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const topLevelKeys = Object.keys(toolInput);
|
|
50
|
+
if (topLevelKeys.length !== 1 || topLevelKeys[0] !== "changes") {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const changes = toolInput.changes;
|
|
55
|
+
if (!Array.isArray(changes) || changes.length === 0) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return changes.every((change) => {
|
|
60
|
+
if (!change || typeof change !== "object" || Array.isArray(change)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const entry = change as Record<string, unknown>;
|
|
65
|
+
const keys = Object.keys(entry).sort();
|
|
66
|
+
return (
|
|
67
|
+
keys.length === 2 &&
|
|
68
|
+
keys[0] === "kind" &&
|
|
69
|
+
keys[1] === "path" &&
|
|
70
|
+
typeof entry.path === "string" &&
|
|
71
|
+
(entry.kind === "add" || entry.kind === "update" || entry.kind === "delete")
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
43
76
|
/**
|
|
44
77
|
* Load tool call history for a session.
|
|
45
78
|
*/
|
|
@@ -92,6 +125,9 @@ export async function checkToolLoop(
|
|
|
92
125
|
|
|
93
126
|
// Strategy 1: Same tool + same args repeated
|
|
94
127
|
const key = `${toolName}:${argsHash}`;
|
|
128
|
+
const repeatCriticalThreshold = isCodexFileChangeArgs(toolName, toolInput)
|
|
129
|
+
? LOW_CARDINALITY_FILE_CHANGE_CRITICAL_THRESHOLD
|
|
130
|
+
: REPEAT_CRITICAL_THRESHOLD;
|
|
95
131
|
let repeatCount = 0;
|
|
96
132
|
for (const record of history) {
|
|
97
133
|
if (`${record.toolName}:${record.argsHash}` === key) {
|
|
@@ -99,7 +135,7 @@ export async function checkToolLoop(
|
|
|
99
135
|
}
|
|
100
136
|
}
|
|
101
137
|
|
|
102
|
-
if (repeatCount >=
|
|
138
|
+
if (repeatCount >= repeatCriticalThreshold) {
|
|
103
139
|
return {
|
|
104
140
|
blocked: true,
|
|
105
141
|
severity: "critical",
|
package/src/http/agents.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
updateAgentStatus,
|
|
22
22
|
upsertSwarmConfig,
|
|
23
23
|
} from "../be/db";
|
|
24
|
+
import { telemetry } from "../telemetry";
|
|
24
25
|
import {
|
|
25
26
|
AgentCredStatusSchema,
|
|
26
27
|
AgentLatestModelSchema,
|
|
@@ -322,6 +323,12 @@ export async function handleAgentRegister(
|
|
|
322
323
|
return { agent, created: true };
|
|
323
324
|
})();
|
|
324
325
|
|
|
326
|
+
telemetry.agent("registered", {
|
|
327
|
+
role: parsed.body.role,
|
|
328
|
+
capabilities: parsed.body.capabilities ?? [],
|
|
329
|
+
isReconnect: !result.created,
|
|
330
|
+
});
|
|
331
|
+
|
|
325
332
|
if (result.created) {
|
|
326
333
|
ensure({
|
|
327
334
|
id: "registered",
|
package/src/http/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
withSpanContext,
|
|
23
23
|
} from "../otel";
|
|
24
24
|
import { startScriptRunSupervisor, stopScriptRunSupervisor } from "../script-workflows/supervisor";
|
|
25
|
+
import { getServerSessionsProcessed } from "../server-runtime-counters";
|
|
25
26
|
import { startSlackApp, stopSlackApp } from "../slack";
|
|
26
27
|
import { initTelemetry, telemetry } from "../telemetry";
|
|
27
28
|
import { getApiKey } from "../utils/api-key";
|
|
@@ -113,6 +114,8 @@ const globalState = globalThis as typeof globalThis & {
|
|
|
113
114
|
|
|
114
115
|
const API_GC_INTERVAL_MS = 5 * 60 * 1000;
|
|
115
116
|
const MCP_TRANSPORT_IDLE_TIMEOUT_MS = DEFAULT_MCP_TRANSPORT_IDLE_TIMEOUT_MS;
|
|
117
|
+
const serverStartedAt = Date.now();
|
|
118
|
+
let shutdownSignal = "unknown";
|
|
116
119
|
|
|
117
120
|
type GcCapableGlobal = typeof globalThis & { gc?: () => void };
|
|
118
121
|
|
|
@@ -363,6 +366,11 @@ globalState.__transportActivityUser = transportActivityUser;
|
|
|
363
366
|
|
|
364
367
|
async function shutdown() {
|
|
365
368
|
console.log("Shutting down HTTP server...");
|
|
369
|
+
telemetry.server("shutdown", {
|
|
370
|
+
signal: shutdownSignal,
|
|
371
|
+
uptimeMs: Date.now() - serverStartedAt,
|
|
372
|
+
sessionsProcessed: getServerSessionsProcessed(),
|
|
373
|
+
});
|
|
366
374
|
|
|
367
375
|
// Stop scheduler (if enabled)
|
|
368
376
|
if (hasCapability("scheduling")) {
|
|
@@ -424,8 +432,14 @@ async function shutdown() {
|
|
|
424
432
|
// Only register signal handlers once (avoid duplicates on hot reload)
|
|
425
433
|
if (!globalState.__sigintRegistered) {
|
|
426
434
|
globalState.__sigintRegistered = true;
|
|
427
|
-
process.on("SIGINT",
|
|
428
|
-
|
|
435
|
+
process.on("SIGINT", () => {
|
|
436
|
+
shutdownSignal = "SIGINT";
|
|
437
|
+
shutdown();
|
|
438
|
+
});
|
|
439
|
+
process.on("SIGTERM", () => {
|
|
440
|
+
shutdownSignal = "SIGTERM";
|
|
441
|
+
shutdown();
|
|
442
|
+
});
|
|
429
443
|
}
|
|
430
444
|
|
|
431
445
|
if (!globalState.__runId) {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "../be/db";
|
|
13
13
|
import { getAllTemplateDefinitions, getTemplateDefinition } from "../prompts/registry";
|
|
14
14
|
import { resolveTemplate } from "../prompts/resolver";
|
|
15
|
-
import { interpolate } from "../
|
|
15
|
+
import { interpolate } from "../utils/template";
|
|
16
16
|
import { route } from "./route-def";
|
|
17
17
|
import { json, jsonError } from "./utils";
|
|
18
18
|
|
package/src/http/repos.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
getSwarmRepos,
|
|
8
8
|
updateSwarmRepo,
|
|
9
9
|
} from "../be/db";
|
|
10
|
-
import { RepoGuidelinesSchema, SwarmRepoSchema } from "../types";
|
|
10
|
+
import { RepoGuidelinesSchema, RepoHooksSchema, SwarmRepoSchema } from "../types";
|
|
11
11
|
import { route } from "./route-def";
|
|
12
12
|
import { json, jsonError } from "./utils";
|
|
13
13
|
|
|
@@ -56,6 +56,7 @@ const createRepo = route({
|
|
|
56
56
|
clonePath: z.string().optional(),
|
|
57
57
|
defaultBranch: z.string().optional(),
|
|
58
58
|
autoClone: z.boolean().optional(),
|
|
59
|
+
hooks: RepoHooksSchema.optional(),
|
|
59
60
|
guidelines: RepoGuidelinesSchema.nullable().optional(),
|
|
60
61
|
}),
|
|
61
62
|
responses: {
|
|
@@ -78,6 +79,7 @@ const updateRepo = route({
|
|
|
78
79
|
clonePath: z.string().optional(),
|
|
79
80
|
defaultBranch: z.string().optional(),
|
|
80
81
|
autoClone: z.boolean().optional(),
|
|
82
|
+
hooks: RepoHooksSchema.nullable().optional(),
|
|
81
83
|
guidelines: RepoGuidelinesSchema.nullable().optional(),
|
|
82
84
|
}),
|
|
83
85
|
responses: {
|
|
@@ -141,6 +143,7 @@ export async function handleRepos(
|
|
|
141
143
|
clonePath: parsed.body.clonePath,
|
|
142
144
|
defaultBranch: parsed.body.defaultBranch,
|
|
143
145
|
autoClone: parsed.body.autoClone,
|
|
146
|
+
hooks: parsed.body.hooks,
|
|
144
147
|
guidelines: parsed.body.guidelines,
|
|
145
148
|
});
|
|
146
149
|
json(res, repo, 201);
|
|
@@ -165,6 +168,7 @@ export async function handleRepos(
|
|
|
165
168
|
clonePath: parsed.body.clonePath,
|
|
166
169
|
defaultBranch: parsed.body.defaultBranch,
|
|
167
170
|
autoClone: parsed.body.autoClone,
|
|
171
|
+
hooks: parsed.body.hooks,
|
|
168
172
|
guidelines: parsed.body.guidelines,
|
|
169
173
|
});
|
|
170
174
|
if (!updated) {
|
package/src/http/schedules.ts
CHANGED
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
updateScheduledTask,
|
|
14
14
|
} from "../be/db";
|
|
15
15
|
import { mergeScheduleTiming, validateRecurringTiming } from "../be/schedules/validate";
|
|
16
|
-
import { ModelTierSchema, splitLegacyModelAlias } from "../model-tiers";
|
|
17
16
|
import { calculateNextRun, createStandaloneScheduleTask } from "../scheduler/scheduler";
|
|
17
|
+
import { ModelTierSchema, splitLegacyModelAlias } from "../types";
|
|
18
18
|
import { getExecutorRegistry } from "../workflows";
|
|
19
19
|
import { handleScheduleTrigger } from "../workflows/triggers";
|
|
20
20
|
import { route } from "./route-def";
|
package/src/http/session-data.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getTaskById,
|
|
15
15
|
} from "../be/db";
|
|
16
16
|
import { normalizeModelKey } from "../be/pricing-normalize";
|
|
17
|
+
import { incrementServerSessionsProcessed } from "../server-runtime-counters";
|
|
17
18
|
import type { SessionCost, SessionCostSource } from "../types";
|
|
18
19
|
import { route } from "./route-def";
|
|
19
20
|
import { json, jsonError } from "./utils";
|
|
@@ -290,6 +291,7 @@ export async function handleSessionData(
|
|
|
290
291
|
isError: parsed.body.isError ?? false,
|
|
291
292
|
costSource,
|
|
292
293
|
});
|
|
294
|
+
incrementServerSessionsProcessed();
|
|
293
295
|
json(res, { success: true, cost }, 201);
|
|
294
296
|
} catch (error) {
|
|
295
297
|
console.error("[HTTP] Failed to create session cost:", error);
|