@penclipai/server 2026.426.0 → 2026.505.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/adapters/builtin-adapter-types.d.ts.map +1 -1
- package/dist/adapters/builtin-adapter-types.js +3 -0
- package/dist/adapters/builtin-adapter-types.js.map +1 -1
- package/dist/adapters/index.d.ts +2 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/registry.d.ts +2 -1
- package/dist/adapters/registry.d.ts.map +1 -1
- package/dist/adapters/registry.js +76 -6
- package/dist/adapters/registry.js.map +1 -1
- package/dist/adapters/types.d.ts +1 -1
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/utils.d.ts.map +1 -1
- package/dist/adapters/utils.js +2 -1
- package/dist/adapters/utils.js.map +1 -1
- package/dist/attachment-types.d.ts +1 -16
- package/dist/attachment-types.d.ts.map +1 -1
- package/dist/attachment-types.js +7 -0
- package/dist/attachment-types.js.map +1 -1
- package/dist/auth/better-auth.d.ts +3 -1
- package/dist/auth/better-auth.d.ts.map +1 -1
- package/dist/auth/better-auth.js +8 -2
- package/dist/auth/better-auth.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -13
- package/dist/index.js.map +1 -1
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +143 -2
- package/dist/middleware/auth.js.map +1 -1
- package/dist/onboarding-assets/ceo/AGENTS.md +1 -1
- package/dist/onboarding-assets/ceo/HEARTBEAT.md +5 -5
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +30 -12
- package/dist/redaction.js.map +1 -1
- package/dist/routes/access.d.ts.map +1 -1
- package/dist/routes/access.js +10 -0
- package/dist/routes/access.js.map +1 -1
- package/dist/routes/activity.d.ts.map +1 -1
- package/dist/routes/activity.js +4 -2
- package/dist/routes/activity.js.map +1 -1
- package/dist/routes/adapters.d.ts.map +1 -1
- package/dist/routes/adapters.js +1 -0
- package/dist/routes/adapters.js.map +1 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +317 -56
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/costs.d.ts.map +1 -1
- package/dist/routes/costs.js +21 -2
- package/dist/routes/costs.js.map +1 -1
- package/dist/routes/instance-settings.d.ts.map +1 -1
- package/dist/routes/instance-settings.js +37 -2
- package/dist/routes/instance-settings.js.map +1 -1
- package/dist/routes/issue-tree-control.d.ts.map +1 -1
- package/dist/routes/issue-tree-control.js +3 -1
- package/dist/routes/issue-tree-control.js.map +1 -1
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +257 -32
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +10 -3
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/routines.d.ts.map +1 -1
- package/dist/routes/routines.js +6 -1
- package/dist/routes/routines.js.map +1 -1
- package/dist/routes/workspace-command-authz.d.ts +1 -1
- package/dist/routes/workspace-command-authz.d.ts.map +1 -1
- package/dist/routes/workspace-command-authz.js +2 -2
- package/dist/routes/workspace-command-authz.js.map +1 -1
- package/dist/runtime-api.d.ts +4 -0
- package/dist/runtime-api.d.ts.map +1 -1
- package/dist/runtime-api.js +38 -10
- package/dist/runtime-api.js.map +1 -1
- package/dist/services/companies.d.ts +6 -0
- package/dist/services/companies.d.ts.map +1 -1
- package/dist/services/companies.js +1 -0
- package/dist/services/companies.js.map +1 -1
- package/dist/services/company-portability.d.ts.map +1 -1
- package/dist/services/company-portability.js +16 -15
- package/dist/services/company-portability.js.map +1 -1
- package/dist/services/costs.d.ts +9 -0
- package/dist/services/costs.d.ts.map +1 -1
- package/dist/services/costs.js +45 -1
- package/dist/services/costs.js.map +1 -1
- package/dist/services/environment-execution-target.d.ts.map +1 -1
- package/dist/services/environment-execution-target.js +7 -13
- package/dist/services/environment-execution-target.js.map +1 -1
- package/dist/services/environment-run-orchestrator.d.ts.map +1 -1
- package/dist/services/environment-run-orchestrator.js +56 -0
- package/dist/services/environment-run-orchestrator.js.map +1 -1
- package/dist/services/environment-runtime.d.ts +2 -0
- package/dist/services/environment-runtime.d.ts.map +1 -1
- package/dist/services/environment-runtime.js +80 -39
- package/dist/services/environment-runtime.js.map +1 -1
- package/dist/services/heartbeat-stop-metadata.d.ts +2 -1
- package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -1
- package/dist/services/heartbeat-stop-metadata.js +10 -1
- package/dist/services/heartbeat-stop-metadata.js.map +1 -1
- package/dist/services/heartbeat-stop-metadata.test.js +24 -0
- package/dist/services/heartbeat-stop-metadata.test.js.map +1 -1
- package/dist/services/heartbeat.d.ts +156 -5
- package/dist/services/heartbeat.d.ts.map +1 -1
- package/dist/services/heartbeat.js +1384 -112
- package/dist/services/heartbeat.js.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/instance-settings.d.ts.map +1 -1
- package/dist/services/instance-settings.js +4 -1
- package/dist/services/instance-settings.js.map +1 -1
- package/dist/services/issue-execution-policy.d.ts +56 -1
- package/dist/services/issue-execution-policy.d.ts.map +1 -1
- package/dist/services/issue-execution-policy.js +400 -2
- package/dist/services/issue-execution-policy.js.map +1 -1
- package/dist/services/issue-thread-interactions.d.ts +5 -1
- package/dist/services/issue-thread-interactions.d.ts.map +1 -1
- package/dist/services/issue-thread-interactions.js +44 -1
- package/dist/services/issue-thread-interactions.js.map +1 -1
- package/dist/services/issue-tree-control.d.ts +1 -0
- package/dist/services/issue-tree-control.d.ts.map +1 -1
- package/dist/services/issue-tree-control.js +84 -4
- package/dist/services/issue-tree-control.js.map +1 -1
- package/dist/services/issues.d.ts +10 -1
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.js +452 -48
- package/dist/services/issues.js.map +1 -1
- package/dist/services/plugin-environment-driver.d.ts +4 -0
- package/dist/services/plugin-environment-driver.d.ts.map +1 -1
- package/dist/services/plugin-environment-driver.js +18 -1
- package/dist/services/plugin-environment-driver.js.map +1 -1
- package/dist/services/productivity-review.d.ts +83 -0
- package/dist/services/productivity-review.d.ts.map +1 -0
- package/dist/services/productivity-review.js +650 -0
- package/dist/services/productivity-review.js.map +1 -0
- package/dist/services/recovery/index.d.ts +1 -1
- package/dist/services/recovery/index.d.ts.map +1 -1
- package/dist/services/recovery/index.js +1 -1
- package/dist/services/recovery/index.js.map +1 -1
- package/dist/services/recovery/issue-graph-liveness.d.ts +13 -1
- package/dist/services/recovery/issue-graph-liveness.d.ts.map +1 -1
- package/dist/services/recovery/issue-graph-liveness.js +212 -92
- package/dist/services/recovery/issue-graph-liveness.js.map +1 -1
- package/dist/services/recovery/origins.d.ts +2 -0
- package/dist/services/recovery/origins.d.ts.map +1 -1
- package/dist/services/recovery/origins.js +4 -0
- package/dist/services/recovery/origins.js.map +1 -1
- package/dist/services/recovery/run-liveness-continuations.d.ts.map +1 -1
- package/dist/services/recovery/run-liveness-continuations.js.map +1 -1
- package/dist/services/recovery/service.d.ts +20 -2
- package/dist/services/recovery/service.d.ts.map +1 -1
- package/dist/services/recovery/service.js +405 -63
- package/dist/services/recovery/service.js.map +1 -1
- package/dist/services/routines.d.ts +5 -2
- package/dist/services/routines.d.ts.map +1 -1
- package/dist/services/routines.js +47 -3
- package/dist/services/routines.js.map +1 -1
- package/dist/worktree-config.d.ts.map +1 -1
- package/dist/worktree-config.js +2 -5
- package/dist/worktree-config.js.map +1 -1
- package/package.json +16 -15
- package/skills/diagnose-why-work-stopped/SKILL.md +161 -0
- package/skills/paperclip/SKILL.md +37 -26
- package/skills/paperclip/references/api-reference.md +6 -2
- package/skills/paperclip-converting-plans-to-tasks/SKILL.md +42 -0
- package/skills/paperclip-create-agent/SKILL.md +3 -2
- package/skills/paperclip-create-agent/references/agent-instruction-templates.md +1 -1
- package/skills/paperclip-create-agent/references/api-reference.md +7 -2
- package/skills/paperclip-create-agent/references/baseline-role-guide.md +1 -1
- package/skills/paperclip-create-agent/references/draft-review-checklist.md +2 -2
- package/skills/paperclip-dev/SKILL.md +267 -0
- package/skills/terminal-bench-loop/SKILL.md +236 -0
- package/ui-dist/assets/{_basePickBy-BRqa7PJ5.js → _basePickBy-BS0Fg_DB.js} +1 -1
- package/ui-dist/assets/{_baseUniq-DhE2yrXC.js → _baseUniq-Dtnt_4SE.js} +1 -1
- package/ui-dist/assets/{arc-7qnikTQ3.js → arc-BCoOPxh5.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-VXUJARFQ-CH0wVUOM.js → architectureDiagram-VXUJARFQ-C6eX2QUo.js} +1 -1
- package/ui-dist/assets/{blockDiagram-VD42YOAC-CeeRyJQX.js → blockDiagram-VD42YOAC-aUueUD4B.js} +1 -1
- package/ui-dist/assets/browser-ponyfill-BlAfsWm_.js +2 -0
- package/ui-dist/assets/{c4Diagram-YG6GDRKO-C_cV0CGo.js → c4Diagram-YG6GDRKO-CfPWRlOF.js} +1 -1
- package/ui-dist/assets/channel-ChNSCFJf.js +1 -0
- package/ui-dist/assets/{chunk-4BX2VUAB-DQ6pxPVT.js → chunk-4BX2VUAB-BTD1apA4.js} +1 -1
- package/ui-dist/assets/{chunk-55IACEB6-L8pS0IoX.js → chunk-55IACEB6-BXXF_ClN.js} +1 -1
- package/ui-dist/assets/{chunk-B4BG7PRW-BZKGE88E.js → chunk-B4BG7PRW-hAZeWGP8.js} +1 -1
- package/ui-dist/assets/{chunk-DI55MBZ5-CefSoZ_K.js → chunk-DI55MBZ5-cOH3UoEl.js} +1 -1
- package/ui-dist/assets/{chunk-FMBD7UC4-Bc3qTTHB.js → chunk-FMBD7UC4-Cu2yZOcl.js} +1 -1
- package/ui-dist/assets/{chunk-QN33PNHL-CjWBr5bI.js → chunk-QN33PNHL-0DNN5aRU.js} +1 -1
- package/ui-dist/assets/{chunk-QZHKN3VN-C0JUdmmz.js → chunk-QZHKN3VN-B9_bhK2n.js} +1 -1
- package/ui-dist/assets/{chunk-TZMSLE5B-D4d4I82z.js → chunk-TZMSLE5B-Cr5xwxio.js} +1 -1
- package/ui-dist/assets/classDiagram-2ON5EDUG-4aK1QZU3.js +1 -0
- package/ui-dist/assets/classDiagram-v2-WZHVMYZB-4aK1QZU3.js +1 -0
- package/ui-dist/assets/clone-C8lk5Qbc.js +1 -0
- package/ui-dist/assets/{cose-bilkent-S5V4N54A-B09h9XGZ.js → cose-bilkent-S5V4N54A-6_Dw6gpQ.js} +1 -1
- package/ui-dist/assets/{dagre-6UL2VRFP-CA02PXuX.js → dagre-6UL2VRFP-CFBhlh5H.js} +1 -1
- package/ui-dist/assets/{diagram-PSM6KHXK-DaT9cnrY.js → diagram-PSM6KHXK-C88ftcah.js} +1 -1
- package/ui-dist/assets/{diagram-QEK2KX5R-Drwc3gBw.js → diagram-QEK2KX5R-9EUupcuH.js} +1 -1
- package/ui-dist/assets/{diagram-S2PKOQOG-CpsGCaT6.js → diagram-S2PKOQOG-Dsml0wWh.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q2GNP2WA-CVkBh9TY.js → erDiagram-Q2GNP2WA-sM-XdfHS.js} +1 -1
- package/ui-dist/assets/{flowDiagram-NV44I4VS-De9sXvPR.js → flowDiagram-NV44I4VS-qll7oaoW.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-JELNMOA3-CSFa0gXS.js → ganttDiagram-JELNMOA3-VWnJMcjC.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-V2S2FVAM-DEJaChxa.js → gitGraphDiagram-V2S2FVAM-DFnocrfl.js} +1 -1
- package/ui-dist/assets/{graph-D2R4DCtu.js → graph-nq3Qye4Z.js} +1 -1
- package/ui-dist/assets/{index-DEG-9CFs.js → index-3Owzaheh.js} +1 -1
- package/ui-dist/assets/{index-DHnKx9xX.js → index-B2A-a635.js} +1 -1
- package/ui-dist/assets/{index-C1I0SGDm.js → index-BGFrRiqa.js} +1 -1
- package/ui-dist/assets/{index-B44EtLRv.js → index-BVC5UhRK.js} +1 -1
- package/ui-dist/assets/{index-C_dAXwxT.js → index-BrP1U_Hy.js} +1 -1
- package/ui-dist/assets/{index-flZjKn_n.js → index-CXXHGqM8.js} +1 -1
- package/ui-dist/assets/{index-ssM_UKPW.js → index-CgyPAauR.js} +1 -1
- package/ui-dist/assets/{index-Ct1AraKR.js → index-CksQ4Ytv.js} +1 -1
- package/ui-dist/assets/{index-DQ6I_vpd.js → index-CrNzj2vZ.js} +1 -1
- package/ui-dist/assets/{index-DzZID5RY.js → index-CxbZBH3M.js} +1 -1
- package/ui-dist/assets/{index-Cn6_RRY5.js → index-D-dSSrf-.js} +1 -1
- package/ui-dist/assets/{index-CVa2OHgx.js → index-D6uZ_7Vh.js} +1 -1
- package/ui-dist/assets/{index-BzjWQd50.js → index-D7JGmxas.js} +1 -1
- package/ui-dist/assets/{index-CnT1_9UF.js → index-DDqO9GAq.js} +1 -1
- package/ui-dist/assets/index-DEUtmlPm.js +513 -0
- package/ui-dist/assets/{index-D2fEhyQg.js → index-DF5RDSoK.js} +1 -1
- package/ui-dist/assets/{index-CZGNe8K3.js → index-DfI92epU.js} +1 -1
- package/ui-dist/assets/{index-ByamXtyB.js → index-Dukb9MDQ.js} +1 -1
- package/ui-dist/assets/index-HP73_6Vr.css +1 -0
- package/ui-dist/assets/{index-BJS4rvUh.js → index-NXDTW2n4.js} +1 -1
- package/ui-dist/assets/{index-Bad5Hy7e.js → index-SxPPG9ig.js} +1 -1
- package/ui-dist/assets/{index-CC51mhhA.js → index-lC4Yz3Gw.js} +1 -1
- package/ui-dist/assets/{index-BFzkl36p.js → index-q2RXGI2V.js} +1 -1
- package/ui-dist/assets/{index-40icqWwg.js → index-qjfdrS96.js} +1 -1
- package/ui-dist/assets/{infoDiagram-HS3SLOUP-CJcjzWkM.js → infoDiagram-HS3SLOUP-CTrK5xoS.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-XKPGCS4Q-ByITI00s.js → journeyDiagram-XKPGCS4Q-YFC7FykG.js} +1 -1
- package/ui-dist/assets/{kanban-definition-3W4ZIXB7-DvEjKke-.js → kanban-definition-3W4ZIXB7-B3dlyva0.js} +1 -1
- package/ui-dist/assets/{layout-CZcd66hi.js → layout-DefunPTK.js} +1 -1
- package/ui-dist/assets/{linear-jTUy3iHu.js → linear-CIPvzeMv.js} +1 -1
- package/ui-dist/assets/{mermaid.core-DECSZPbJ.js → mermaid.core-zKYhmnnR.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-VGOIOE7T-Twtu17_c.js → mindmap-definition-VGOIOE7T-BlU-ebRa.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ADFJNKIX-DlbgZ010.js → pieDiagram-ADFJNKIX-Ceto4LXH.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-AYHSOK5B-CMAa3qAT.js → quadrantDiagram-AYHSOK5B-C6M6hkuE.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-UZGBJVZJ-CXRTfJOe.js → requirementDiagram-UZGBJVZJ-B-bcG938.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-TZEHDZUN-DeyO4fer.js → sankeyDiagram-TZEHDZUN-CIqty6Qi.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-WL72ISMW-Ch8wlJIL.js → sequenceDiagram-WL72ISMW-CIt2R5tk.js} +1 -1
- package/ui-dist/assets/{stateDiagram-FKZM4ZOC-BgL_AAl9.js → stateDiagram-FKZM4ZOC-BC1RFlfg.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-Iy6tYSSw.js +1 -0
- package/ui-dist/assets/{timeline-definition-IT6M3QCI-D1QWd7TQ.js → timeline-definition-IT6M3QCI-DZqvoU94.js} +1 -1
- package/ui-dist/assets/{treemap-GDKQZRPO-B5RkmUv8.js → treemap-GDKQZRPO-CSeKauwA.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-PRI3JC2R-WtDhjZfk.js → xychartDiagram-PRI3JC2R-Ut3mCiEd.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/locales/en/common.json +137 -1
- package/ui-dist/locales/zh-CN/common.json +111 -1
- package/ui-dist/assets/browser-ponyfill-Ct3hGqsr.js +0 -2
- package/ui-dist/assets/channel-pHFjGZL-.js +0 -1
- package/ui-dist/assets/classDiagram-2ON5EDUG-X4ZksqXl.js +0 -1
- package/ui-dist/assets/classDiagram-v2-WZHVMYZB-X4ZksqXl.js +0 -1
- package/ui-dist/assets/clone-DZzimpfG.js +0 -1
- package/ui-dist/assets/index-C1oE3J7o.css +0 -1
- package/ui-dist/assets/index-fSIlEIHr.js +0 -510
- package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-gnLzrhSv.js +0 -1
package/dist/routes/agents.js
CHANGED
|
@@ -3,7 +3,7 @@ import { generateKeyPairSync, randomUUID } from "node:crypto";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { agents as agentsTable, companies, heartbeatRuns, issues as issuesTable } from "@penclipai/db";
|
|
5
5
|
import { and, desc, eq, inArray, not, sql } from "drizzle-orm";
|
|
6
|
-
import { agentSkillSyncSchema, agentMineInboxQuerySchema, AGENT_DEFAULT_MAX_CONCURRENT_RUNS, createAgentKeySchema, createAgentHireSchema, createAgentSchema, deriveAgentUrlKey, isUuidLike, resetAgentSessionSchema, testAdapterEnvironmentSchema, upsertAgentInstructionsFileSchema, updateAgentInstructionsBundleSchema, updateAgentPermissionsSchema, updateAgentInstructionsPathSchema, wakeAgentSchema, updateAgentSchema, supportedEnvironmentDriversForAdapter, } from "@penclipai/shared";
|
|
6
|
+
import { agentSkillSyncSchema, agentMineInboxQuerySchema, AGENT_DEFAULT_MAX_CONCURRENT_RUNS, createAgentKeySchema, createAgentHireSchema, createAgentSchema, deriveAgentUrlKey, isUuidLike, normalizeIssueIdentifier, resetAgentSessionSchema, testAdapterEnvironmentSchema, upsertAgentInstructionsFileSchema, updateAgentInstructionsBundleSchema, updateAgentPermissionsSchema, updateAgentInstructionsPathSchema, wakeAgentSchema, updateAgentSchema, supportedEnvironmentDriversForAdapter, } from "@penclipai/shared";
|
|
7
7
|
import { readPaperclipSkillSyncPreference, writePaperclipSkillSyncPreference, } from "@penclipai/adapter-utils/server-utils";
|
|
8
8
|
import { trackAgentCreated } from "@penclipai/shared/telemetry";
|
|
9
9
|
import { validate } from "../middleware/validate.js";
|
|
@@ -12,8 +12,9 @@ import { conflict, forbidden, notFound, unprocessable } from "../errors.js";
|
|
|
12
12
|
import { assertBoard, assertCompanyAccess, assertInstanceAdmin, getActorInfo } from "./authz.js";
|
|
13
13
|
import { assertNoAgentHostWorkspaceCommandMutation, collectAgentAdapterWorkspaceCommandPaths, } from "./workspace-command-authz.js";
|
|
14
14
|
import { environmentService } from "../services/environments.js";
|
|
15
|
+
import { resolveEnvironmentExecutionTarget } from "../services/environment-execution-target.js";
|
|
15
16
|
import { secretService } from "../services/secrets.js";
|
|
16
|
-
import { detectAdapterModel, findActiveServerAdapter, findServerAdapter, listAdapterModels, refreshAdapterModels, requireServerAdapter, } from "../adapters/index.js";
|
|
17
|
+
import { detectAdapterModel, findActiveServerAdapter, findServerAdapter, listAdapterModels, listAdapterModelProfiles, refreshAdapterModels, requireServerAdapter, } from "../adapters/index.js";
|
|
17
18
|
import { redactEventPayload } from "../redaction.js";
|
|
18
19
|
import { redactCurrentUserValue } from "../log-redaction.js";
|
|
19
20
|
import { renderOrgChartSvg, renderOrgChartPng, ORG_CHART_STYLES } from "./org-chart-svg.js";
|
|
@@ -21,12 +22,14 @@ import { instanceSettingsService } from "../services/instance-settings.js";
|
|
|
21
22
|
import { resolveExplicitRequestUiLocale } from "../ui-locale.js";
|
|
22
23
|
import { runClaudeLogin } from "@penclipai/adapter-claude-local/server";
|
|
23
24
|
import { DEFAULT_CODEBUDDY_LOCAL_MODEL, } from "@penclipai/adapter-codebuddy-local";
|
|
25
|
+
import { DEFAULT_ACPX_LOCAL_AGENT, DEFAULT_ACPX_LOCAL_MODE, DEFAULT_ACPX_LOCAL_NON_INTERACTIVE_PERMISSIONS, DEFAULT_ACPX_LOCAL_PERMISSION_MODE, } from "@penclipai/adapter-acpx-local";
|
|
24
26
|
import { DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX, DEFAULT_CODEX_LOCAL_MODEL, } from "@penclipai/adapter-codex-local";
|
|
25
27
|
import { DEFAULT_CURSOR_LOCAL_MODEL } from "@penclipai/adapter-cursor-local";
|
|
26
28
|
import { DEFAULT_GEMINI_LOCAL_MODEL } from "@penclipai/adapter-gemini-local";
|
|
29
|
+
import { DEFAULT_OPENCODE_LOCAL_MODEL } from "@penclipai/adapter-opencode-local";
|
|
27
30
|
import { DEFAULT_QWEN_LOCAL_MODEL } from "@penclipai/adapter-qwen-local";
|
|
28
31
|
import { ensureCodeBuddyModelConfiguredAndAvailable } from "@penclipai/adapter-codebuddy-local/server";
|
|
29
|
-
import {
|
|
32
|
+
import { requireOpenCodeModelId } from "@penclipai/adapter-opencode-local/server";
|
|
30
33
|
import { loadDefaultAgentInstructionsBundle, resolveDefaultAgentInstructionsBundleRole, } from "../services/default-agent-instructions.js";
|
|
31
34
|
import { getTelemetryClient } from "../telemetry.js";
|
|
32
35
|
import { assertEnvironmentSelectionForCompany } from "./environment-selection.js";
|
|
@@ -43,12 +46,15 @@ function readLiveRunsQueryInt(value, max, fallback = 0) {
|
|
|
43
46
|
const parsed = Number(value);
|
|
44
47
|
if (!Number.isFinite(parsed))
|
|
45
48
|
return fallback;
|
|
46
|
-
|
|
49
|
+
if (parsed <= 0)
|
|
50
|
+
return fallback;
|
|
51
|
+
return Math.min(max, Math.trunc(parsed));
|
|
47
52
|
}
|
|
48
53
|
export function agentRoutes(db, options = {}) {
|
|
49
54
|
// Legacy hardcoded maps — used as fallback when adapter module does not
|
|
50
55
|
// declare capability flags explicitly.
|
|
51
56
|
const DEFAULT_INSTRUCTIONS_PATH_KEYS = {
|
|
57
|
+
acpx_local: "instructionsFilePath",
|
|
52
58
|
claude_local: "instructionsFilePath",
|
|
53
59
|
codex_local: "instructionsFilePath",
|
|
54
60
|
codebuddy_local: "instructionsFilePath",
|
|
@@ -111,6 +117,96 @@ export function agentRoutes(db, options = {}) {
|
|
|
111
117
|
allowedDrivers: allowedEnvironmentDriversForAgent(adapterType),
|
|
112
118
|
});
|
|
113
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Resolve the execution target the adapter should run its test probes against.
|
|
122
|
+
*
|
|
123
|
+
* - No environmentId / local environment → returns a local target so the
|
|
124
|
+
* adapter probes the Paperclip host (legacy behavior).
|
|
125
|
+
* - SSH environment → builds an SSH execution target from the environment
|
|
126
|
+
* config so the adapter probes the remote box. No lease is required:
|
|
127
|
+
* the SSH spec is fully derived from the saved environment config.
|
|
128
|
+
* - Sandbox / plugin environments → currently fall back to local probing
|
|
129
|
+
* with a warning check, since lifting a temporary sandbox lease for an
|
|
130
|
+
* ad-hoc test invocation is out of scope for this iteration.
|
|
131
|
+
*/
|
|
132
|
+
async function resolveAdapterTestExecutionContext(input) {
|
|
133
|
+
if (!input.environmentId) {
|
|
134
|
+
return { executionTarget: null, environmentName: null, fallbackChecks: [] };
|
|
135
|
+
}
|
|
136
|
+
const environment = await environmentsSvc.getById(input.environmentId);
|
|
137
|
+
if (!environment || environment.companyId !== input.companyId) {
|
|
138
|
+
return {
|
|
139
|
+
executionTarget: null,
|
|
140
|
+
environmentName: null,
|
|
141
|
+
fallbackChecks: [
|
|
142
|
+
{
|
|
143
|
+
code: "environment_not_found",
|
|
144
|
+
level: "warn",
|
|
145
|
+
message: "Selected environment was not found. Falling back to a local probe.",
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (environment.driver === "local") {
|
|
151
|
+
return { executionTarget: null, environmentName: environment.name, fallbackChecks: [] };
|
|
152
|
+
}
|
|
153
|
+
if (environment.driver === "ssh") {
|
|
154
|
+
try {
|
|
155
|
+
const target = await resolveEnvironmentExecutionTarget({
|
|
156
|
+
db,
|
|
157
|
+
companyId: input.companyId,
|
|
158
|
+
adapterType: input.adapterType,
|
|
159
|
+
environment: {
|
|
160
|
+
id: environment.id,
|
|
161
|
+
driver: environment.driver,
|
|
162
|
+
config: environment.config ?? null,
|
|
163
|
+
},
|
|
164
|
+
leaseMetadata: null,
|
|
165
|
+
});
|
|
166
|
+
if (target) {
|
|
167
|
+
return { executionTarget: target, environmentName: environment.name, fallbackChecks: [] };
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
executionTarget: null,
|
|
171
|
+
environmentName: environment.name,
|
|
172
|
+
fallbackChecks: [
|
|
173
|
+
{
|
|
174
|
+
code: "environment_target_unavailable",
|
|
175
|
+
level: "warn",
|
|
176
|
+
message: `Could not resolve an execution target for environment "${environment.name}". Falling back to a local probe.`,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
return {
|
|
183
|
+
executionTarget: null,
|
|
184
|
+
environmentName: environment.name,
|
|
185
|
+
fallbackChecks: [
|
|
186
|
+
{
|
|
187
|
+
code: "environment_target_failed",
|
|
188
|
+
level: "warn",
|
|
189
|
+
message: `Could not connect to environment "${environment.name}" to run the test. Falling back to a local probe.`,
|
|
190
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// sandbox / plugin / other drivers: not yet supported for ad-hoc adapter tests.
|
|
197
|
+
return {
|
|
198
|
+
executionTarget: null,
|
|
199
|
+
environmentName: environment.name,
|
|
200
|
+
fallbackChecks: [
|
|
201
|
+
{
|
|
202
|
+
code: "environment_driver_not_supported_for_test",
|
|
203
|
+
level: "warn",
|
|
204
|
+
message: `Adapter testing inside ${environment.driver} environments is not yet supported. Falling back to a local probe; results may not reflect runs in "${environment.name}".`,
|
|
205
|
+
hint: "Run a real heartbeat in the environment to verify end-to-end behavior.",
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
114
210
|
async function getCurrentUserRedactionOptions() {
|
|
115
211
|
return {
|
|
116
212
|
enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
|
|
@@ -484,6 +580,66 @@ export function agentRoutes(db, options = {}) {
|
|
|
484
580
|
normalizedRuntimeConfig.heartbeat = heartbeat;
|
|
485
581
|
return normalizedRuntimeConfig;
|
|
486
582
|
}
|
|
583
|
+
function listRuntimeModelProfileAdapterConfigs(runtimeConfig) {
|
|
584
|
+
const runtimeRecord = asRecord(runtimeConfig);
|
|
585
|
+
const modelProfiles = asRecord(runtimeRecord?.modelProfiles);
|
|
586
|
+
if (!modelProfiles)
|
|
587
|
+
return [];
|
|
588
|
+
const entries = [];
|
|
589
|
+
for (const [profileKey, rawProfile] of Object.entries(modelProfiles)) {
|
|
590
|
+
const profile = asRecord(rawProfile);
|
|
591
|
+
const adapterConfig = asRecord(profile?.adapterConfig);
|
|
592
|
+
if (!profile || !adapterConfig)
|
|
593
|
+
continue;
|
|
594
|
+
entries.push({
|
|
595
|
+
profileKey,
|
|
596
|
+
profile,
|
|
597
|
+
adapterConfig,
|
|
598
|
+
path: `runtimeConfig.modelProfiles.${profileKey}.adapterConfig`,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
return entries;
|
|
602
|
+
}
|
|
603
|
+
function assertNoAgentRuntimeConfigAdapterConfigMutation(req, runtimeConfig) {
|
|
604
|
+
for (const entry of listRuntimeModelProfileAdapterConfigs(runtimeConfig)) {
|
|
605
|
+
assertNoAgentAdapterConfigMutation(req, entry.adapterConfig, entry.path);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
async function normalizeMediatedAdapterConfigForPersistence(input) {
|
|
609
|
+
const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(input.companyId, input.adapterConfig, { strictMode: strictSecretsMode });
|
|
610
|
+
await assertAdapterConfigConstraints(input.companyId, input.adapterType, input.constraintAdapterConfig
|
|
611
|
+
? { ...input.constraintAdapterConfig, ...normalizedAdapterConfig }
|
|
612
|
+
: normalizedAdapterConfig);
|
|
613
|
+
return normalizedAdapterConfig;
|
|
614
|
+
}
|
|
615
|
+
async function normalizeRuntimeConfigAdapterConfigsForPersistence(companyId, adapterType, runtimeConfig, baseAdapterConfig) {
|
|
616
|
+
const entries = listRuntimeModelProfileAdapterConfigs(runtimeConfig);
|
|
617
|
+
if (entries.length === 0)
|
|
618
|
+
return runtimeConfig;
|
|
619
|
+
const adapterModelProfiles = await listAdapterModelProfiles(adapterType);
|
|
620
|
+
const normalizedRuntimeConfig = { ...runtimeConfig };
|
|
621
|
+
const modelProfiles = asRecord(runtimeConfig.modelProfiles) ?? {};
|
|
622
|
+
const normalizedModelProfiles = { ...modelProfiles };
|
|
623
|
+
normalizedRuntimeConfig.modelProfiles = normalizedModelProfiles;
|
|
624
|
+
for (const entry of entries) {
|
|
625
|
+
const adapterProfile = adapterModelProfiles.find((profile) => profile.key === entry.profileKey);
|
|
626
|
+
const adapterDefaultConfig = asRecord(adapterProfile?.adapterConfig) ?? {};
|
|
627
|
+
const normalizedAdapterConfig = await normalizeMediatedAdapterConfigForPersistence({
|
|
628
|
+
companyId,
|
|
629
|
+
adapterType,
|
|
630
|
+
adapterConfig: entry.adapterConfig,
|
|
631
|
+
constraintAdapterConfig: {
|
|
632
|
+
...baseAdapterConfig,
|
|
633
|
+
...adapterDefaultConfig,
|
|
634
|
+
},
|
|
635
|
+
});
|
|
636
|
+
normalizedModelProfiles[entry.profileKey] = {
|
|
637
|
+
...entry.profile,
|
|
638
|
+
adapterConfig: normalizedAdapterConfig,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
return normalizedRuntimeConfig;
|
|
642
|
+
}
|
|
487
643
|
function generateEd25519PrivateKeyPem() {
|
|
488
644
|
const { privateKey } = generateKeyPairSync("ed25519");
|
|
489
645
|
return privateKey.export({ type: "pkcs8", format: "pem" }).toString();
|
|
@@ -500,6 +656,21 @@ export function agentRoutes(db, options = {}) {
|
|
|
500
656
|
}
|
|
501
657
|
function applyCreateDefaultsByAdapterType(adapterType, adapterConfig) {
|
|
502
658
|
const next = { ...adapterConfig };
|
|
659
|
+
if (adapterType === "acpx_local") {
|
|
660
|
+
if (!asNonEmptyString(next.agent)) {
|
|
661
|
+
next.agent = DEFAULT_ACPX_LOCAL_AGENT;
|
|
662
|
+
}
|
|
663
|
+
if (!asNonEmptyString(next.mode)) {
|
|
664
|
+
next.mode = DEFAULT_ACPX_LOCAL_MODE;
|
|
665
|
+
}
|
|
666
|
+
if (!asNonEmptyString(next.permissionMode)) {
|
|
667
|
+
next.permissionMode = DEFAULT_ACPX_LOCAL_PERMISSION_MODE;
|
|
668
|
+
}
|
|
669
|
+
if (!asNonEmptyString(next.nonInteractivePermissions)) {
|
|
670
|
+
next.nonInteractivePermissions = DEFAULT_ACPX_LOCAL_NON_INTERACTIVE_PERMISSIONS;
|
|
671
|
+
}
|
|
672
|
+
return ensureGatewayDeviceKey(adapterType, next);
|
|
673
|
+
}
|
|
503
674
|
if (adapterType === "codex_local") {
|
|
504
675
|
if (!asNonEmptyString(next.model)) {
|
|
505
676
|
next.model = DEFAULT_CODEX_LOCAL_MODEL;
|
|
@@ -523,7 +694,10 @@ export function agentRoutes(db, options = {}) {
|
|
|
523
694
|
next.model = DEFAULT_QWEN_LOCAL_MODEL;
|
|
524
695
|
return ensureGatewayDeviceKey(adapterType, next);
|
|
525
696
|
}
|
|
526
|
-
|
|
697
|
+
if (adapterType === "opencode_local" && !asNonEmptyString(next.model)) {
|
|
698
|
+
next.model = DEFAULT_OPENCODE_LOCAL_MODEL;
|
|
699
|
+
return ensureGatewayDeviceKey(adapterType, next);
|
|
700
|
+
}
|
|
527
701
|
if (adapterType === "cursor" && !asNonEmptyString(next.model)) {
|
|
528
702
|
next.model = DEFAULT_CURSOR_LOCAL_MODEL;
|
|
529
703
|
}
|
|
@@ -549,15 +723,8 @@ export function agentRoutes(db, options = {}) {
|
|
|
549
723
|
}
|
|
550
724
|
if (adapterType !== "opencode_local")
|
|
551
725
|
return;
|
|
552
|
-
const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(companyId, adapterConfig);
|
|
553
|
-
const runtimeEnv = asRecord(runtimeConfig.env) ?? {};
|
|
554
726
|
try {
|
|
555
|
-
|
|
556
|
-
model: runtimeConfig.model,
|
|
557
|
-
command: runtimeConfig.command,
|
|
558
|
-
cwd: runtimeConfig.cwd,
|
|
559
|
-
env: runtimeEnv,
|
|
560
|
-
});
|
|
727
|
+
requireOpenCodeModelId(adapterConfig.model);
|
|
561
728
|
}
|
|
562
729
|
catch (err) {
|
|
563
730
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -577,7 +744,7 @@ export function agentRoutes(db, options = {}) {
|
|
|
577
744
|
}
|
|
578
745
|
return path.resolve(cwd, trimmed);
|
|
579
746
|
}
|
|
580
|
-
async function materializeDefaultInstructionsBundleForNewAgent(agent) {
|
|
747
|
+
async function materializeDefaultInstructionsBundleForNewAgent(agent, input) {
|
|
581
748
|
if (!adapterSupportsInstructionsBundle(agent.adapterType)) {
|
|
582
749
|
return agent;
|
|
583
750
|
}
|
|
@@ -588,20 +755,33 @@ export function agentRoutes(db, options = {}) {
|
|
|
588
755
|
|| Boolean(asNonEmptyString(adapterConfig.instructionsFilePath))
|
|
589
756
|
|| Boolean(asNonEmptyString(adapterConfig.agentsMdPath));
|
|
590
757
|
if (hasExplicitInstructionsBundle) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
758
|
+
const nextAdapterConfig = { ...adapterConfig };
|
|
759
|
+
const hadLegacyPrompt = Object.prototype.hasOwnProperty.call(nextAdapterConfig, "promptTemplate")
|
|
760
|
+
|| Object.prototype.hasOwnProperty.call(nextAdapterConfig, "bootstrapPromptTemplate");
|
|
761
|
+
delete nextAdapterConfig.promptTemplate;
|
|
762
|
+
delete nextAdapterConfig.bootstrapPromptTemplate;
|
|
763
|
+
if (!hadLegacyPrompt)
|
|
764
|
+
return agent;
|
|
765
|
+
const updated = await svc.update(agent.id, { adapterConfig: nextAdapterConfig });
|
|
766
|
+
return updated ?? { ...agent, adapterConfig: nextAdapterConfig };
|
|
767
|
+
}
|
|
768
|
+
const files = input?.files
|
|
769
|
+
?? await loadDefaultAgentInstructionsBundle(resolveDefaultAgentInstructionsBundleRole(agent.role));
|
|
770
|
+
const materialized = await instructions.materializeManagedBundle(agent, files, { entryFile: input?.entryFile ?? "AGENTS.md", replaceExisting: false });
|
|
600
771
|
const nextAdapterConfig = { ...materialized.adapterConfig };
|
|
601
772
|
delete nextAdapterConfig.promptTemplate;
|
|
773
|
+
delete nextAdapterConfig.bootstrapPromptTemplate;
|
|
602
774
|
const updated = await svc.update(agent.id, { adapterConfig: nextAdapterConfig });
|
|
603
775
|
return updated ?? { ...agent, adapterConfig: nextAdapterConfig };
|
|
604
776
|
}
|
|
777
|
+
function assertNoNewAgentLegacyPromptTemplate(adapterType, adapterConfig) {
|
|
778
|
+
if (!adapterSupportsInstructionsBundle(adapterType))
|
|
779
|
+
return;
|
|
780
|
+
if (Object.prototype.hasOwnProperty.call(adapterConfig, "promptTemplate")
|
|
781
|
+
|| Object.prototype.hasOwnProperty.call(adapterConfig, "bootstrapPromptTemplate")) {
|
|
782
|
+
throw unprocessable("New agents must use instructionsBundle/AGENTS.md instead of adapterConfig.promptTemplate or bootstrapPromptTemplate");
|
|
783
|
+
}
|
|
784
|
+
}
|
|
605
785
|
async function assertCanManageInstructionsPath(req, targetAgent) {
|
|
606
786
|
assertCompanyAccess(req, targetAgent.companyId);
|
|
607
787
|
if (req.actor.type !== "board") {
|
|
@@ -609,14 +789,23 @@ export function agentRoutes(db, options = {}) {
|
|
|
609
789
|
}
|
|
610
790
|
await assertBoardCanManageAgentsForCompany(req, targetAgent.companyId);
|
|
611
791
|
}
|
|
612
|
-
function assertNoAgentInstructionsConfigMutation(req, adapterConfig) {
|
|
792
|
+
function assertNoAgentInstructionsConfigMutation(req, adapterConfig, path = "adapterConfig") {
|
|
613
793
|
if (req.actor.type !== "agent" || !adapterConfig)
|
|
614
794
|
return;
|
|
615
|
-
const changedSensitiveKeys = KNOWN_INSTRUCTIONS_BUNDLE_KEYS
|
|
795
|
+
const changedSensitiveKeys = KNOWN_INSTRUCTIONS_BUNDLE_KEYS
|
|
796
|
+
.filter((key) => adapterConfig[key] !== undefined)
|
|
797
|
+
.map((key) => `${path}.${key}`);
|
|
616
798
|
if (changedSensitiveKeys.length === 0)
|
|
617
799
|
return;
|
|
618
800
|
throw forbidden(`Agent-authenticated callers cannot modify instructions path or bundle configuration (${changedSensitiveKeys.join(", ")})`);
|
|
619
801
|
}
|
|
802
|
+
function adapterConfigTouchesInstructionsConfig(adapterConfig) {
|
|
803
|
+
return KNOWN_INSTRUCTIONS_BUNDLE_KEYS.some((key) => adapterConfig[key] !== undefined);
|
|
804
|
+
}
|
|
805
|
+
function assertNoAgentAdapterConfigMutation(req, adapterConfig, path = "adapterConfig") {
|
|
806
|
+
assertNoAgentInstructionsConfigMutation(req, adapterConfig, path);
|
|
807
|
+
assertNoAgentHostWorkspaceCommandMutation(req, collectAgentAdapterWorkspaceCommandPaths(adapterConfig, path));
|
|
808
|
+
}
|
|
620
809
|
function summarizeAgentUpdateDetails(patch) {
|
|
621
810
|
const changedTopLevelKeys = Object.keys(patch).sort();
|
|
622
811
|
const details = { changedTopLevelKeys };
|
|
@@ -765,11 +954,29 @@ export function agentRoutes(db, options = {}) {
|
|
|
765
954
|
const refresh = typeof req.query.refresh === "string"
|
|
766
955
|
? ["1", "true", "yes"].includes(req.query.refresh.toLowerCase())
|
|
767
956
|
: false;
|
|
957
|
+
const environmentId = asNonEmptyString(req.query.environmentId);
|
|
958
|
+
const environment = environmentId ? await environmentsSvc.getById(environmentId) : null;
|
|
959
|
+
if (environmentId && (!environment || environment.companyId !== companyId)) {
|
|
960
|
+
res.status(404).json({ error: "Environment not found" });
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (type === "opencode_local" && environment && environment.driver !== "local") {
|
|
964
|
+
const adapter = requireServerAdapter(type);
|
|
965
|
+
res.json(adapter.models ?? []);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
768
968
|
const models = refresh
|
|
769
969
|
? await refreshAdapterModels(type)
|
|
770
970
|
: await listAdapterModels(type);
|
|
771
971
|
res.json(models);
|
|
772
972
|
});
|
|
973
|
+
router.get("/companies/:companyId/adapters/:type/model-profiles", async (req, res) => {
|
|
974
|
+
const companyId = req.params.companyId;
|
|
975
|
+
assertCompanyAccess(req, companyId);
|
|
976
|
+
const type = assertKnownAdapterType(req.params.type);
|
|
977
|
+
const profiles = await listAdapterModelProfiles(type);
|
|
978
|
+
res.json(profiles);
|
|
979
|
+
});
|
|
773
980
|
router.get("/companies/:companyId/adapters/:type/detect-model", async (req, res) => {
|
|
774
981
|
const companyId = req.params.companyId;
|
|
775
982
|
await assertCanReadConfigurations(req, companyId);
|
|
@@ -783,13 +990,33 @@ export function agentRoutes(db, options = {}) {
|
|
|
783
990
|
await assertCanReadConfigurations(req, companyId);
|
|
784
991
|
const adapter = requireServerAdapter(type);
|
|
785
992
|
const inputAdapterConfig = (req.body?.adapterConfig ?? {});
|
|
993
|
+
const requestedEnvironmentId = typeof req.body?.environmentId === "string" && req.body.environmentId.trim().length > 0
|
|
994
|
+
? req.body.environmentId
|
|
995
|
+
: null;
|
|
786
996
|
const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(companyId, inputAdapterConfig, { strictMode: strictSecretsMode });
|
|
787
997
|
const { config: runtimeAdapterConfig } = await secretsSvc.resolveAdapterConfigForRuntime(companyId, normalizedAdapterConfig);
|
|
998
|
+
const { executionTarget, environmentName, fallbackChecks } = await resolveAdapterTestExecutionContext({
|
|
999
|
+
companyId,
|
|
1000
|
+
adapterType: type,
|
|
1001
|
+
environmentId: requestedEnvironmentId,
|
|
1002
|
+
});
|
|
788
1003
|
const result = await adapter.testEnvironment({
|
|
789
1004
|
companyId,
|
|
790
1005
|
adapterType: type,
|
|
791
1006
|
config: runtimeAdapterConfig,
|
|
1007
|
+
executionTarget,
|
|
1008
|
+
environmentName,
|
|
792
1009
|
});
|
|
1010
|
+
if (fallbackChecks.length > 0) {
|
|
1011
|
+
const checks = [...fallbackChecks, ...result.checks];
|
|
1012
|
+
const status = checks.some((c) => c.level === "error")
|
|
1013
|
+
? "fail"
|
|
1014
|
+
: checks.some((c) => c.level === "warn")
|
|
1015
|
+
? "warn"
|
|
1016
|
+
: result.status;
|
|
1017
|
+
res.json({ ...result, checks, status });
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
793
1020
|
res.json(result);
|
|
794
1021
|
});
|
|
795
1022
|
router.get("/agents/:id/skills", async (req, res) => {
|
|
@@ -1199,18 +1426,24 @@ export function agentRoutes(db, options = {}) {
|
|
|
1199
1426
|
const companyId = req.params.companyId;
|
|
1200
1427
|
await assertCanCreateAgentsForCompany(req, companyId);
|
|
1201
1428
|
const sourceIssueIds = parseSourceIssueIds(req.body);
|
|
1202
|
-
const { desiredSkills: requestedDesiredSkills, sourceIssueId: _sourceIssueId, sourceIssueIds: _sourceIssueIds, ...hireInput } = req.body;
|
|
1429
|
+
const { desiredSkills: requestedDesiredSkills, instructionsBundle, sourceIssueId: _sourceIssueId, sourceIssueIds: _sourceIssueIds, ...hireInput } = req.body;
|
|
1203
1430
|
hireInput.adapterType = assertKnownAdapterType(hireInput.adapterType);
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1431
|
+
const rawHireAdapterConfig = (hireInput.adapterConfig ?? {});
|
|
1432
|
+
assertNoNewAgentLegacyPromptTemplate(hireInput.adapterType, rawHireAdapterConfig);
|
|
1433
|
+
assertNoAgentAdapterConfigMutation(req, rawHireAdapterConfig);
|
|
1434
|
+
assertNoAgentRuntimeConfigAdapterConfigMutation(req, hireInput.runtimeConfig);
|
|
1435
|
+
const requestedAdapterConfig = applyCreateDefaultsByAdapterType(hireInput.adapterType, rawHireAdapterConfig);
|
|
1207
1436
|
const desiredSkillAssignment = await resolveDesiredSkillAssignment(companyId, hireInput.adapterType, requestedAdapterConfig, Array.isArray(requestedDesiredSkills) ? requestedDesiredSkills : undefined);
|
|
1208
|
-
const normalizedAdapterConfig = await
|
|
1209
|
-
|
|
1437
|
+
const normalizedAdapterConfig = await normalizeMediatedAdapterConfigForPersistence({
|
|
1438
|
+
companyId,
|
|
1439
|
+
adapterType: hireInput.adapterType,
|
|
1440
|
+
adapterConfig: desiredSkillAssignment.adapterConfig,
|
|
1441
|
+
});
|
|
1442
|
+
const normalizedRuntimeConfig = await normalizeRuntimeConfigAdapterConfigsForPersistence(companyId, hireInput.adapterType, normalizeNewAgentRuntimeConfig(hireInput.runtimeConfig), normalizedAdapterConfig);
|
|
1210
1443
|
const normalizedHireInput = {
|
|
1211
1444
|
...hireInput,
|
|
1212
1445
|
adapterConfig: normalizedAdapterConfig,
|
|
1213
|
-
runtimeConfig:
|
|
1446
|
+
runtimeConfig: normalizedRuntimeConfig,
|
|
1214
1447
|
};
|
|
1215
1448
|
const company = await db
|
|
1216
1449
|
.select()
|
|
@@ -1229,7 +1462,7 @@ export function agentRoutes(db, options = {}) {
|
|
|
1229
1462
|
spentMonthlyCents: 0,
|
|
1230
1463
|
lastHeartbeatAt: null,
|
|
1231
1464
|
});
|
|
1232
|
-
const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent);
|
|
1465
|
+
const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent, instructionsBundle);
|
|
1233
1466
|
let approval = null;
|
|
1234
1467
|
const actor = getActorInfo(req);
|
|
1235
1468
|
if (requiresApproval) {
|
|
@@ -1331,14 +1564,20 @@ export function agentRoutes(db, options = {}) {
|
|
|
1331
1564
|
if (company.requireBoardApprovalForNewAgents) {
|
|
1332
1565
|
throw conflict("Direct agent creation requires board approval. Use POST /api/companies/:companyId/agent-hires to create a pending hire approval.");
|
|
1333
1566
|
}
|
|
1334
|
-
const { desiredSkills: requestedDesiredSkills, ...createInput } = req.body;
|
|
1567
|
+
const { desiredSkills: requestedDesiredSkills, instructionsBundle, ...createInput } = req.body;
|
|
1335
1568
|
createInput.adapterType = assertKnownAdapterType(createInput.adapterType);
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1569
|
+
const rawCreateAdapterConfig = (createInput.adapterConfig ?? {});
|
|
1570
|
+
assertNoNewAgentLegacyPromptTemplate(createInput.adapterType, rawCreateAdapterConfig);
|
|
1571
|
+
assertNoAgentAdapterConfigMutation(req, rawCreateAdapterConfig);
|
|
1572
|
+
assertNoAgentRuntimeConfigAdapterConfigMutation(req, createInput.runtimeConfig);
|
|
1573
|
+
const requestedAdapterConfig = applyCreateDefaultsByAdapterType(createInput.adapterType, rawCreateAdapterConfig);
|
|
1339
1574
|
const desiredSkillAssignment = await resolveDesiredSkillAssignment(companyId, createInput.adapterType, requestedAdapterConfig, Array.isArray(requestedDesiredSkills) ? requestedDesiredSkills : undefined);
|
|
1340
|
-
const normalizedAdapterConfig = await
|
|
1341
|
-
|
|
1575
|
+
const normalizedAdapterConfig = await normalizeMediatedAdapterConfigForPersistence({
|
|
1576
|
+
companyId,
|
|
1577
|
+
adapterType: createInput.adapterType,
|
|
1578
|
+
adapterConfig: desiredSkillAssignment.adapterConfig,
|
|
1579
|
+
});
|
|
1580
|
+
const normalizedRuntimeConfig = await normalizeRuntimeConfigAdapterConfigsForPersistence(companyId, createInput.adapterType, normalizeNewAgentRuntimeConfig(createInput.runtimeConfig), normalizedAdapterConfig);
|
|
1342
1581
|
await assertAgentEnvironmentSelection(companyId, createInput.adapterType, createInput.defaultEnvironmentId);
|
|
1343
1582
|
await assertAgentDefaultEnvironmentSelection(companyId, createInput.defaultEnvironmentId, {
|
|
1344
1583
|
allowedDrivers: allowedEnvironmentDriversForAgent(createInput.adapterType),
|
|
@@ -1347,12 +1586,12 @@ export function agentRoutes(db, options = {}) {
|
|
|
1347
1586
|
const createdAgent = await svc.create(companyId, {
|
|
1348
1587
|
...createInput,
|
|
1349
1588
|
adapterConfig: normalizedAdapterConfig,
|
|
1350
|
-
runtimeConfig:
|
|
1589
|
+
runtimeConfig: normalizedRuntimeConfig,
|
|
1351
1590
|
status: "idle",
|
|
1352
1591
|
spentMonthlyCents: 0,
|
|
1353
1592
|
lastHeartbeatAt: null,
|
|
1354
1593
|
});
|
|
1355
|
-
const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent);
|
|
1594
|
+
const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent, instructionsBundle);
|
|
1356
1595
|
const actor = getActorInfo(req);
|
|
1357
1596
|
await logActivity(db, {
|
|
1358
1597
|
companyId,
|
|
@@ -1646,9 +1885,8 @@ export function agentRoutes(db, options = {}) {
|
|
|
1646
1885
|
res.status(422).json({ error: "adapterConfig must be an object" });
|
|
1647
1886
|
return;
|
|
1648
1887
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
const changingInstructionsConfig = Object.keys(adapterConfig).some((key) => KNOWN_INSTRUCTIONS_BUNDLE_KEYS.includes(key));
|
|
1888
|
+
assertNoAgentAdapterConfigMutation(req, adapterConfig);
|
|
1889
|
+
const changingInstructionsConfig = adapterConfigTouchesInstructionsConfig(adapterConfig);
|
|
1652
1890
|
if (changingInstructionsConfig) {
|
|
1653
1891
|
await assertCanManageInstructionsPath(req, existing);
|
|
1654
1892
|
}
|
|
@@ -1657,6 +1895,16 @@ export function agentRoutes(db, options = {}) {
|
|
|
1657
1895
|
const requestedAdapterType = hasOwn(patchData, "adapterType")
|
|
1658
1896
|
? assertKnownAdapterType(patchData.adapterType)
|
|
1659
1897
|
: existing.adapterType;
|
|
1898
|
+
let requestedRuntimeConfig = null;
|
|
1899
|
+
if (hasOwn(patchData, "runtimeConfig")) {
|
|
1900
|
+
const runtimeConfig = asRecord(patchData.runtimeConfig);
|
|
1901
|
+
if (!runtimeConfig) {
|
|
1902
|
+
res.status(422).json({ error: "runtimeConfig must be an object" });
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
assertNoAgentRuntimeConfigAdapterConfigMutation(req, runtimeConfig);
|
|
1906
|
+
requestedRuntimeConfig = runtimeConfig;
|
|
1907
|
+
}
|
|
1660
1908
|
const touchesAdapterConfiguration = hasOwn(patchData, "adapterType") ||
|
|
1661
1909
|
hasOwn(patchData, "adapterConfig");
|
|
1662
1910
|
if (touchesAdapterConfiguration) {
|
|
@@ -1690,12 +1938,16 @@ export function agentRoutes(db, options = {}) {
|
|
|
1690
1938
|
rawEffectiveAdapterConfig = preserveInstructionsBundleConfig(existingAdapterConfig, rawEffectiveAdapterConfig);
|
|
1691
1939
|
}
|
|
1692
1940
|
const effectiveAdapterConfig = applyCreateDefaultsByAdapterType(requestedAdapterType, rawEffectiveAdapterConfig);
|
|
1693
|
-
const normalizedEffectiveAdapterConfig = await
|
|
1941
|
+
const normalizedEffectiveAdapterConfig = await normalizeMediatedAdapterConfigForPersistence({
|
|
1942
|
+
companyId: existing.companyId,
|
|
1943
|
+
adapterType: requestedAdapterType,
|
|
1944
|
+
adapterConfig: effectiveAdapterConfig,
|
|
1945
|
+
});
|
|
1694
1946
|
patchData.adapterConfig = syncInstructionsBundleConfigFromFilePath(existing, normalizedEffectiveAdapterConfig);
|
|
1695
1947
|
}
|
|
1696
|
-
if (
|
|
1697
|
-
const
|
|
1698
|
-
await
|
|
1948
|
+
if (requestedRuntimeConfig) {
|
|
1949
|
+
const baseAdapterConfig = asRecord(patchData.adapterConfig) ?? asRecord(existing.adapterConfig) ?? {};
|
|
1950
|
+
patchData.runtimeConfig = await normalizeRuntimeConfigAdapterConfigsForPersistence(existing.companyId, requestedAdapterType, requestedRuntimeConfig, baseAdapterConfig);
|
|
1699
1951
|
}
|
|
1700
1952
|
if (touchesAdapterConfiguration || Object.prototype.hasOwnProperty.call(patchData, "defaultEnvironmentId")) {
|
|
1701
1953
|
await assertAgentDefaultEnvironmentSelection(existing.companyId, Object.prototype.hasOwnProperty.call(patchData, "defaultEnvironmentId")
|
|
@@ -2042,14 +2294,21 @@ export function agentRoutes(db, options = {}) {
|
|
|
2042
2294
|
router.get("/companies/:companyId/live-runs", async (req, res) => {
|
|
2043
2295
|
const companyId = req.params.companyId;
|
|
2044
2296
|
assertCompanyAccess(req, companyId);
|
|
2045
|
-
|
|
2046
|
-
|
|
2297
|
+
// `minCount` is a padding floor for callers that want a minimum number of
|
|
2298
|
+
// recent runs to render (e.g. dashboard cards). It must default to 0 so
|
|
2299
|
+
// callers asking for "live runs" get only actually-live runs — otherwise
|
|
2300
|
+
// every caller with no minCount param gets up to 50 historical runs
|
|
2301
|
+
// padded in and renders bogus "live" counts.
|
|
2302
|
+
const minCount = readLiveRunsQueryInt(req.query.minCount, 50, 0);
|
|
2303
|
+
const limit = readLiveRunsQueryInt(req.query.limit, 50, 50);
|
|
2047
2304
|
const columns = {
|
|
2048
2305
|
id: heartbeatRuns.id,
|
|
2049
2306
|
companyId: heartbeatRuns.companyId,
|
|
2050
2307
|
status: heartbeatRuns.status,
|
|
2051
2308
|
invocationSource: heartbeatRuns.invocationSource,
|
|
2052
2309
|
triggerDetail: heartbeatRuns.triggerDetail,
|
|
2310
|
+
contextCommentId: sql `${heartbeatRuns.contextSnapshot} ->> 'commentId'`.as("contextCommentId"),
|
|
2311
|
+
contextWakeCommentId: sql `${heartbeatRuns.contextSnapshot} ->> 'wakeCommentId'`.as("contextWakeCommentId"),
|
|
2053
2312
|
startedAt: heartbeatRuns.startedAt,
|
|
2054
2313
|
finishedAt: heartbeatRuns.finishedAt,
|
|
2055
2314
|
createdAt: heartbeatRuns.createdAt,
|
|
@@ -2075,8 +2334,8 @@ export function agentRoutes(db, options = {}) {
|
|
|
2075
2334
|
.innerJoin(agentsTable, eq(heartbeatRuns.agentId, agentsTable.id))
|
|
2076
2335
|
.where(and(eq(heartbeatRuns.companyId, companyId), inArray(heartbeatRuns.status, ["queued", "running"])))
|
|
2077
2336
|
.orderBy(desc(heartbeatRuns.createdAt));
|
|
2078
|
-
const liveRuns =
|
|
2079
|
-
const targetRunCount =
|
|
2337
|
+
const liveRuns = await liveRunsQuery.limit(limit);
|
|
2338
|
+
const targetRunCount = Math.min(minCount, limit);
|
|
2080
2339
|
if (targetRunCount > 0 && liveRuns.length < targetRunCount) {
|
|
2081
2340
|
const activeIds = liveRuns.map((r) => r.id);
|
|
2082
2341
|
const recentRuns = await db
|
|
@@ -2231,8 +2490,8 @@ export function agentRoutes(db, options = {}) {
|
|
|
2231
2490
|
router.get("/issues/:issueId/live-runs", async (req, res) => {
|
|
2232
2491
|
const rawId = req.params.issueId;
|
|
2233
2492
|
const issueSvc = issueService(db);
|
|
2234
|
-
const
|
|
2235
|
-
const issue =
|
|
2493
|
+
const identifier = normalizeIssueIdentifier(rawId);
|
|
2494
|
+
const issue = identifier ? await issueSvc.getByIdentifier(identifier) : await issueSvc.getById(rawId);
|
|
2236
2495
|
if (!issue) {
|
|
2237
2496
|
res.status(404).json({ error: "Issue not found" });
|
|
2238
2497
|
return;
|
|
@@ -2244,6 +2503,8 @@ export function agentRoutes(db, options = {}) {
|
|
|
2244
2503
|
status: heartbeatRuns.status,
|
|
2245
2504
|
invocationSource: heartbeatRuns.invocationSource,
|
|
2246
2505
|
triggerDetail: heartbeatRuns.triggerDetail,
|
|
2506
|
+
contextCommentId: sql `${heartbeatRuns.contextSnapshot} ->> 'commentId'`.as("contextCommentId"),
|
|
2507
|
+
contextWakeCommentId: sql `${heartbeatRuns.contextSnapshot} ->> 'wakeCommentId'`.as("contextWakeCommentId"),
|
|
2247
2508
|
startedAt: heartbeatRuns.startedAt,
|
|
2248
2509
|
finishedAt: heartbeatRuns.finishedAt,
|
|
2249
2510
|
createdAt: heartbeatRuns.createdAt,
|
|
@@ -2274,8 +2535,8 @@ export function agentRoutes(db, options = {}) {
|
|
|
2274
2535
|
router.get("/issues/:issueId/active-run", async (req, res) => {
|
|
2275
2536
|
const rawId = req.params.issueId;
|
|
2276
2537
|
const issueSvc = issueService(db);
|
|
2277
|
-
const
|
|
2278
|
-
const issue =
|
|
2538
|
+
const identifier = normalizeIssueIdentifier(rawId);
|
|
2539
|
+
const issue = identifier ? await issueSvc.getByIdentifier(identifier) : await issueSvc.getById(rawId);
|
|
2279
2540
|
if (!issue) {
|
|
2280
2541
|
res.status(404).json({ error: "Issue not found" });
|
|
2281
2542
|
return;
|