codeden 0.4.2 → 0.4.4
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 -1
- package/dist/cli.js +1 -1
- package/dist/{main-sdPbRDgf.js → main-CKLHi4sz.js} +292 -47
- package/dist/main-CKLHi4sz.js.map +1 -0
- package/dist/{main-B7rU7myk.js → main-D335_24e.js} +2 -2
- package/dist/{main-B7rU7myk.js.map → main-D335_24e.js.map} +1 -1
- package/dist/{runner-B20DGEAR.js → runner-DjavcMnG.js} +83 -8
- package/dist/runner-DjavcMnG.js.map +1 -0
- package/dist/runner.js +1 -1
- package/dist/web/assets/{core-CZsR2WCQ.js → core-BFbrQR07.js} +1 -1
- package/dist/web/assets/engine-oniguruma-Dod-24Yp.js +1 -0
- package/dist/web/assets/{highlighted-body-JLMTFACH-Tgy2loNJ.js → highlighted-body-JLMTFACH-C9Ii0196.js} +1 -1
- package/dist/web/assets/{highlights-C-rSaP9T.js → highlights-C4aNCgJw.js} +1 -1
- package/dist/web/assets/index-BIBpPpjX.css +1 -0
- package/dist/web/assets/{index-5SrUkic_.js → index-WLihS360.js} +209 -209
- package/dist/web/assets/langs-X885YKmd.js +1 -0
- package/dist/web/assets/mermaid-WFAXDIZX-Dh9Bmb5R.js +1 -0
- package/dist/web/assets/{shiki-CBcuG-Nl.js → shiki-MNiRnTih.js} +2 -2
- package/dist/web/assets/{stream-tokenizer-CzcO6yIN.js → stream-tokenizer-BOiamYv-.js} +1 -1
- package/dist/web/assets/{themes-BL20ecgi.js → themes-DPD-gG-5.js} +2 -2
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/dist/main-sdPbRDgf.js.map +0 -1
- package/dist/runner-B20DGEAR.js.map +0 -1
- package/dist/web/assets/engine-oniguruma-B6ZU4lV4.js +0 -1
- package/dist/web/assets/index-DcErU_f1.css +0 -1
- package/dist/web/assets/langs-DPvNNit8.js +0 -1
- package/dist/web/assets/mermaid-WFAXDIZX-df73_wYp.js +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as __require, r as __toESM, t as __commonJSMin } from "./rolldown-runtime-DegdyBIC.js";
|
|
2
|
-
import { $ as BrowserNavigateRequest, $n as
|
|
2
|
+
import { $ as BrowserNavigateRequest, $n as InboxItemKind, $t as isNewerVersion, A as SessionFileQuery, An as TriagePolicy, Ar as unknown, At as RespondRequest, B as NetworkStatus, Bn as TerminalClientMessage, Bt as HealthResponse, C as isElectronRuntime, Cn as isFactoryMode, Cr as number, Ct as CreateProjectRequest, D as CODEDEN_VERSION, Dn as AutomationGuards, Dr as record, Dt as ListSessionsQuery, E as wrapper_default, En as AutomationAction, Er as preprocess, Et as ListInboxQuery, F as ListConflictsQuery, Fn as GitCommitRequest, Ft as StartSessionRequest, G as SetNetworkPasswordResponse, Gn as ConflictDecision, Gt as LOCAL_STT_SAMPLE_RATE, H as RemoteAccessFile, Hn as RunnerCommandResults, Ht as READ_ALOUD_FULL_MAX_CHARS, I as ListNoticesQuery, In as GitPushRequest, It as SteerRequest, J as ImageTestRequest, Jn as Project, Jt as TtsRequest, K as SignInRequest, Kn as StartedBy, Kt as LocalSttModelParams, L as ResolveConflictRequest, Ln as RestoreFilesRequest, Lt as ThreadItemsQuery, M as RunnerToServerMessage, Mn as SessionStatus, Mr as toJSONSchema, Mt as SendMessageRequest, N as ServerLock, Nn as DiffScope, Nr as safeParse$1, Nt as SessionStartMeta, O as UsageQuery, On as AutomationRun, Or as string, Ot as ReorderProjectsRequest, P as ArchiveSessionRequest, Pn as EditQueuedMessageRequest, Pr as prettifyError, Pt as SetSecretRequest, Q as BrowserHistoryRequest, Qn as InboxItem, Qt as compareVersions, R as RoleMessageRequest, Rn as SessionResult, Rt as UiClientMessage, S as acquireInstallLock, Sn as SetAutonomyModeRequest, Sr as looseObject, St as UpdateAutomationRequest, T as import_websocket_server, Tn as Automation, Tr as optional, Tt as FileWriteRequest, U as SetNetworkAccessRequest, Un as SessionOptions, Ut as Settings, V as PASSWORD_RULE_TEXT, Vn as TerminalQuery, Vt as agentEventToNewEvent, W as SetNetworkPasswordRequest, Wn as ProjectConfigFile, Wt as speechExcerpt, X as BrowserClientMessage, Xn as DEFAULT_VISION_PATH, Xt as UpdateInstallRequest, Y as summarizeActivity, Yn as sortProjects, Yt as AppPointer, Z as BrowserDevServerRequest, Zn as FactoryLimits, Zt as UpdateStatus, _ as importClaudeSdkFrom, _n as FactoryTuning, _r as custom, _t as StarterSetRequest, a as Hono, an as GithubLabelRequest, ar as AgentConfigWriteRequest, at as AutomationImportSource, b as runNpm, bn as PutVisionRequest, br as intersection, bt as UpcomingRunsQuery, c as createAdaptorServer, cn as MergePrRequest, cr as McpServerUpsertRequest, ct as ImportAutomationsRequest, d as createImageEngineRegistry, dn as parseGithubPrId, dr as ReasoningEffort, dt as AutomationProjectRequest, en as ConflictRecord, er as messageFrom, et as BrowserNewTabRequest, f as IMAGE_ASPECT_RATIOS, fn as DiscoveryArea, fr as datetime, ft as CreateAutomationRequest, g as CLAUDE_SDK_VERSION, gn as FactoryTriageRequest, gr as boolean, gt as SetAutomationEnabledRequest, h as claudeSdkSource, hn as FactoryDecisionRequest, hr as array, ht as RunAutomationRequest, i as killTree$1, in as GithubIssue, ir as AgentConfigLocator, it as AUTOMATION_IMPORT_LIMITS, j as RunnerLock, jn as Session, jr as ZodError, jt as SecretKey, k as UsageSettings, kn as AutomationTrigger, kr as union$1, kt as ResolveInboxRequest, l as upgradeWebSocket, ln as TriageIssueRequest, lr as AutonomyLevel, lt as AutomationFileFrontmatter, m as ImageGenerationError, mn as FactoryCandidate, mr as _null, mt as ListAutomationsQuery, n as readLock, nn as CreateGithubIssueRequest, nr as AgentConfigDiffRequest, nt as BrowserViewportRequest, o as tryDecodeURIComponent, on as GithubListQuery, or as McpServerDiffRequest, ot as DuplicateAutomationRequest, p as resolveImageOutput, pn as FactoryAllowLargeChangeRequest, pr as _enum, pt as ListAutomationRunsQuery, q as passwordProblem, qn as WorkKey, qt as SttQuery, r as isAlive, rn as GithubCommentRequest, rr as AgentConfigListQuery, rt as BrowserWsQuery, s as bufferToFormData, sn as GithubPr, sr as McpServerTarget, st as ExportAutomationsQuery, tn as NOTICE_BATCH_CODE, tr as AgentConfigDeleteQuery, tt as BrowserOpenRequest, u as ImageEngineRegistry, un as githubPrId, ur as ProviderId, ut as AutomationGuardsPatch, v as installClaudeSdk, vn as KillSwitchRequest, vr as discriminatedUnion, vt as TriageDecisionRequest, w as ownNodeEnv, wn as Artifact, wr as object, wt as FileReadQuery, x as resolveNpm, xn as ReviewVerdict, xr as literal, xt as UpdateAutomationProjectSettingsRequest, y as isClaudeSdkInstalled, yn as LEGACY_SIZE_LIMITS, yr as int, yt as TriageReport, z as UpdateSessionRequest, zn as SpeechSummaryRequest, zt as UpdateProjectRequest } from "./runner-DjavcMnG.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import fs, { accessSync, appendFileSync, closeSync, constants, copyFileSync, cpSync, createReadStream, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
5
5
|
import { homedir, networkInterfaces, tmpdir } from "node:os";
|
|
@@ -31573,24 +31573,24 @@ function validateAutomation(a) {
|
|
|
31573
31573
|
if (a.action.type === "triage" && a.trigger.type === "cron") throw badRequest("Triage runs on issue events, not on a schedule", "invalid_trigger");
|
|
31574
31574
|
}
|
|
31575
31575
|
// ../server/src/services/automations/import.ts
|
|
31576
|
-
const tooLarge = (message) => new ServiceError(413, "import_too_large", message);
|
|
31576
|
+
const tooLarge$1 = (message) => new ServiceError(413, "import_too_large", message);
|
|
31577
31577
|
/** The last path segment of a zip entry or uploaded file name. */
|
|
31578
31578
|
const baseName = (name) => name.split(/[\\/]/).pop()?.trim() || "automation.md";
|
|
31579
31579
|
/** Read the `.md` entries of a zip, refusing oversized or overfull archives before inflating. */
|
|
31580
31580
|
function unzipMarkdown(zipBase64) {
|
|
31581
31581
|
const data = Buffer.from(zipBase64, "base64");
|
|
31582
31582
|
if (data.length === 0) throw badRequest("The zip is empty or not base64", "invalid_zip");
|
|
31583
|
-
if (data.length > AUTOMATION_IMPORT_LIMITS.maxZipBytes) throw tooLarge(`The zip is larger than ${AUTOMATION_IMPORT_LIMITS.maxZipBytes / 1024} KB`);
|
|
31583
|
+
if (data.length > AUTOMATION_IMPORT_LIMITS.maxZipBytes) throw tooLarge$1(`The zip is larger than ${AUTOMATION_IMPORT_LIMITS.maxZipBytes / 1024} KB`);
|
|
31584
31584
|
let entries = 0;
|
|
31585
31585
|
let total = 0;
|
|
31586
31586
|
const wanted = (file) => {
|
|
31587
31587
|
entries++;
|
|
31588
|
-
if (entries > AUTOMATION_IMPORT_LIMITS.maxZipEntries) throw tooLarge(`The zip lists more than ${AUTOMATION_IMPORT_LIMITS.maxZipEntries} entries`);
|
|
31588
|
+
if (entries > AUTOMATION_IMPORT_LIMITS.maxZipEntries) throw tooLarge$1(`The zip lists more than ${AUTOMATION_IMPORT_LIMITS.maxZipEntries} entries`);
|
|
31589
31589
|
const name = file.name.replace(/\\/g, "/");
|
|
31590
31590
|
if (name.endsWith("/") || name.startsWith("__MACOSX/") || !/\.md$/i.test(name)) return false;
|
|
31591
|
-
if (file.originalSize > AUTOMATION_IMPORT_LIMITS.maxFileChars) throw tooLarge(`${baseName(name)} is larger than ${AUTOMATION_IMPORT_LIMITS.maxFileChars / 1024} KB`);
|
|
31591
|
+
if (file.originalSize > AUTOMATION_IMPORT_LIMITS.maxFileChars) throw tooLarge$1(`${baseName(name)} is larger than ${AUTOMATION_IMPORT_LIMITS.maxFileChars / 1024} KB`);
|
|
31592
31592
|
total += file.originalSize;
|
|
31593
|
-
if (total > AUTOMATION_IMPORT_LIMITS.maxTotalChars) throw tooLarge(`The zip holds more than ${AUTOMATION_IMPORT_LIMITS.maxTotalChars / 1024} KB of automations`);
|
|
31593
|
+
if (total > AUTOMATION_IMPORT_LIMITS.maxTotalChars) throw tooLarge$1(`The zip holds more than ${AUTOMATION_IMPORT_LIMITS.maxTotalChars / 1024} KB of automations`);
|
|
31594
31594
|
return true;
|
|
31595
31595
|
};
|
|
31596
31596
|
let files;
|
|
@@ -31601,7 +31601,7 @@ function unzipMarkdown(zipBase64) {
|
|
|
31601
31601
|
throw badRequest(`Not a readable zip: ${errorMessage$1(error)}`, "invalid_zip");
|
|
31602
31602
|
}
|
|
31603
31603
|
return Object.entries(files).map(([name, bytes]) => {
|
|
31604
|
-
if (bytes.length > AUTOMATION_IMPORT_LIMITS.maxFileChars) throw tooLarge(`${baseName(name)} is too large`);
|
|
31604
|
+
if (bytes.length > AUTOMATION_IMPORT_LIMITS.maxFileChars) throw tooLarge$1(`${baseName(name)} is too large`);
|
|
31605
31605
|
return {
|
|
31606
31606
|
name: baseName(name),
|
|
31607
31607
|
content: strFromU8(bytes)
|
|
@@ -31630,8 +31630,8 @@ function collectImportFiles(source) {
|
|
|
31630
31630
|
...source.text?.trim() ? [pastedFile(source.text)] : []
|
|
31631
31631
|
];
|
|
31632
31632
|
if (files.length === 0) throw badRequest("No .md automation files found", "import_empty");
|
|
31633
|
-
if (files.length > AUTOMATION_IMPORT_LIMITS.maxFiles) throw tooLarge(`At most ${AUTOMATION_IMPORT_LIMITS.maxFiles} automations can be imported at once`);
|
|
31634
|
-
if (files.reduce((n, f) => n + f.content.length, 0) > AUTOMATION_IMPORT_LIMITS.maxTotalChars) throw tooLarge(`The import holds more than ${AUTOMATION_IMPORT_LIMITS.maxTotalChars / 1024} KB`);
|
|
31633
|
+
if (files.length > AUTOMATION_IMPORT_LIMITS.maxFiles) throw tooLarge$1(`At most ${AUTOMATION_IMPORT_LIMITS.maxFiles} automations can be imported at once`);
|
|
31634
|
+
if (files.reduce((n, f) => n + f.content.length, 0) > AUTOMATION_IMPORT_LIMITS.maxTotalChars) throw tooLarge$1(`The import holds more than ${AUTOMATION_IMPORT_LIMITS.maxTotalChars / 1024} KB`);
|
|
31635
31635
|
const seen = /* @__PURE__ */ new Map();
|
|
31636
31636
|
return files.map((f) => {
|
|
31637
31637
|
const key = f.name.toLowerCase();
|
|
@@ -33984,6 +33984,18 @@ function fixPrompt(item, failed) {
|
|
|
33984
33984
|
"Fix these in the same branch, run the tests, commit and push (call `create_pull_request` again to update the PR), then call `finish`."
|
|
33985
33985
|
].filter((l) => l !== "").join("\n");
|
|
33986
33986
|
}
|
|
33987
|
+
/** Message to the PM when a pull request is over the size limit. */
|
|
33988
|
+
function tooLargePrompt(item, detail, failed) {
|
|
33989
|
+
const others = failed.filter((c) => c.name !== "size");
|
|
33990
|
+
const pr = item.pr ? `pull request #${item.pr.number}` : "its pull request";
|
|
33991
|
+
return [
|
|
33992
|
+
`Item ${item.id} ("${item.title}", ${pr}) is too large to merge automatically: ${detail}.`,
|
|
33993
|
+
others.length > 0 ? `It also failed: ${others.map((c) => `${c.name} (${c.detail ?? "failed"})`).join("; ")}.` : "Every other check that ran passed.",
|
|
33994
|
+
"Decide now; don't ask the user:",
|
|
33995
|
+
"- If the work can be split, open the parts as smaller issues with `create_issue` (each under the limit, with acceptance criteria). They become candidates; triage them as usual. Then close the oversized pull request on GitHub.",
|
|
33996
|
+
`- If it can't be split (an initial scaffold, a stack migration), call \`factory_allow_large_change\` with item ${item.id} and the reason. The checks run again${others.length > 0 ? ", the builder gets a fix round for the other failures," : ""} and it merges once the rest pass.`
|
|
33997
|
+
].join("\n");
|
|
33998
|
+
}
|
|
33987
33999
|
/** Ask a reviewer to look again after fixes. */
|
|
33988
34000
|
function reReviewPrompt(item) {
|
|
33989
34001
|
return `The author pushed fixes to pull request #${item.pr?.number ?? "?"}. Review the updated change again against the same criteria and call \`factory_submit_review\` once with a fresh verdict.`;
|
|
@@ -33998,12 +34010,13 @@ function revertPrompt(opts) {
|
|
|
33998
34010
|
* The PM's structured triage instructions for new candidates. `unattended` (dark factory): the
|
|
33999
34011
|
* PM makes every judgement call itself and records it, since nobody is watching to answer.
|
|
34000
34012
|
*/
|
|
34001
|
-
function triageInstructions(items, unattended = false) {
|
|
34013
|
+
function triageInstructions(items, unattended = false, sizeLimit = "no limit") {
|
|
34002
34014
|
return [
|
|
34003
34015
|
`The factory has ${items.length} new candidate${items.length === 1 ? "" : "s"} to triage. Score each against the project vision (read it with read_project_notes or the vision file) and decide:`,
|
|
34004
34016
|
...items.map((i) => `- item ${i.id} [${i.source}${i.area ? `/${i.area}` : ""}] "${i.title}": ${i.summary.slice(0, 300)}${i.acceptanceCriteria.length ? ` | criteria: ${i.acceptanceCriteria.join("; ").slice(0, 400)}` : ""}`),
|
|
34005
34017
|
"",
|
|
34006
34018
|
"For each, call `factory_triage` with decision `accept` (a score from 0 to 1 and, if needed, sharper acceptance criteria) or `reject` (with the reason). Accepted items become GitHub issues and are built and merged automatically within the limits; reject anything outside the vision, too large, or touching protected areas.",
|
|
34019
|
+
sizeLimit === "no limit" ? "" : `Size: pull requests over ${sizeLimit} (lockfiles and generated or vendored files don't count) aren't merged automatically. Split bigger work into separate items that each fit (create the extra ones with \`create_issue\`). If the work can't be split (an initial scaffold, a stack migration), accept it with \`largeChange\` and the reason instead.`,
|
|
34007
34020
|
"The vision wins over the current code. When the code doesn't match it (a different stack or architecture, say), the work that brings it in line comes first: score it highest, or open it with `create_issue` if no candidate covers it (new issues become candidates). Reject work that change would throw away, saying it waits for it.",
|
|
34008
34021
|
unattended ? "This project runs as a dark factory: nobody is watching, so never ask the user. Make every judgement call yourself from the vision and record the ones that matter with `factory_record_decision` (the user reviews them in the inbox and the daily digest). Flag `needsHuman` only for a protected area, a missing or self-contradicting vision, or something irreversible outside the code, and triage everything else meanwhile." : ""
|
|
34009
34022
|
].filter((l) => l !== "").join("\n");
|
|
@@ -34235,16 +34248,19 @@ async function answerProviderQuestion(ctx, respond, e) {
|
|
|
34235
34248
|
return false;
|
|
34236
34249
|
}
|
|
34237
34250
|
}
|
|
34238
|
-
/**
|
|
34239
|
-
|
|
34240
|
-
|
|
34251
|
+
/**
|
|
34252
|
+
* `factory_record_decision`: put a factory decision in front of the user (the inbox and the
|
|
34253
|
+
* daily digest). Also used for the PM's size allowances.
|
|
34254
|
+
*/
|
|
34255
|
+
function recordDecision(ctx, by, req) {
|
|
34256
|
+
if (!by.projectId) throw badRequest("Only a project's sessions record factory decisions", "no_project");
|
|
34241
34257
|
const id = `${DECISION_INBOX_PREFIX}${randomUUID()}`;
|
|
34242
34258
|
raiseInbox(ctx, {
|
|
34243
34259
|
id,
|
|
34244
34260
|
kind: "message",
|
|
34245
34261
|
priority: req.needsHuman ? "high" : "low",
|
|
34246
|
-
projectId:
|
|
34247
|
-
sessionId:
|
|
34262
|
+
projectId: by.projectId,
|
|
34263
|
+
sessionId: by.sessionId,
|
|
34248
34264
|
title: `${req.needsHuman ? "Factory needs you" : "Factory decided"}: ${oneLine(req.question, 120)}`,
|
|
34249
34265
|
body: [
|
|
34250
34266
|
`**Question:** ${req.question}`,
|
|
@@ -34697,7 +34713,7 @@ const factoryTools = [
|
|
|
34697
34713
|
}),
|
|
34698
34714
|
defineTool({
|
|
34699
34715
|
name: "factory_triage",
|
|
34700
|
-
description: "Decide on a factory candidate after scoring it against the vision. `accept` with a score (0..1) and, if needed, sharper acceptance criteria: CodeDen creates the GitHub issue and builds it within the limits. `reject` with the reason (outside the vision, too big, touches protected areas, duplicate).",
|
|
34716
|
+
description: "Decide on a factory candidate after scoring it against the vision. `accept` with a score (0..1) and, if needed, sharper acceptance criteria: CodeDen creates the GitHub issue and builds it within the limits. `reject` with the reason (outside the vision, too big, touches protected areas, duplicate). Work over the size limit that can't be split (an initial scaffold, a stack migration): accept with `largeChange` and the reason.",
|
|
34701
34717
|
roles: ["pm"],
|
|
34702
34718
|
scope: "project",
|
|
34703
34719
|
alwaysLoad: true,
|
|
@@ -34713,6 +34729,23 @@ const factoryTools = [
|
|
|
34713
34729
|
};
|
|
34714
34730
|
})
|
|
34715
34731
|
}),
|
|
34732
|
+
defineTool({
|
|
34733
|
+
name: "factory_allow_large_change",
|
|
34734
|
+
description: "Let a factory item whose pull request failed the size check go ahead after all, because the work can't be split (an initial scaffold, a stack migration). Give the item id and the reason. The merge checks run again; CI, the review, tests and protected areas still apply. Prefer splitting the work into smaller issues when it can be split.",
|
|
34735
|
+
roles: ["pm"],
|
|
34736
|
+
scope: "project",
|
|
34737
|
+
alwaysLoad: true,
|
|
34738
|
+
isAvailable: inFactoryProject,
|
|
34739
|
+
input: FactoryAllowLargeChangeRequest,
|
|
34740
|
+
handler: async (input, { services, projectId }) => asTool(() => {
|
|
34741
|
+
const item = services.factory.allowLargeChange(projectId, input);
|
|
34742
|
+
return {
|
|
34743
|
+
itemId: item.id,
|
|
34744
|
+
stage: item.stage,
|
|
34745
|
+
pr: item.pr
|
|
34746
|
+
};
|
|
34747
|
+
})
|
|
34748
|
+
}),
|
|
34716
34749
|
defineTool({
|
|
34717
34750
|
name: "factory_record_decision",
|
|
34718
34751
|
description: "Record a decision you made on your own instead of asking the user (this project runs as a dark factory, so nobody waits to answer). Give the question you would have asked, what you decided and why, grounded in the vision. It goes to the user's inbox and the daily digest so they can review or override it; nothing waits on it. Set `needsHuman` only for a protected area, a missing or self-contradicting vision, or something irreversible outside the code: that flags it for the user, and you carry on with the work that doesn't depend on it.",
|
|
@@ -46527,6 +46560,42 @@ function killTree(pid) {
|
|
|
46527
46560
|
process.kill(pid, "SIGKILL");
|
|
46528
46561
|
} catch {}
|
|
46529
46562
|
}
|
|
46563
|
+
// ../server/src/services/factory/large-change.ts
|
|
46564
|
+
/**
|
|
46565
|
+
* Work that can't be split under the size limit (an initial scaffold, a stack migration). The
|
|
46566
|
+
* PM allows it at triage or, after the size check failed, with `factory_allow_large_change`.
|
|
46567
|
+
* Either way it is recorded as a factory decision, so the user sees it in the inbox and the
|
|
46568
|
+
* digest. Every other merge check still applies.
|
|
46569
|
+
*/
|
|
46570
|
+
/** Record the PM's allowance for `item` as a factory decision. */
|
|
46571
|
+
function recordLargeChange(ctx, item, reason) {
|
|
46572
|
+
recordDecision(ctx, {
|
|
46573
|
+
projectId: item.projectId,
|
|
46574
|
+
sessionId: null
|
|
46575
|
+
}, {
|
|
46576
|
+
question: `"${item.title}" is bigger than the factory's size limit. Split it, or build and merge it as one change?`,
|
|
46577
|
+
decision: "Build and merge it as one change",
|
|
46578
|
+
reasoning: reason
|
|
46579
|
+
});
|
|
46580
|
+
}
|
|
46581
|
+
/** `factory_allow_large_change`: let an item that failed on size go ahead after all. */
|
|
46582
|
+
function allowLargeChange(ctx, projectId, req) {
|
|
46583
|
+
const item = ctx.store.item(req.itemId);
|
|
46584
|
+
if (!item || item.projectId !== projectId) throw notFound(`No factory item ${req.itemId}`, "item_not_found");
|
|
46585
|
+
if (item.stage !== "failed" && item.stage !== "verifying") throw conflict(`Item ${item.id} is ${item.stage}; only an item whose pull request is being verified, or failed its checks, can be allowed`, "not_verifiable");
|
|
46586
|
+
if (item.pr?.state !== "open") throw conflict(`Item ${item.id} has no open pull request`, "no_open_pr");
|
|
46587
|
+
const sizeFailed = item.checks.some((c) => c.name === "size" && c.state === "fail");
|
|
46588
|
+
if (item.stage === "failed" && !sizeFailed) throw conflict(`Item ${item.id} didn't fail on size: ${item.reason ?? ""}`, "not_size");
|
|
46589
|
+
recordLargeChange(ctx, item, req.reason);
|
|
46590
|
+
return ctx.store.updateItem(item.id, (i) => ({
|
|
46591
|
+
...i,
|
|
46592
|
+
largeChange: req.reason,
|
|
46593
|
+
stage: "verifying",
|
|
46594
|
+
reason: null,
|
|
46595
|
+
finishedAt: null,
|
|
46596
|
+
note: "Allowed over the size limit by the PM; checking again"
|
|
46597
|
+
}));
|
|
46598
|
+
}
|
|
46530
46599
|
// ../server/src/services/factory/globs.ts
|
|
46531
46600
|
/**
|
|
46532
46601
|
* Small glob matcher for protected paths (§10.1): `**` matches any number of path segments,
|
|
@@ -46675,7 +46744,7 @@ function slugify$1(title) {
|
|
|
46675
46744
|
// ../server/src/services/factory/outcomes.ts
|
|
46676
46745
|
const AUDIT_TIMEOUT_MS = 3e5;
|
|
46677
46746
|
/** Mark an item failed and count it; pauses the loop after too many failures in a row. */
|
|
46678
|
-
function failItem(ctx, loop, item, reason) {
|
|
46747
|
+
function failItem(ctx, loop, item, reason, opts = {}) {
|
|
46679
46748
|
const at = ctx.now().toISOString();
|
|
46680
46749
|
ctx.store.updateItem(item.id, (i) => ({
|
|
46681
46750
|
...i,
|
|
@@ -46684,7 +46753,7 @@ function failItem(ctx, loop, item, reason) {
|
|
|
46684
46753
|
note: null,
|
|
46685
46754
|
finishedAt: at
|
|
46686
46755
|
}));
|
|
46687
|
-
factoryNotice(ctx, {
|
|
46756
|
+
if (opts.notify !== false) factoryNotice(ctx, {
|
|
46688
46757
|
id: `factory.failed:${item.id}`,
|
|
46689
46758
|
projectId: item.projectId,
|
|
46690
46759
|
target: "pm",
|
|
@@ -46694,6 +46763,7 @@ function failItem(ctx, loop, item, reason) {
|
|
|
46694
46763
|
itemIds: [item.id],
|
|
46695
46764
|
wake: false
|
|
46696
46765
|
});
|
|
46766
|
+
if (opts.countsAsFailure === false) return loop;
|
|
46697
46767
|
const next = {
|
|
46698
46768
|
...loop,
|
|
46699
46769
|
consecutiveFailures: loop.consecutiveFailures + 1
|
|
@@ -46947,6 +47017,88 @@ async function regression(ctx, project, loop, item) {
|
|
|
46947
47017
|
next = pauseLoop(ctx, next, `CI on ${branch} failed after merging #${prNumber}; ${revertNote}`, { inbox: false });
|
|
46948
47018
|
return next;
|
|
46949
47019
|
}
|
|
47020
|
+
// ../server/src/services/factory/size.ts
|
|
47021
|
+
/**
|
|
47022
|
+
* The size merge check (§10.1 step 4) counts code someone has to review. Lockfiles and
|
|
47023
|
+
* generated or vendored files are left out: a new dependency can add thousands of lockfile
|
|
47024
|
+
* lines, and a scaffold copies in UI components nobody reviews line by line.
|
|
47025
|
+
*/
|
|
47026
|
+
const LOCKFILES = /* @__PURE__ */ new Set([
|
|
47027
|
+
"package-lock.json",
|
|
47028
|
+
"npm-shrinkwrap.json",
|
|
47029
|
+
"pnpm-lock.yaml",
|
|
47030
|
+
"yarn.lock",
|
|
47031
|
+
"bun.lock",
|
|
47032
|
+
"bun.lockb",
|
|
47033
|
+
"deno.lock",
|
|
47034
|
+
"cargo.lock",
|
|
47035
|
+
"poetry.lock",
|
|
47036
|
+
"uv.lock",
|
|
47037
|
+
"pipfile.lock",
|
|
47038
|
+
"pdm.lock",
|
|
47039
|
+
"gemfile.lock",
|
|
47040
|
+
"composer.lock",
|
|
47041
|
+
"go.sum",
|
|
47042
|
+
"flake.lock",
|
|
47043
|
+
"packages.lock.json",
|
|
47044
|
+
"pubspec.lock",
|
|
47045
|
+
"mix.lock",
|
|
47046
|
+
"podfile.lock",
|
|
47047
|
+
"package.resolved"
|
|
47048
|
+
]);
|
|
47049
|
+
/** Folders whose contents are built, installed or vendored rather than written. */
|
|
47050
|
+
const GENERATED_DIRS = [
|
|
47051
|
+
"node_modules",
|
|
47052
|
+
"vendor",
|
|
47053
|
+
"dist",
|
|
47054
|
+
"build",
|
|
47055
|
+
".next",
|
|
47056
|
+
".nuxt",
|
|
47057
|
+
".svelte-kit",
|
|
47058
|
+
"coverage",
|
|
47059
|
+
"__snapshots__",
|
|
47060
|
+
"__generated__"
|
|
47061
|
+
];
|
|
47062
|
+
/** File names that mark generated output. */
|
|
47063
|
+
const GENERATED_NAME = /\.(min\.(js|css)|map|snap|lock)$|\.(gen|generated)\.[a-z]+$/i;
|
|
47064
|
+
/** True for files the size check doesn't count (lockfiles, generated or vendored files). */
|
|
47065
|
+
function isUncountedFile(path) {
|
|
47066
|
+
const parts = path.replace(/\\/g, "/").split("/");
|
|
47067
|
+
const name = (parts.at(-1) ?? "").toLowerCase();
|
|
47068
|
+
if (LOCKFILES.has(name) || GENERATED_NAME.test(name)) return true;
|
|
47069
|
+
const dirs = parts.slice(0, -1);
|
|
47070
|
+
if (dirs.some((d) => GENERATED_DIRS.includes(d))) return true;
|
|
47071
|
+
return dirs.some((d, i) => d === "components" && dirs[i + 1] === "ui");
|
|
47072
|
+
}
|
|
47073
|
+
/** Lines and files the size check counts for a PR. */
|
|
47074
|
+
function changeSize(pr) {
|
|
47075
|
+
if (!pr.fileLines) return {
|
|
47076
|
+
lines: pr.additions + pr.deletions,
|
|
47077
|
+
files: pr.files.length,
|
|
47078
|
+
uncounted: 0
|
|
47079
|
+
};
|
|
47080
|
+
let lines = 0;
|
|
47081
|
+
let files = 0;
|
|
47082
|
+
for (const f of pr.fileLines) {
|
|
47083
|
+
if (isUncountedFile(f.path)) continue;
|
|
47084
|
+
lines += f.lines;
|
|
47085
|
+
files += 1;
|
|
47086
|
+
}
|
|
47087
|
+
return {
|
|
47088
|
+
lines,
|
|
47089
|
+
files,
|
|
47090
|
+
uncounted: pr.fileLines.length - files
|
|
47091
|
+
};
|
|
47092
|
+
}
|
|
47093
|
+
/** The size limits as a phrase ("5,000 lines", "no limit"). */
|
|
47094
|
+
function sizeLimitText(tuning) {
|
|
47095
|
+
const parts = [tuning.maxChangedLines !== null ? `${tuning.maxChangedLines.toLocaleString("en")} lines` : "", tuning.maxChangedFiles !== null ? `${tuning.maxChangedFiles.toLocaleString("en")} files` : ""].filter(Boolean);
|
|
47096
|
+
return parts.length > 0 ? parts.join(", ") : "no limit";
|
|
47097
|
+
}
|
|
47098
|
+
/** True when the change is over a limit that is set. */
|
|
47099
|
+
function overSizeLimit(size, tuning) {
|
|
47100
|
+
return tuning.maxChangedLines !== null && size.lines > tuning.maxChangedLines || tuning.maxChangedFiles !== null && size.files > tuning.maxChangedFiles;
|
|
47101
|
+
}
|
|
46950
47102
|
// ../server/src/services/factory/store.ts
|
|
46951
47103
|
/** Stages of items that are still moving through the loop. */
|
|
46952
47104
|
const ACTIVE_STAGES = [
|
|
@@ -46976,7 +47128,8 @@ var FactoryStore = class {
|
|
|
46976
47128
|
this.events = events;
|
|
46977
47129
|
}
|
|
46978
47130
|
loop(projectId) {
|
|
46979
|
-
|
|
47131
|
+
const loop = this.db.select().from(factoryLoops).where(eq(factoryLoops.projectId, projectId)).get()?.loop;
|
|
47132
|
+
return loop ? upgradeSizeLimits(loop) : void 0;
|
|
46980
47133
|
}
|
|
46981
47134
|
/** The project's loop, or a fresh idle one (not saved). */
|
|
46982
47135
|
loopOrNew(projectId, now = /* @__PURE__ */ new Date()) {
|
|
@@ -47044,6 +47197,7 @@ var FactoryStore = class {
|
|
|
47044
47197
|
revertOf: null,
|
|
47045
47198
|
triageRequestedAt: null,
|
|
47046
47199
|
nudges: {},
|
|
47200
|
+
largeChange: null,
|
|
47047
47201
|
mergedAt: null,
|
|
47048
47202
|
finishedAt: null,
|
|
47049
47203
|
...draft,
|
|
@@ -47086,6 +47240,24 @@ var FactoryStore = class {
|
|
|
47086
47240
|
}]);
|
|
47087
47241
|
}
|
|
47088
47242
|
};
|
|
47243
|
+
/**
|
|
47244
|
+
* Loops saved before 0.4.4 carry the old default size limits (800 lines, 40 files, counting
|
|
47245
|
+
* lockfiles and generated files), which blocked ordinary scaffolds. Untouched old defaults
|
|
47246
|
+
* become the current defaults; limits someone chose are kept.
|
|
47247
|
+
*/
|
|
47248
|
+
function upgradeSizeLimits(loop) {
|
|
47249
|
+
const t = loop.tuning;
|
|
47250
|
+
if (t.maxChangedLines !== LEGACY_SIZE_LIMITS.maxChangedLines || t.maxChangedFiles !== LEGACY_SIZE_LIMITS.maxChangedFiles) return loop;
|
|
47251
|
+
const defaults = FactoryTuning.parse({});
|
|
47252
|
+
return {
|
|
47253
|
+
...loop,
|
|
47254
|
+
tuning: {
|
|
47255
|
+
...t,
|
|
47256
|
+
maxChangedLines: defaults.maxChangedLines,
|
|
47257
|
+
maxChangedFiles: defaults.maxChangedFiles
|
|
47258
|
+
}
|
|
47259
|
+
};
|
|
47260
|
+
}
|
|
47089
47261
|
// ../server/src/services/factory/checks.ts
|
|
47090
47262
|
const ORDER$1 = [
|
|
47091
47263
|
"protected_paths",
|
|
@@ -47125,9 +47297,11 @@ function evaluateChecks(i) {
|
|
|
47125
47297
|
});
|
|
47126
47298
|
if (hits.paths.length > 0 || hits.topics.length > 0) set("protected_paths", "fail", `Touches protected areas (${[hits.paths.length ? `paths: ${list(hits.paths.map((h) => h.path))}` : "", hits.topics.length ? `topics: ${hits.topics.join(", ")}` : ""].filter(Boolean).join("; ")})`, true);
|
|
47127
47299
|
else set("protected_paths", "pass", null);
|
|
47128
|
-
const
|
|
47129
|
-
|
|
47130
|
-
|
|
47300
|
+
const size = changeSize(i.pr);
|
|
47301
|
+
const measured = `${size.lines.toLocaleString("en")} lines in ${size.files} files${size.uncounted ? ` (${size.uncounted} lockfiles or generated files not counted)` : ""}`;
|
|
47302
|
+
if (!overSizeLimit(size, i.tuning)) set("size", "pass", measured);
|
|
47303
|
+
else if (i.largeChange) set("size", "pass", `${measured}; allowed by the PM: ${i.largeChange}`);
|
|
47304
|
+
else set("size", "fail", `${measured}, over the limit of ${sizeLimitText(i.tuning)}`);
|
|
47131
47305
|
switch (i.pr.checks) {
|
|
47132
47306
|
case "success":
|
|
47133
47307
|
set("ci", "pass", null);
|
|
@@ -47238,6 +47412,7 @@ async function advanceVerifying(ctx, project, loop, item) {
|
|
|
47238
47412
|
ciGraceOver: now.getTime() - since > ctx.ciGraceMs,
|
|
47239
47413
|
limits: project.factoryLimits,
|
|
47240
47414
|
tuning: loop.tuning,
|
|
47415
|
+
largeChange: item.largeChange ?? null,
|
|
47241
47416
|
areas: vision.parsed.protectedAreas,
|
|
47242
47417
|
now: now.toISOString()
|
|
47243
47418
|
});
|
|
@@ -47414,6 +47589,7 @@ function failReview(ctx, project, loop, item, reason) {
|
|
|
47414
47589
|
/** Failed checks: one more fix round if allowed, otherwise the item fails (and may pause). */
|
|
47415
47590
|
async function handleFailure(ctx, project, loop, item) {
|
|
47416
47591
|
const failed = failedChecks$1(item.checks);
|
|
47592
|
+
if (failed.some((c) => c.name === "size")) return tooLarge(ctx, project, loop, item);
|
|
47417
47593
|
const builder = item.builderSessionId ? ctx.repo.find(item.builderSessionId)?.session : void 0;
|
|
47418
47594
|
if (item.attempts < loop.tuning.maxFixAttempts && builder !== void 0 && builder.status !== "failed" && builder.status !== "stopped" && item.review?.verdict !== "reject" && builder) {
|
|
47419
47595
|
const at = ctx.now().toISOString();
|
|
@@ -47436,6 +47612,30 @@ async function handleFailure(ctx, project, loop, item) {
|
|
|
47436
47612
|
if (item.pr) await ctx.github.comment(project.id, item.pr.number, `The CodeDen factory's merge checks failed, so this PR is left for a human:\n\n${failed.map((c) => `- **${c.name}**: ${c.detail ?? "failed"}`).join("\n")}`).catch((error) => ctx.log?.(`factory: comment: ${errorMessage$1(error)}`));
|
|
47437
47613
|
return next;
|
|
47438
47614
|
}
|
|
47615
|
+
/**
|
|
47616
|
+
* The size check failed. That isn't a quality failure, so it doesn't count towards pausing
|
|
47617
|
+
* the loop, and no fix round runs. The PM is woken to split the work into smaller issues or,
|
|
47618
|
+
* when it can't be split, to allow it with `factory_allow_large_change`. The PR stays open.
|
|
47619
|
+
*/
|
|
47620
|
+
async function tooLarge(ctx, project, loop, item) {
|
|
47621
|
+
const failed = failedChecks$1(item.checks);
|
|
47622
|
+
const size = failed.find((c) => c.name === "size");
|
|
47623
|
+
const next = failItem(ctx, loop, item, failed.map((c) => `${c.name}: ${c.detail ?? "failed"}`).join("; "), {
|
|
47624
|
+
countsAsFailure: false,
|
|
47625
|
+
notify: false
|
|
47626
|
+
});
|
|
47627
|
+
factoryNotice(ctx, {
|
|
47628
|
+
id: `factory.too-large:${item.id}:${item.pr?.number ?? ""}`,
|
|
47629
|
+
projectId: item.projectId,
|
|
47630
|
+
target: "pm",
|
|
47631
|
+
event: "failed",
|
|
47632
|
+
title: `Too large to merge automatically: ${item.title}`,
|
|
47633
|
+
text: tooLargePrompt(item, size?.detail ?? "over the size limit", failed),
|
|
47634
|
+
itemIds: [item.id]
|
|
47635
|
+
});
|
|
47636
|
+
if (item.pr) await ctx.github.comment(project.id, item.pr.number, `This PR is larger than the CodeDen factory's size limit (${size?.detail ?? "over the limit"}), so it isn't merged automatically yet. The project manager will split the work into smaller issues or allow it as one change.`).catch((error) => ctx.log?.(`factory: comment: ${errorMessage$1(error)}`));
|
|
47637
|
+
return next;
|
|
47638
|
+
}
|
|
47439
47639
|
// ../server/src/services/factory/loop.ts
|
|
47440
47640
|
/** Label on issues the factory created or accepted. */
|
|
47441
47641
|
const FACTORY_LABEL = "codeden-factory";
|
|
@@ -47646,7 +47846,7 @@ function requestTriage(ctx, project) {
|
|
|
47646
47846
|
target: "pm",
|
|
47647
47847
|
event: "triage_needed",
|
|
47648
47848
|
title: `Factory: ${fresh.length} candidate${fresh.length === 1 ? "" : "s"} to triage`,
|
|
47649
|
-
text: triageInstructions(fresh, project.autonomyMode === "dark_factory"),
|
|
47849
|
+
text: triageInstructions(fresh, project.autonomyMode === "dark_factory", sizeLimitText(ctx.store.loopOrNew(project.id).tuning)),
|
|
47650
47850
|
itemIds: fresh.map((i) => i.id)
|
|
47651
47851
|
});
|
|
47652
47852
|
for (const item of fresh) ctx.store.updateItem(item.id, (i) => ({
|
|
@@ -47780,6 +47980,7 @@ async function triage(ctx, projectId, req) {
|
|
|
47780
47980
|
labels: [FACTORY_LABEL]
|
|
47781
47981
|
});
|
|
47782
47982
|
const accepted = issue;
|
|
47983
|
+
if (req.largeChange) recordLargeChange(ctx, item, req.largeChange);
|
|
47783
47984
|
return ctx.store.updateItem(item.id, (i) => ({
|
|
47784
47985
|
...i,
|
|
47785
47986
|
stage: "backlog",
|
|
@@ -47787,6 +47988,7 @@ async function triage(ctx, projectId, req) {
|
|
|
47787
47988
|
acceptanceCriteria: criteria,
|
|
47788
47989
|
issue: accepted,
|
|
47789
47990
|
workKey: `issue:${accepted.number}`,
|
|
47991
|
+
largeChange: req.largeChange ?? i.largeChange ?? null,
|
|
47790
47992
|
note: null
|
|
47791
47993
|
}));
|
|
47792
47994
|
}
|
|
@@ -48213,9 +48415,18 @@ var FactoryService = class {
|
|
|
48213
48415
|
if (session.projectId) this.soon(session.projectId);
|
|
48214
48416
|
return res;
|
|
48215
48417
|
}
|
|
48418
|
+
/** `factory_allow_large_change`: the PM lets an item over the size limit merge. */
|
|
48419
|
+
allowLargeChange(projectId, req) {
|
|
48420
|
+
const item = allowLargeChange(this.ctx, projectId, req);
|
|
48421
|
+
this.soon(projectId);
|
|
48422
|
+
return item;
|
|
48423
|
+
}
|
|
48216
48424
|
/** `factory_record_decision`: a decision an unattended agent made instead of asking. */
|
|
48217
48425
|
recordDecision(session, req) {
|
|
48218
|
-
return recordDecision(this.ctx,
|
|
48426
|
+
return recordDecision(this.ctx, {
|
|
48427
|
+
projectId: session.projectId,
|
|
48428
|
+
sessionId: session.id
|
|
48429
|
+
}, req);
|
|
48219
48430
|
}
|
|
48220
48431
|
/** True when nobody answers this session's questions (dark factory PM or factory agent). */
|
|
48221
48432
|
isUnattended(sessionId) {
|
|
@@ -48371,7 +48582,11 @@ var GithubFactoryPort = class {
|
|
|
48371
48582
|
].includes(c.conclusion)).map((c) => c.name),
|
|
48372
48583
|
additions: pr.additions,
|
|
48373
48584
|
deletions: pr.deletions,
|
|
48374
|
-
files: pr.files.map((f) => f.path)
|
|
48585
|
+
files: pr.files.map((f) => f.path),
|
|
48586
|
+
fileLines: pr.files.map((f) => ({
|
|
48587
|
+
path: f.path,
|
|
48588
|
+
lines: f.additions + f.deletions
|
|
48589
|
+
}))
|
|
48375
48590
|
};
|
|
48376
48591
|
}
|
|
48377
48592
|
async hasScreenshotEvidence(projectId, prNumber, issueNumber) {
|
|
@@ -59283,13 +59498,13 @@ function round(n) {
|
|
|
59283
59498
|
return Math.round(n * 1e6) / 1e6;
|
|
59284
59499
|
}
|
|
59285
59500
|
// ../server/src/services/utility.ts
|
|
59501
|
+
/** The summary-mode fallback: the first sentence or two, within the TL;DR cap. */
|
|
59502
|
+
function shortSpeech(plain) {
|
|
59503
|
+
return speechExcerpt(plain, 220, 2);
|
|
59504
|
+
}
|
|
59286
59505
|
/** Strip code blocks and markdown so text reads naturally aloud (§11.3). */
|
|
59287
|
-
function plainSpeech(text
|
|
59288
|
-
|
|
59289
|
-
if (plain.length <= maxChars) return plain;
|
|
59290
|
-
const cut = plain.slice(0, maxChars);
|
|
59291
|
-
const end = cut.lastIndexOf(". ");
|
|
59292
|
-
return end > maxChars / 2 ? cut.slice(0, end + 1) : `${cut.trimEnd()}...`;
|
|
59506
|
+
function plainSpeech(text) {
|
|
59507
|
+
return text.replace(/```[\s\S]*?```/g, " ").replace(/`([^`]*)`/g, "$1").replace(/!\[[^\]]*\]\([^)]*\)/g, " ").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/^\s*[-*+]\s+/gm, "").replace(/[*_~>|]/g, "").replace(/\s+/g, " ").trim();
|
|
59293
59508
|
}
|
|
59294
59509
|
function runnerOneShot(runner) {
|
|
59295
59510
|
return {
|
|
@@ -59303,6 +59518,18 @@ function runnerOneShot(runner) {
|
|
|
59303
59518
|
}
|
|
59304
59519
|
const PREFERENCE = ["claude", "codex"];
|
|
59305
59520
|
const TIMEOUT_MS = 6e4;
|
|
59521
|
+
/** Read-aloud waits on the summary, so it gets less time than the other jobs. */
|
|
59522
|
+
const SPEECH_TIMEOUT_MS = 3e4;
|
|
59523
|
+
const TLDR_SYSTEM = [
|
|
59524
|
+
"You write the spoken TL;DR of a coding agent's final message. It is read aloud by a text-to-speech voice, so the listener cannot skim.",
|
|
59525
|
+
"Reply with at most two short sentences, under 200 characters in total.",
|
|
59526
|
+
"Say what was done and whether anything needs the user (a decision, a review, a failure). If nothing needs them, don't mention it.",
|
|
59527
|
+
"Plain spoken English only: no markdown, code, file paths, lists, numbers of lines or preamble such as 'Summary:'."
|
|
59528
|
+
].join(" ");
|
|
59529
|
+
/** Server-side warnings for utility jobs, shown whatever the debug setting (stderr). */
|
|
59530
|
+
function defaultWarn(message) {
|
|
59531
|
+
console.warn(`[codeden] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}`);
|
|
59532
|
+
}
|
|
59306
59533
|
/**
|
|
59307
59534
|
* The real `UtilityService`: a one-shot, tool-less call on a small model (§7.4). The provider
|
|
59308
59535
|
* comes from settings (`auto` = Claude, then Codex); when the chosen provider fails, the
|
|
@@ -59313,51 +59540,70 @@ var ProviderUtilityService = class {
|
|
|
59313
59540
|
constructor(deps) {
|
|
59314
59541
|
this.deps = deps;
|
|
59315
59542
|
}
|
|
59543
|
+
/** Set while no provider can run one-shots, so that is reported once, not on every job. */
|
|
59544
|
+
reportedNoProvider = false;
|
|
59545
|
+
warn(message) {
|
|
59546
|
+
(this.deps.warn ?? defaultWarn)(message);
|
|
59547
|
+
}
|
|
59316
59548
|
async sessionTitle(ctx) {
|
|
59317
59549
|
if (!this.deps.settings().utilityModel.jobs.sessionTitles) return null;
|
|
59318
59550
|
const source = ctx.firstMessage ?? ctx.finalMessage;
|
|
59319
59551
|
if (!source) return null;
|
|
59320
|
-
const title = (await this.run("Write a title of 3 to 6 words for a coding session that starts with the request below. Reply with the title only: no quotes, no trailing punctuation.", `Request:\n${clip(source, 4e3)}`))?.split("\n")[0]?.replace(/^["'#\s]+|["'.\s]+$/g, "").slice(0, 80);
|
|
59552
|
+
const title = (await this.run("session title", "Write a title of 3 to 6 words for a coding session that starts with the request below. Reply with the title only: no quotes, no trailing punctuation.", `Request:\n${clip(source, 4e3)}`))?.split("\n")[0]?.replace(/^["'#\s]+|["'.\s]+$/g, "").slice(0, 80);
|
|
59321
59553
|
return title ? title : null;
|
|
59322
59554
|
}
|
|
59323
59555
|
async suggestFollowups(ctx) {
|
|
59324
59556
|
if (!this.deps.settings().utilityModel.jobs.followups || !ctx.finalMessage) return null;
|
|
59325
59557
|
const files = ctx.changedFiles?.length ? `\nFiles changed: ${ctx.changedFiles.join(", ")}` : "";
|
|
59326
|
-
const lines = (await this.run("You suggest what a developer might ask a coding agent next. Reply with 2 to 4 short prompts, one per line, written as the developer would type them. No numbering, no quotes, nothing else.", `The agent's final message:\n${clip(ctx.finalMessage, 6e3)}${files}`) ?? "").split("\n").map((l) => l.replace(/^\s*(?:[-*\d.)]+\s*)?/, "").replace(/^["']|["']$/g, "").trim()).filter((l) => l.length > 0 && l.length <= 300).slice(0, 4);
|
|
59558
|
+
const lines = (await this.run("follow-ups", "You suggest what a developer might ask a coding agent next. Reply with 2 to 4 short prompts, one per line, written as the developer would type them. No numbering, no quotes, nothing else.", `The agent's final message:\n${clip(ctx.finalMessage, 6e3)}${files}`) ?? "").split("\n").map((l) => l.replace(/^\s*(?:[-*\d.)]+\s*)?/, "").replace(/^["']|["']$/g, "").trim()).filter((l) => l.length > 0 && l.length <= 300).slice(0, 4);
|
|
59327
59559
|
return lines.length >= 2 ? lines.map((prompt) => ({ prompt })) : null;
|
|
59328
59560
|
}
|
|
59329
59561
|
async speechSummary(text) {
|
|
59330
|
-
const plain = plainSpeech(text
|
|
59331
|
-
if (
|
|
59332
|
-
text:
|
|
59562
|
+
const plain = plainSpeech(text);
|
|
59563
|
+
if (this.deps.settings().voice.readAloud.content === "full") return {
|
|
59564
|
+
text: speechExcerpt(plain, READ_ALOUD_FULL_MAX_CHARS),
|
|
59333
59565
|
source: "plain"
|
|
59334
59566
|
};
|
|
59335
|
-
|
|
59336
|
-
|
|
59337
|
-
|
|
59567
|
+
if (plain.length <= 220) return {
|
|
59568
|
+
text: plain,
|
|
59569
|
+
source: "plain"
|
|
59570
|
+
};
|
|
59571
|
+
const out = await this.run("read-aloud TL;DR", TLDR_SYSTEM, clip(plain, 12e3), { timeoutMs: SPEECH_TIMEOUT_MS });
|
|
59572
|
+
const tldr = out ? plainSpeech(out) : "";
|
|
59573
|
+
if (tldr) return {
|
|
59574
|
+
text: shortSpeech(tldr),
|
|
59338
59575
|
source: "utility"
|
|
59339
|
-
}
|
|
59340
|
-
|
|
59576
|
+
};
|
|
59577
|
+
this.warn("utility: read-aloud TL;DR unavailable, speaking the message's first sentences");
|
|
59578
|
+
return {
|
|
59579
|
+
text: shortSpeech(plain),
|
|
59341
59580
|
source: "plain"
|
|
59342
59581
|
};
|
|
59343
59582
|
}
|
|
59344
59583
|
/** One-shot on the configured provider, falling back to the next; null on failure. */
|
|
59345
|
-
async run(system, prompt) {
|
|
59584
|
+
async run(job, system, prompt, opts = {}) {
|
|
59346
59585
|
const { utilityModel } = this.deps.settings();
|
|
59347
59586
|
const available = this.deps.oneShot.available();
|
|
59348
59587
|
const chosen = utilityModel.provider === "auto" ? null : utilityModel.provider;
|
|
59349
59588
|
const order = [chosen, ...PREFERENCE].filter((id, i, all) => !!id && available.includes(id) && all.indexOf(id) === i);
|
|
59589
|
+
if (order.length === 0) {
|
|
59590
|
+
if (!this.reportedNoProvider) this.warn(`utility: no provider can run the utility model (runner lists: ${available.join(", ") || "none"})`);
|
|
59591
|
+
this.reportedNoProvider = true;
|
|
59592
|
+
return null;
|
|
59593
|
+
}
|
|
59594
|
+
this.reportedNoProvider = false;
|
|
59350
59595
|
for (const providerId of order) try {
|
|
59351
59596
|
const model = providerId === chosen ? utilityModel.model ?? void 0 : void 0;
|
|
59352
59597
|
const text = await this.deps.oneShot.oneShot(providerId, {
|
|
59353
59598
|
prompt,
|
|
59354
59599
|
system,
|
|
59355
59600
|
model,
|
|
59356
|
-
timeoutMs: TIMEOUT_MS
|
|
59601
|
+
timeoutMs: opts.timeoutMs ?? TIMEOUT_MS
|
|
59357
59602
|
});
|
|
59358
59603
|
if (text.trim()) return text.trim();
|
|
59604
|
+
this.warn(`utility: ${job} on ${providerId} returned nothing`);
|
|
59359
59605
|
} catch (error) {
|
|
59360
|
-
this.
|
|
59606
|
+
this.warn(`utility: ${job} on ${providerId} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
59361
59607
|
}
|
|
59362
59608
|
return null;
|
|
59363
59609
|
}
|
|
@@ -59897,8 +60143,7 @@ function createServices(opts) {
|
|
|
59897
60143
|
const userHome = opts.userHome ?? homedir();
|
|
59898
60144
|
const utility = opts.utility ?? new ProviderUtilityService({
|
|
59899
60145
|
settings: () => opts.settings.get(),
|
|
59900
|
-
oneShot: runnerOneShot(opts.runner)
|
|
59901
|
-
log: opts.log
|
|
60146
|
+
oneShot: runnerOneShot(opts.runner)
|
|
59902
60147
|
});
|
|
59903
60148
|
const projects = new ProjectService({
|
|
59904
60149
|
db: opts.db,
|
|
@@ -61367,4 +61612,4 @@ main().then((code) => process.exit(code), (error) => {
|
|
|
61367
61612
|
});
|
|
61368
61613
|
export {};
|
|
61369
61614
|
|
|
61370
|
-
//# sourceMappingURL=main-
|
|
61615
|
+
//# sourceMappingURL=main-CKLHi4sz.js.map
|