@straiffi/archon 1.8.15 → 1.8.17
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/client/assets/ActivityView-XT8l-IRo.js +1 -0
- package/dist/client/assets/GitHubActionsDialog-C1nmgELc.js +1 -0
- package/dist/client/assets/VerificationDialog-Cg5XNaPd.js +4 -0
- package/dist/client/assets/{arc-C83vRtlz.js → arc-D2Nn9xIj.js} +1 -1
- package/dist/client/assets/architecture-7EHR7CIX-C-4Cp4ml.js +1 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-Dx7MJofM.js → architectureDiagram-3BPJPVTR-YDD_HNbm.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-CSSJR8F_.js → blockDiagram-GPEHLZMM-BCgBIlJR.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-C4F9969r.js → c4Diagram-AAUBKEIU-BLfk5bKl.js} +1 -1
- package/dist/client/assets/channel-3_WpUZue.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-23HW6i-y.js → chunk-2J33WTMH-DvwqUfn2.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-C3ZmRDMb.js → chunk-3OPIFGDE-D9R3JLGz.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-C5pU9ApX.js → chunk-4BX2VUAB-CtLS8D71.js} +1 -1
- package/dist/client/assets/{chunk-4EGX6M5U-CAjWPjND.js → chunk-4EGX6M5U-Btnm0QNe.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-DhO4-AG4.js → chunk-55IACEB6-74CcJaub.js} +1 -1
- package/dist/client/assets/{chunk-5DO6E6H7-BDKA-zHS.js → chunk-5DO6E6H7-WLZ8L4ko.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-B_-UQvCg.js → chunk-5ZQYHXKU-CZeWOab2.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-5Dua6u1B.js → chunk-727SXJPM-YdTt6zCi.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DcpcaFRO.js → chunk-AQP2D5EJ-BFHbP_eb.js} +1 -1
- package/dist/client/assets/{chunk-BR22UD5L-12D2GWh4.js → chunk-BR22UD5L-Cou5beGe.js} +1 -1
- package/dist/client/assets/{chunk-BSJP7CBP-DMi77e84.js → chunk-BSJP7CBP-DJ57XcV5.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-CLCpOw52.js → chunk-CSCIHK7Q-C0BPXL2C.js} +2 -2
- package/dist/client/assets/{chunk-FHYWG6QK-BhSajKiB.js → chunk-FHYWG6QK-Ba10e2kj.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Drmlc0wM.js → chunk-FMBD7UC4-CEo2wZMx.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-uOZFe2JI.js → chunk-KSCS5N6A-DqC4tO6g.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-DNnzSNoX.js → chunk-L5ZTLDWV-BERUWsJ6.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-CSA3Xnoh.js → chunk-LZXEDZCA-BUHwhuWm.js} +2 -2
- package/dist/client/assets/{chunk-MPE355IW-Cw8YmjTC.js → chunk-MPE355IW-MqEt-J8G.js} +1 -1
- package/dist/client/assets/{chunk-MZUSXYTE-jvxRCZ7x.js → chunk-MZUSXYTE-DdNi9lhx.js} +1 -1
- package/dist/client/assets/{chunk-N66VUXT2-00i4y0yD.js → chunk-N66VUXT2-CjemTW4E.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-BQR5rD7w.js → chunk-ND2GUHAM-DCfmNJ41.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-DyIwcQYQ.js → chunk-NZK2D7GU-CHOkPepJ.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-DfW60w5V.js → chunk-O5CBEL6O-ByMuL2EO.js} +1 -1
- package/dist/client/assets/{chunk-PUPMXCY4-DfWourL7.js → chunk-PUPMXCY4-ZvwTw0To.js} +1 -1
- package/dist/client/assets/chunk-QZHKN3VN-LI_RZca2.js +1 -0
- package/dist/client/assets/{chunk-UIBZB4QT-v-a5ZfN6.js → chunk-UIBZB4QT-BTY9be9s.js} +1 -1
- package/dist/client/assets/{chunk-WCWK7LTN-CtYzhvvb.js → chunk-WCWK7LTN-Db1n3n3-.js} +1 -1
- package/dist/client/assets/chunk-WU5MYG2G-BGsB3vTj.js +1 -0
- package/dist/client/assets/{chunk-XPW4576I-CvnAuXH2.js → chunk-XPW4576I-DObFPxjB.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK--L8rxPgF.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-uW5z5Qib.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-fchD-dH2.js → cose-bilkent-S5V4N54A-MlOPiG6P.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-Ba7Pz-ZI.js → dagre-BM42HDAG-CtmlVYvf.js} +1 -1
- package/dist/client/assets/{dagre-DSrzm-Gq.js → dagre-BWNHuDdf.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-BxYfgpZ6.js → diagram-2AECGRRQ-DmQ2-tFR.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-Bc4-O-UJ.js → diagram-5GNKFQAL-l49VXoBJ.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-Ds1Lmrzm.js → diagram-KO2AKTUF-0-U37RyJ.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-VFQabxKS.js → diagram-LMA3HP47-DupuMWgW.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-Baff59Zc.js → diagram-OG6HWLK6-D5DoIHW2.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-Da5zHjOK.js → erDiagram-TEJ5UH35-DO-3-WYF.js} +1 -1
- package/dist/client/assets/eventmodeling-FCH6USID-Dqb6QKoI.js +1 -0
- package/dist/client/assets/{flowDiagram-I6XJVG4X-pdGMAuog.js → flowDiagram-I6XJVG4X-CG1viGSO.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-DjJGX0nm.js → ganttDiagram-6RSMTGT7-CV-6ALMZ.js} +1 -1
- package/dist/client/assets/gitGraph-WXDBUCRP-DYEsqh93.js +1 -0
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-kErlwtcT.js → gitGraphDiagram-PVQCEYII-CHnyP-d3.js} +1 -1
- package/dist/client/assets/index-CsZfTdMO.css +2 -0
- package/dist/client/assets/index-DQ7z6ZWk.js +275 -0
- package/dist/client/assets/info-J43DQDTF-CfjXFZai.js +1 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-Bx78HhDi.js → infoDiagram-5YYISTIA-iKC-l4xs.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BP0YQCMl.js → ishikawaDiagram-YF4QCWOH-zi-y6jRI.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-DUgrCOuD.js → journeyDiagram-JHISSGLW-Da4wMeC4.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-K0Zc5_M8.js → kanban-definition-UN3LZRKU-CZQ1yL5y.js} +1 -1
- package/dist/client/assets/{line-Dtg-krjk.js → line-B-FS1zIN.js} +1 -1
- package/dist/client/assets/{linear-BOwW57yX.js → linear-DT4rSZvy.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-Dw5BI77W.js → mermaid-parser.core-DPXl3axs.js} +2 -2
- package/dist/client/assets/{mermaid.core-BE-qlWNj.js → mermaid.core-DjqrtE_A.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-1Piz4H9I.js → mindmap-definition-RKZ34NQL-Day2-dLJ.js} +1 -1
- package/dist/client/assets/{ordinal-CZ33lFpK.js → ordinal-k--hYEme.js} +1 -1
- package/dist/client/assets/packet-YPE3B663-Dv0HkRHU.js +1 -0
- package/dist/client/assets/pie-LRSECV5Y-S5zPzZFN.js +1 -0
- package/dist/client/assets/{pieDiagram-4H26LBE5-zlWdJ7lL.js → pieDiagram-4H26LBE5-BmZiJzvi.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DRQY8Adp.js → quadrantDiagram-W4KKPZXB-DJgAcycV.js} +1 -1
- package/dist/client/assets/radar-GUYGQ44K-B7psLv22.js +1 -0
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-B8_GJFxl.js → requirementDiagram-4Y6WPE33-y94ek5ya.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BaI0Njo8.js → sankeyDiagram-5OEKKPKP-ChuulS6b.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DjqAXbRF.js → sequenceDiagram-3UESZ5HK-C7eiUu5p.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-Ovr_3LeO.js → stateDiagram-AJRCARHV-Dy4QkKUV.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-HdcReOP7.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-DkczYKeZ.js → timeline-definition-PNZ67QCA-CTtZLJWJ.js} +1 -1
- package/dist/client/assets/treeView-BLDUP644-B8H0kT67.js +1 -0
- package/dist/client/assets/treemap-LRROVOQU-DPLhFHnh.js +1 -0
- package/dist/client/assets/{vennDiagram-CIIHVFJN-BZyo4ESi.js → vennDiagram-CIIHVFJN-C5matxMS.js} +1 -1
- package/dist/client/assets/wardley-L42UT6IY-B5-Vb08V.js +1 -0
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BEISrECX.js → wardleyDiagram-YWT4CUSO-m_iOvfzY.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-CyNzGK7f.js → xychartDiagram-2RQKCTM6-BmOxTt9K.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +235 -0
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +200 -45
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +139 -90
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/agentProvider.js +1 -6
- package/dist/server/lib/agentProvider.js.map +1 -1
- package/dist/server/lib/buildExecutionEnvironment.js +535 -0
- package/dist/server/lib/buildExecutionEnvironment.js.map +1 -0
- package/dist/server/lib/buildOpenQuestionOperations.js +9 -3
- package/dist/server/lib/buildOpenQuestionOperations.js.map +1 -1
- package/dist/server/lib/checkAutoFix.js +1 -0
- package/dist/server/lib/checkAutoFix.js.map +1 -1
- package/dist/server/lib/checkRepairDispatch.js +3 -1
- package/dist/server/lib/checkRepairDispatch.js.map +1 -1
- package/dist/server/lib/dockerSandboxCredentials.js +62 -0
- package/dist/server/lib/dockerSandboxCredentials.js.map +1 -0
- package/dist/server/lib/dockerSandboxEnvironmentOperations.js +54 -0
- package/dist/server/lib/dockerSandboxEnvironmentOperations.js.map +1 -0
- package/dist/server/lib/dockerSandboxExecutionEnvironment.js +725 -0
- package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -0
- package/dist/server/lib/dockerSandboxExecutionProvider.js +196 -0
- package/dist/server/lib/dockerSandboxExecutionProvider.js.map +1 -0
- package/dist/server/lib/dockerSandboxOpencodeSessionOperations.js +29 -0
- package/dist/server/lib/dockerSandboxOpencodeSessionOperations.js.map +1 -0
- package/dist/server/lib/dockerSandboxRuntimeController.js +187 -0
- package/dist/server/lib/dockerSandboxRuntimeController.js.map +1 -0
- package/dist/server/lib/executionEnvironment.js +22 -0
- package/dist/server/lib/executionEnvironment.js.map +1 -1
- package/dist/server/lib/executionEnvironmentContexts.js +144 -0
- package/dist/server/lib/executionEnvironmentContexts.js.map +1 -0
- package/dist/server/lib/executionEnvironmentLifecycle.js +63 -0
- package/dist/server/lib/executionEnvironmentLifecycle.js.map +1 -0
- package/dist/server/lib/executionEnvironmentReconciliation.js +782 -0
- package/dist/server/lib/executionEnvironmentReconciliation.js.map +1 -0
- package/dist/server/lib/executionEnvironmentRoutes.js +375 -0
- package/dist/server/lib/executionEnvironmentRoutes.js.map +1 -0
- package/dist/server/lib/executionEnvironmentValidation.js +540 -0
- package/dist/server/lib/executionEnvironmentValidation.js.map +1 -0
- package/dist/server/lib/executionEnvironmentValidationOperations.js +636 -0
- package/dist/server/lib/executionEnvironmentValidationOperations.js.map +1 -0
- package/dist/server/lib/executionEnvironments.js +726 -0
- package/dist/server/lib/executionEnvironments.js.map +1 -0
- package/dist/server/lib/executionReconciliation.js +77 -0
- package/dist/server/lib/executionReconciliation.js.map +1 -1
- package/dist/server/lib/executionRetry.js +3 -0
- package/dist/server/lib/executionRetry.js.map +1 -1
- package/dist/server/lib/executionRuns.js +1 -0
- package/dist/server/lib/executionRuns.js.map +1 -1
- package/dist/server/lib/executionTargets.js +107 -0
- package/dist/server/lib/executionTargets.js.map +1 -0
- package/dist/server/lib/executionTimeline.js +3 -0
- package/dist/server/lib/executionTimeline.js.map +1 -1
- package/dist/server/lib/hostExecutionEnvironment.js +47 -1
- package/dist/server/lib/hostExecutionEnvironment.js.map +1 -1
- package/dist/server/lib/mobileAccess.js +2 -2
- package/dist/server/lib/mobileAccess.js.map +1 -1
- package/dist/server/lib/models.js +11 -0
- package/dist/server/lib/models.js.map +1 -1
- package/dist/server/lib/opencodeErrors.js +31 -3
- package/dist/server/lib/opencodeErrors.js.map +1 -1
- package/dist/server/lib/opencodeSessionOperations.js +189 -9
- package/dist/server/lib/opencodeSessionOperations.js.map +1 -1
- package/dist/server/lib/opencodeTaskSessions.js +7 -12
- package/dist/server/lib/opencodeTaskSessions.js.map +1 -1
- package/dist/server/lib/postBuildChecks.js +2 -1
- package/dist/server/lib/postBuildChecks.js.map +1 -1
- package/dist/server/lib/projects.js +10 -0
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/specArtifact.js +1 -1
- package/dist/server/lib/specArtifact.js.map +1 -1
- package/dist/server/lib/specDraft.js +1 -5
- package/dist/server/lib/specDraft.js.map +1 -1
- package/dist/server/lib/specExport.js +1 -6
- package/dist/server/lib/specExport.js.map +1 -1
- package/dist/server/lib/specGraphFeature.js +3 -0
- package/dist/server/lib/specGraphFeature.js.map +1 -0
- package/dist/server/lib/specRepositoryEvidenceFormatting.js +2 -1
- package/dist/server/lib/specRepositoryEvidenceFormatting.js.map +1 -1
- package/dist/server/lib/specRepositoryImpact.js +4 -0
- package/dist/server/lib/specRepositoryImpact.js.map +1 -1
- package/dist/server/lib/specReviewContext.js +6 -20
- package/dist/server/lib/specReviewContext.js.map +1 -1
- package/dist/server/lib/specSnapshotEvidence.js +4 -0
- package/dist/server/lib/specSnapshotEvidence.js.map +1 -1
- package/dist/server/lib/specTicketMaterialization.js +4 -1
- package/dist/server/lib/specTicketMaterialization.js.map +1 -1
- package/dist/server/lib/ticketFollowUpOperations.js +8 -1
- package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
- package/dist/server/lib/ticketRunOperations.js +0 -3
- package/dist/server/lib/ticketRunOperations.js.map +1 -1
- package/dist/server/lib/types.js +1 -0
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/lib/workspaceIdentity.js +3 -0
- package/dist/server/lib/workspaceIdentity.js.map +1 -1
- package/dist/server/workers/build.js +115 -8
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/followUp.js +119 -3
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +34 -22
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/review.js +14 -13
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specReview.js +0 -1
- package/dist/server/workers/specReview.js.map +1 -1
- package/dist/server/workers/specSession.js +46 -122
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/ActivityView-DpY9GZez.js +0 -1
- package/dist/client/assets/GitHubActionsDialog-BCr-hjvp.js +0 -1
- package/dist/client/assets/RepositoryGraphDialog-_hm-x1Dx.js +0 -1
- package/dist/client/assets/VerificationDialog-CsjDzpE1.js +0 -4
- package/dist/client/assets/architecture-7EHR7CIX-Dm8H7N6Z.js +0 -1
- package/dist/client/assets/channel-DVkhJzgx.js +0 -1
- package/dist/client/assets/chunk-QZHKN3VN-CtbsU1Bj.js +0 -1
- package/dist/client/assets/chunk-WU5MYG2G-CzMMPYdi.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-6oZ93s5p.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DYO_YnkU.js +0 -1
- package/dist/client/assets/eventmodeling-FCH6USID-BWhw0UJ8.js +0 -1
- package/dist/client/assets/gitGraph-WXDBUCRP-BSnxAa1J.js +0 -1
- package/dist/client/assets/index-CS4TzG41.css +0 -2
- package/dist/client/assets/index-D5TEfELZ.js +0 -275
- package/dist/client/assets/info-J43DQDTF-DcFwF1-h.js +0 -1
- package/dist/client/assets/packet-YPE3B663-1VZiO_9r.js +0 -1
- package/dist/client/assets/pie-LRSECV5Y-BX1-tQCu.js +0 -1
- package/dist/client/assets/radar-GUYGQ44K-B2HRrCR4.js +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-9pmeAorV.js +0 -1
- package/dist/client/assets/treeView-BLDUP644-CDfUVbUi.js +0 -1
- package/dist/client/assets/treemap-LRROVOQU-DUWqu8sK.js +0 -1
- package/dist/client/assets/wardley-L42UT6IY-BEFuPUCy.js +0 -1
- /package/dist/client/assets/{array-B2f2amhX.js → array-BNor45A1.js} +0 -0
- /package/dist/client/assets/{chunk-NNHCCRGN-DgKrp8T-.js → chunk-NNHCCRGN-CZB-V-Gv.js} +0 -0
- /package/dist/client/assets/{cytoscape.esm-BInZCgaJ.js → cytoscape.esm-B2lJcMSI.js} +0 -0
- /package/dist/client/assets/{defaultLocale-sPXJ5urm.js → defaultLocale-DPzUsThw.js} +0 -0
- /package/dist/client/assets/{dist-DfHDMDTt.js → dist-CERhmQhR.js} +0 -0
- /package/dist/client/assets/{graphlib-DacNv4Jd.js → graphlib-BHOW-eWz.js} +0 -0
- /package/dist/client/assets/{init-BVCnG1lj.js → init-C0L3woqb.js} +0 -0
- /package/dist/client/assets/{katex-BlkrXOwm.js → katex-CpjQsUQe.js} +0 -0
- /package/dist/client/assets/{path-Bw5AvBNC.js → path-sMK4d_s9.js} +0 -0
- /package/dist/client/assets/{rough.esm-DeLgKbOI.js → rough.esm-BoTisKeL.js} +0 -0
- /package/dist/client/assets/{src-CFGsdLiG.js → src-BRjoYtUW.js} +0 -0
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
import { execFile as execFileCallback, spawn } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { constants as fsConstants } from 'node:fs';
|
|
4
|
+
import { access } from 'node:fs/promises';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { requireExecutionCapabilities } from './executionEnvironmentLifecycle.js';
|
|
7
|
+
export const DOCKER_SANDBOX_PROVIDER = 'docker_sandbox';
|
|
8
|
+
export const QUALIFIED_SBX_VERSION = '0.38.0';
|
|
9
|
+
export const QUALIFIED_SBX_BUILD = 'c022b14634c4bea846ca12870d1d5e97d5868b54';
|
|
10
|
+
export const DOCKER_SANDBOX_ADAPTER_CONTRACT_VERSION = '4';
|
|
11
|
+
export const DOCKER_SANDBOX_MAX_NAME_BYTES = 63;
|
|
12
|
+
export const QUALIFIED_DOCKER_SANDBOX_TEMPLATES = {
|
|
13
|
+
opencode: 'docker.io/docker/sandbox-templates:opencode',
|
|
14
|
+
claude: 'docker.io/docker/sandbox-templates:claude-code',
|
|
15
|
+
};
|
|
16
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
17
|
+
const DEFAULT_OUTPUT_LIMIT_BYTES = 256 * 1024;
|
|
18
|
+
const SANDBOX_NAME_HASH_LENGTH = 16;
|
|
19
|
+
const MINIMUM_MACOS_MAJOR = 14;
|
|
20
|
+
const KVM_DEVICE_PATH = '/dev/kvm';
|
|
21
|
+
const execFile = promisify(execFileCallback);
|
|
22
|
+
const QUALIFIED_DOCKER_SANDBOX_HOSTS = [
|
|
23
|
+
{ platform: 'linux', architecture: 'x64' },
|
|
24
|
+
{ platform: 'darwin', architecture: 'arm64' },
|
|
25
|
+
];
|
|
26
|
+
export const isQualifiedDockerSandboxHost = (platform, architecture) => QUALIFIED_DOCKER_SANDBOX_HOSTS.some(host => host.platform === platform && host.architecture === architecture);
|
|
27
|
+
export const dockerSandboxExecutionEnvironmentCapabilities = {
|
|
28
|
+
isolation: 'microvm',
|
|
29
|
+
directWritableWorkspace: 'supported',
|
|
30
|
+
transparentHeadlessStreams: 'supported',
|
|
31
|
+
credentialBrokering: 'supported',
|
|
32
|
+
enforcedNetworkAllowlist: 'unknown',
|
|
33
|
+
persistentEnvironments: 'supported',
|
|
34
|
+
resourceIsolation: 'unknown',
|
|
35
|
+
nestedContainers: 'unsupported',
|
|
36
|
+
publishedPorts: 'unsupported',
|
|
37
|
+
resourceControls: 'unsupported',
|
|
38
|
+
};
|
|
39
|
+
export const dockerSandboxExecutionEnvironmentObservation = {
|
|
40
|
+
providerVersion: QUALIFIED_SBX_VERSION,
|
|
41
|
+
capabilities: dockerSandboxExecutionEnvironmentCapabilities,
|
|
42
|
+
degradationReasons: ['The selected direct workspace remains writable from the sandbox.'],
|
|
43
|
+
agentTemplates: {
|
|
44
|
+
opencode: { support: 'unknown', templateId: null, reason: 'Template output has not been qualified.' },
|
|
45
|
+
claude: { support: 'unknown', templateId: null, reason: 'Template output has not been qualified.' },
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
export class SbxCommandFailure extends Error {
|
|
49
|
+
kind;
|
|
50
|
+
args;
|
|
51
|
+
constructor(kind, args, message, options = {}) {
|
|
52
|
+
super(message, options);
|
|
53
|
+
this.name = 'SbxCommandFailure';
|
|
54
|
+
this.kind = kind;
|
|
55
|
+
this.args = [...args];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class SbxContractFailure extends Error {
|
|
59
|
+
command;
|
|
60
|
+
constructor(command, message, options = {}) {
|
|
61
|
+
super(message, options);
|
|
62
|
+
this.name = 'SbxContractFailure';
|
|
63
|
+
this.command = command;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export class DockerSandboxStopVerificationFailure extends SbxContractFailure {
|
|
67
|
+
constructor(message) {
|
|
68
|
+
super('stop', message);
|
|
69
|
+
this.name = 'DockerSandboxStopVerificationFailure';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export class DockerSandboxCollisionFailure extends Error {
|
|
73
|
+
sandboxName;
|
|
74
|
+
constructor(sandboxName, message) {
|
|
75
|
+
super(message);
|
|
76
|
+
this.name = 'DockerSandboxCollisionFailure';
|
|
77
|
+
this.sandboxName = sandboxName;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const validateCommandLimit = (name, value, maximum) => {
|
|
81
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
|
|
82
|
+
throw new Error(`${name} must be between 1 and ${maximum}`);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
export const createSbxCommandRunner = (dependencies = {}) => {
|
|
86
|
+
const executable = dependencies.executable ?? 'sbx';
|
|
87
|
+
const spawnCommand = dependencies.spawn ?? spawn;
|
|
88
|
+
return {
|
|
89
|
+
run: (args, options = {}) => new Promise((resolve, reject) => {
|
|
90
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
91
|
+
const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES;
|
|
92
|
+
validateCommandLimit('sbx timeoutMs', timeoutMs, 3_600_000);
|
|
93
|
+
validateCommandLimit('sbx maxOutputBytes', maxOutputBytes, 128 * 1024 * 1024);
|
|
94
|
+
if (options.signal?.aborted) {
|
|
95
|
+
reject(new SbxCommandFailure('cancelled', args, 'sbx command was cancelled before it started'));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
let child;
|
|
99
|
+
try {
|
|
100
|
+
child = spawnCommand(executable, args, {
|
|
101
|
+
env: process.env,
|
|
102
|
+
shell: false,
|
|
103
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
104
|
+
windowsHide: true,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
const kind = error.code === 'ENOENT' ? 'missing_cli' : 'spawn_failed';
|
|
109
|
+
reject(new SbxCommandFailure(kind, args, `Unable to start ${executable}`, { cause: error }));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const stdout = [];
|
|
113
|
+
const stderr = [];
|
|
114
|
+
let outputBytes = 0;
|
|
115
|
+
let settled = false;
|
|
116
|
+
const cleanup = () => {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
119
|
+
};
|
|
120
|
+
const fail = (kind, message, cause) => {
|
|
121
|
+
if (settled) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
settled = true;
|
|
125
|
+
cleanup();
|
|
126
|
+
child.kill('SIGKILL');
|
|
127
|
+
reject(new SbxCommandFailure(kind, args, message, cause === undefined ? {} : { cause }));
|
|
128
|
+
};
|
|
129
|
+
const collect = (destination, rawChunk) => {
|
|
130
|
+
const chunk = Buffer.from(rawChunk);
|
|
131
|
+
outputBytes += chunk.length;
|
|
132
|
+
if (outputBytes > maxOutputBytes) {
|
|
133
|
+
fail('output_limit', `sbx output exceeded ${maxOutputBytes} bytes`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
destination.push(chunk);
|
|
137
|
+
};
|
|
138
|
+
const onAbort = () => fail('cancelled', 'sbx command was cancelled');
|
|
139
|
+
const timer = setTimeout(() => fail('timeout', `sbx command exceeded ${timeoutMs}ms`), timeoutMs);
|
|
140
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
141
|
+
if (options.signal?.aborted) {
|
|
142
|
+
onAbort();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
child.stdout.on('data', chunk => collect(stdout, chunk));
|
|
146
|
+
child.stderr.on('data', chunk => collect(stderr, chunk));
|
|
147
|
+
child.once('error', error => {
|
|
148
|
+
const kind = error.code === 'ENOENT' ? 'missing_cli' : 'spawn_failed';
|
|
149
|
+
fail(kind, `Unable to run ${executable}`, error);
|
|
150
|
+
});
|
|
151
|
+
child.stdin.once('error', error => {
|
|
152
|
+
if (settled) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
fail('spawn_failed', `Unable to write stdin to ${executable}`, error);
|
|
156
|
+
});
|
|
157
|
+
child.once('close', code => {
|
|
158
|
+
if (settled) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
settled = true;
|
|
162
|
+
cleanup();
|
|
163
|
+
resolve({
|
|
164
|
+
args: [...args],
|
|
165
|
+
exitCode: code ?? 1,
|
|
166
|
+
stdout: Buffer.concat(stdout).toString('utf8'),
|
|
167
|
+
stderr: Buffer.concat(stderr).toString('utf8'),
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
try {
|
|
171
|
+
if (options.stdin === undefined) {
|
|
172
|
+
child.stdin.end();
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
child.stdin.end(options.stdin);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
fail('spawn_failed', `Unable to write stdin to ${executable}`, error);
|
|
180
|
+
}
|
|
181
|
+
}),
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
const asRecord = (value) => (typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
185
|
+
? value
|
|
186
|
+
: null);
|
|
187
|
+
const parseJsonObject = (command, output) => {
|
|
188
|
+
let parsed;
|
|
189
|
+
try {
|
|
190
|
+
parsed = JSON.parse(output);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
throw new SbxContractFailure(command, `sbx ${command} returned invalid JSON`, { cause: error });
|
|
194
|
+
}
|
|
195
|
+
const record = asRecord(parsed);
|
|
196
|
+
if (!record) {
|
|
197
|
+
throw new SbxContractFailure(command, `sbx ${command} returned an unrecognized JSON document`);
|
|
198
|
+
}
|
|
199
|
+
return record;
|
|
200
|
+
};
|
|
201
|
+
export const parseQualifiedSbxVersion = (output) => {
|
|
202
|
+
const match = /^sbx version: v(\d+\.\d+\.\d+) ([0-9a-f]{40})\r?\n?$/.exec(output);
|
|
203
|
+
if (!match) {
|
|
204
|
+
throw new SbxContractFailure('version', 'sbx version output did not match the qualified contract');
|
|
205
|
+
}
|
|
206
|
+
if (match[1] !== QUALIFIED_SBX_VERSION) {
|
|
207
|
+
throw new SbxContractFailure('version', `Unsupported sbx version ${match[1]}; Archon requires exactly ${QUALIFIED_SBX_VERSION}`);
|
|
208
|
+
}
|
|
209
|
+
if (match[2] !== QUALIFIED_SBX_BUILD) {
|
|
210
|
+
throw new SbxContractFailure('version', `Unsupported sbx build ${match[2]}; Archon requires exactly ${QUALIFIED_SBX_BUILD}`);
|
|
211
|
+
}
|
|
212
|
+
return match[1];
|
|
213
|
+
};
|
|
214
|
+
export const parseSbxDiagnostics = (output) => {
|
|
215
|
+
const record = parseJsonObject('diagnose', output);
|
|
216
|
+
if (record.version !== '1.0' || !Array.isArray(record.checks)) {
|
|
217
|
+
throw new SbxContractFailure('diagnose', 'sbx diagnose returned an unsupported schema');
|
|
218
|
+
}
|
|
219
|
+
return record.checks.map((value, index) => {
|
|
220
|
+
const check = asRecord(value);
|
|
221
|
+
if (!check
|
|
222
|
+
|| typeof check.name !== 'string'
|
|
223
|
+
|| typeof check.status !== 'string'
|
|
224
|
+
|| !['fail', 'pass', 'skip', 'warn'].includes(check.status)
|
|
225
|
+
|| typeof check.message !== 'string'
|
|
226
|
+
|| typeof check.hint !== 'string') {
|
|
227
|
+
throw new SbxContractFailure('diagnose', `sbx diagnose check ${index} was unrecognized`);
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
name: check.name,
|
|
231
|
+
status: check.status,
|
|
232
|
+
message: check.message,
|
|
233
|
+
hint: check.hint,
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
const diagnosticObservation = (checks, code, names, fallback) => {
|
|
238
|
+
const check = checks.find(candidate => names.includes(candidate.name.toLowerCase()));
|
|
239
|
+
if (!check) {
|
|
240
|
+
return { code, state: 'unknown', summary: fallback, remediation: 'Run sbx diagnose and review this check.' };
|
|
241
|
+
}
|
|
242
|
+
const state = check.status === 'pass' ? 'ready' : check.status === 'skip' ? 'unknown' : 'not_ready';
|
|
243
|
+
return {
|
|
244
|
+
code,
|
|
245
|
+
state,
|
|
246
|
+
summary: check.message.replace(/[\u0000-\u001f\u007f]/g, ' ').replace(/\s+/g, ' ').trim(),
|
|
247
|
+
remediation: state === 'ready'
|
|
248
|
+
? null
|
|
249
|
+
: check.hint.replace(/[\u0000-\u001f\u007f]/g, ' ').replace(/\s+/g, ' ').trim() || 'Review sbx diagnostics.',
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
const nodeErrorCode = (error) => {
|
|
253
|
+
if (error instanceof Error && 'code' in error && typeof error.code === 'string') {
|
|
254
|
+
return error.code;
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
};
|
|
258
|
+
const readMacOsVersion = async () => {
|
|
259
|
+
try {
|
|
260
|
+
const result = await execFile('sw_vers', ['-productVersion'], { timeout: 5_000, maxBuffer: 4096 });
|
|
261
|
+
const version = result.stdout.trim();
|
|
262
|
+
return version.length > 0 ? version : null;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const macOsMajorVersion = (version) => {
|
|
269
|
+
const match = /^(\d+)(?:\.|$)/.exec(version);
|
|
270
|
+
return match ? Number(match[1]) : null;
|
|
271
|
+
};
|
|
272
|
+
const probeDockerSandboxPlatformReadiness = async (platform, architecture, options) => {
|
|
273
|
+
if (!isQualifiedDockerSandboxHost(platform, architecture)) {
|
|
274
|
+
return {
|
|
275
|
+
code: 'platform',
|
|
276
|
+
state: 'unsupported',
|
|
277
|
+
summary: `${platform}/${architecture} is not qualified.`,
|
|
278
|
+
remediation: 'Use Linux x64 or macOS Sonoma 14+ on Apple silicon for Docker Sandboxes.',
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
if (platform !== 'darwin') {
|
|
282
|
+
return {
|
|
283
|
+
code: 'platform',
|
|
284
|
+
state: 'ready',
|
|
285
|
+
summary: `${platform}/${architecture} is supported.`,
|
|
286
|
+
remediation: null,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const version = options.macOsVersion !== undefined
|
|
290
|
+
? options.macOsVersion
|
|
291
|
+
: await (options.macOsVersionProbe ?? readMacOsVersion)();
|
|
292
|
+
const majorVersion = version ? macOsMajorVersion(version) : null;
|
|
293
|
+
if (majorVersion === null) {
|
|
294
|
+
return {
|
|
295
|
+
code: 'platform',
|
|
296
|
+
state: 'unknown',
|
|
297
|
+
summary: 'The macOS version could not be verified.',
|
|
298
|
+
remediation: 'Run macOS Sonoma 14 or later on Apple silicon, then recheck.',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (majorVersion < MINIMUM_MACOS_MAJOR) {
|
|
302
|
+
return {
|
|
303
|
+
code: 'platform',
|
|
304
|
+
state: 'unsupported',
|
|
305
|
+
summary: `macOS ${version} is not qualified; Docker Sandboxes requires Sonoma 14 or later.`,
|
|
306
|
+
remediation: 'Upgrade to macOS Sonoma 14 or later on Apple silicon, then recheck.',
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
code: 'platform',
|
|
311
|
+
state: 'ready',
|
|
312
|
+
summary: `darwin/${architecture} is supported on macOS ${version}.`,
|
|
313
|
+
remediation: null,
|
|
314
|
+
};
|
|
315
|
+
};
|
|
316
|
+
export const probeLinuxKvmReadiness = async (platform, architecture, kvmAccess = access) => {
|
|
317
|
+
if (platform !== 'linux' || architecture !== 'x64') {
|
|
318
|
+
return {
|
|
319
|
+
code: 'virtualization',
|
|
320
|
+
state: 'unsupported',
|
|
321
|
+
summary: 'Virtualization probing is only qualified on Linux x64.',
|
|
322
|
+
remediation: 'Use a Linux x64 host supported by Docker Sandboxes.',
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
await kvmAccess(KVM_DEVICE_PATH, fsConstants.R_OK | fsConstants.W_OK);
|
|
327
|
+
return {
|
|
328
|
+
code: 'virtualization',
|
|
329
|
+
state: 'ready',
|
|
330
|
+
summary: 'KVM device /dev/kvm is available.',
|
|
331
|
+
remediation: null,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
const code = nodeErrorCode(error);
|
|
336
|
+
if (code === 'ENOENT') {
|
|
337
|
+
return {
|
|
338
|
+
code: 'virtualization',
|
|
339
|
+
state: 'not_ready',
|
|
340
|
+
summary: 'KVM device /dev/kvm was not found.',
|
|
341
|
+
remediation: 'Enable CPU virtualization and KVM on this host, then recheck.',
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (code === 'EACCES' || code === 'EPERM') {
|
|
345
|
+
return {
|
|
346
|
+
code: 'virtualization',
|
|
347
|
+
state: 'not_ready',
|
|
348
|
+
summary: 'KVM device /dev/kvm is not accessible to the Archon process.',
|
|
349
|
+
remediation: 'Grant the Archon user access to /dev/kvm, typically through the kvm group, then recheck.',
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
code: 'virtualization',
|
|
354
|
+
state: 'unknown',
|
|
355
|
+
summary: 'KVM availability could not be verified.',
|
|
356
|
+
remediation: 'Review host KVM diagnostics, then recheck.',
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
const probeDockerSandboxVirtualizationReadiness = async (platform, architecture, platformObservation, kvmAccess) => {
|
|
361
|
+
if (platform === 'linux') {
|
|
362
|
+
return probeLinuxKvmReadiness(platform, architecture, kvmAccess);
|
|
363
|
+
}
|
|
364
|
+
if (platform === 'darwin' && architecture === 'arm64') {
|
|
365
|
+
if (platformObservation.state === 'ready') {
|
|
366
|
+
return {
|
|
367
|
+
code: 'virtualization',
|
|
368
|
+
state: 'ready',
|
|
369
|
+
summary: 'Apple silicon virtualization is provided by the qualified macOS host.',
|
|
370
|
+
remediation: null,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
if (platformObservation.state === 'unsupported') {
|
|
374
|
+
return {
|
|
375
|
+
code: 'virtualization',
|
|
376
|
+
state: 'unsupported',
|
|
377
|
+
summary: 'macOS virtualization is unavailable because this host is not qualified.',
|
|
378
|
+
remediation: 'Use macOS Sonoma 14 or later on Apple silicon, then recheck.',
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
code: 'virtualization',
|
|
383
|
+
state: 'unknown',
|
|
384
|
+
summary: 'macOS virtualization could not be verified because the host platform is unknown.',
|
|
385
|
+
remediation: 'Run macOS Sonoma 14 or later on Apple silicon, then recheck.',
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
code: 'virtualization',
|
|
390
|
+
state: 'unsupported',
|
|
391
|
+
summary: 'Virtualization is not qualified on this host.',
|
|
392
|
+
remediation: 'Use Linux x64 or macOS Sonoma 14+ on Apple silicon for Docker Sandboxes.',
|
|
393
|
+
};
|
|
394
|
+
};
|
|
395
|
+
export const probeDockerSandboxReadiness = async (runner, options = {}) => {
|
|
396
|
+
const platform = options.platform ?? process.platform;
|
|
397
|
+
const architecture = options.architecture ?? process.arch;
|
|
398
|
+
const observations = [];
|
|
399
|
+
let providerVersion = null;
|
|
400
|
+
let versionResult;
|
|
401
|
+
try {
|
|
402
|
+
versionResult = await runner.run(['version'], options);
|
|
403
|
+
observations.push({ code: 'cli', state: 'ready', summary: 'sbx CLI is installed.', remediation: null });
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
if (error instanceof SbxCommandFailure && error.kind === 'missing_cli') {
|
|
407
|
+
return {
|
|
408
|
+
providerVersion: null,
|
|
409
|
+
observations: [
|
|
410
|
+
{ code: 'cli', state: 'not_ready', summary: 'sbx CLI is not installed.', remediation: 'Install the qualified sbx release from Docker.' },
|
|
411
|
+
...['version', 'platform', 'virtualization', 'daemon', 'docker_login', 'network_policy', 'opencode_template', 'claude_template']
|
|
412
|
+
.map(code => ({ code, state: 'unknown', summary: 'Not checked because sbx is unavailable.', remediation: null })),
|
|
413
|
+
],
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
418
|
+
try {
|
|
419
|
+
if (versionResult.exitCode !== 0) {
|
|
420
|
+
throw new SbxContractFailure('version', `sbx version exited with code ${versionResult.exitCode}`);
|
|
421
|
+
}
|
|
422
|
+
providerVersion = parseQualifiedSbxVersion(versionResult.stdout);
|
|
423
|
+
observations.push({ code: 'version', state: 'ready', summary: `sbx ${providerVersion} is qualified.`, remediation: null });
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
observations.push({
|
|
427
|
+
code: 'version',
|
|
428
|
+
state: 'unsupported',
|
|
429
|
+
summary: error instanceof Error ? error.message : 'sbx version is unsupported.',
|
|
430
|
+
remediation: `Install sbx ${QUALIFIED_SBX_VERSION}.`,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
const platformObservation = await probeDockerSandboxPlatformReadiness(platform, architecture, options);
|
|
434
|
+
observations.push(platformObservation);
|
|
435
|
+
observations.push(await probeDockerSandboxVirtualizationReadiness(platform, architecture, platformObservation, options.kvmAccess));
|
|
436
|
+
const diagnose = await runner.run(['diagnose', '--output', 'json'], options);
|
|
437
|
+
if (diagnose.exitCode !== 0 && diagnose.stdout.trim().length === 0) {
|
|
438
|
+
throw new SbxContractFailure('diagnose', 'sbx diagnose failed without structured output');
|
|
439
|
+
}
|
|
440
|
+
const checks = parseSbxDiagnostics(diagnose.stdout);
|
|
441
|
+
observations.push(diagnosticObservation(checks, 'daemon', ['daemon'], 'Daemon readiness was not reported.'));
|
|
442
|
+
observations.push(diagnosticObservation(checks, 'docker_login', ['authentication'], 'Docker login readiness was not reported.'));
|
|
443
|
+
const daemonReady = observations.some(observation => observation.code === 'daemon' && observation.state === 'ready');
|
|
444
|
+
const loginReady = observations.some(observation => observation.code === 'docker_login' && observation.state === 'ready');
|
|
445
|
+
if (options.includeDeferredObservations !== false && daemonReady && loginReady) {
|
|
446
|
+
const policy = await runner.run(['policy', 'ls', '--json'], options);
|
|
447
|
+
observations.push({
|
|
448
|
+
code: 'network_policy',
|
|
449
|
+
state: policy.exitCode === 0 ? 'unknown' : 'not_ready',
|
|
450
|
+
summary: policy.exitCode === 0
|
|
451
|
+
? 'Sandbox network policy output has not been qualified.'
|
|
452
|
+
: 'Sandbox network policy is not ready.',
|
|
453
|
+
remediation: policy.exitCode === 0
|
|
454
|
+
? 'Review sbx policy output before enabling this capability.'
|
|
455
|
+
: 'Initialize network policy interactively with sbx before retrying.',
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
observations.push({
|
|
460
|
+
code: 'network_policy',
|
|
461
|
+
state: 'unknown',
|
|
462
|
+
summary: options.includeDeferredObservations === false
|
|
463
|
+
? 'Network policy is deferred until environment preparation and validation.'
|
|
464
|
+
: 'Network policy was not queried because the runtime is not ready.',
|
|
465
|
+
remediation: null,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
for (const [code, agent] of [['opencode_template', 'OpenCode'], ['claude_template', 'Claude']]) {
|
|
469
|
+
observations.push({
|
|
470
|
+
code,
|
|
471
|
+
state: 'unknown',
|
|
472
|
+
summary: `${agent} template output has not been qualified.`,
|
|
473
|
+
remediation: 'Review qualified template output before enabling this capability.',
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return { providerVersion, observations };
|
|
477
|
+
};
|
|
478
|
+
const slug = (value, maximum) => {
|
|
479
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9.-]+/g, '-').replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, '');
|
|
480
|
+
return (normalized || 'target').slice(0, maximum).replace(/[^a-z0-9]+$/g, '') || 'target';
|
|
481
|
+
};
|
|
482
|
+
export const createDockerSandboxName = (identity) => {
|
|
483
|
+
if (!Number.isSafeInteger(identity.environmentRevision) || identity.environmentRevision < 1) {
|
|
484
|
+
throw new Error('Docker Sandbox environment revision must be a positive integer');
|
|
485
|
+
}
|
|
486
|
+
if (!/^[0-9a-f]{64}$/.test(identity.validationFingerprint)) {
|
|
487
|
+
throw new Error('Docker Sandbox validation fingerprint must be a SHA-256 digest');
|
|
488
|
+
}
|
|
489
|
+
const hash = createHash('sha256')
|
|
490
|
+
.update(`${identity.projectId}\0${identity.targetKey}\0${identity.environmentRevision}\0${identity.validationFingerprint}`, 'utf8')
|
|
491
|
+
.digest('hex')
|
|
492
|
+
.slice(0, SANDBOX_NAME_HASH_LENGTH);
|
|
493
|
+
const revision = String(identity.environmentRevision);
|
|
494
|
+
const fixedBytes = Buffer.byteLength(`archon---r${revision}-${hash}`, 'utf8');
|
|
495
|
+
const available = DOCKER_SANDBOX_MAX_NAME_BYTES - fixedBytes;
|
|
496
|
+
const projectLength = Math.max(4, Math.floor(available * 0.4));
|
|
497
|
+
const targetLength = Math.max(4, available - projectLength);
|
|
498
|
+
return `archon-${slug(identity.projectId, projectLength)}-${slug(identity.targetKey, targetLength)}-r${revision}-${hash}`;
|
|
499
|
+
};
|
|
500
|
+
const QUALIFIED_INSPECTION_FIELDS = new Set([
|
|
501
|
+
'agent',
|
|
502
|
+
'auth_mode',
|
|
503
|
+
'daemon_uptime',
|
|
504
|
+
'daemon_version',
|
|
505
|
+
'image',
|
|
506
|
+
'image_digest',
|
|
507
|
+
'kits',
|
|
508
|
+
'mcp_gateway',
|
|
509
|
+
'name',
|
|
510
|
+
'network',
|
|
511
|
+
'network_policy',
|
|
512
|
+
'proxy',
|
|
513
|
+
'secrets',
|
|
514
|
+
'sessions',
|
|
515
|
+
'state',
|
|
516
|
+
'uptime',
|
|
517
|
+
'workspace',
|
|
518
|
+
]);
|
|
519
|
+
const jsonValueKind = (value) => (value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value);
|
|
520
|
+
const describeInspectionShape = (record) => {
|
|
521
|
+
const fields = Object.keys(record).sort();
|
|
522
|
+
const description = fields.map(field => `${field}:${jsonValueKind(record[field])}`).join(', ');
|
|
523
|
+
return description.slice(0, 500);
|
|
524
|
+
};
|
|
525
|
+
export const parseDockerSandboxInspection = (output) => {
|
|
526
|
+
const record = parseJsonObject('inspect', output);
|
|
527
|
+
const fields = Object.keys(record).sort();
|
|
528
|
+
const workspace = record.workspace;
|
|
529
|
+
if (fields.some(field => !QUALIFIED_INSPECTION_FIELDS.has(field))
|
|
530
|
+
|| typeof record.name !== 'string'
|
|
531
|
+
|| record.name.length === 0
|
|
532
|
+
|| (record.agent !== 'opencode' && record.agent !== 'claude')
|
|
533
|
+
|| (record.state !== 'running' && record.state !== 'stopped')
|
|
534
|
+
|| typeof workspace !== 'string'
|
|
535
|
+
|| workspace.length === 0
|
|
536
|
+
|| typeof record.image !== 'string'
|
|
537
|
+
|| record.image.length === 0) {
|
|
538
|
+
throw new SbxContractFailure('inspect', `sbx inspect returned an unrecognized sandbox identity (fields: ${describeInspectionShape(record)})`);
|
|
539
|
+
}
|
|
540
|
+
return {
|
|
541
|
+
name: record.name,
|
|
542
|
+
agent: record.agent,
|
|
543
|
+
state: record.state,
|
|
544
|
+
workspacePath: workspace,
|
|
545
|
+
template: record.image,
|
|
546
|
+
};
|
|
547
|
+
};
|
|
548
|
+
const assertInspectionMatches = (expected, observed) => {
|
|
549
|
+
if (observed.name !== expected.name
|
|
550
|
+
|| observed.agent !== expected.agent
|
|
551
|
+
|| observed.workspacePath !== expected.workspacePath
|
|
552
|
+
|| observed.template !== expected.template) {
|
|
553
|
+
throw new DockerSandboxCollisionFailure(expected.name, `Sandbox ${expected.name} does not match Archon's persisted external identity`);
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
const exitCodeFailureMessage = (command, result) => {
|
|
557
|
+
const stderr = result.stderr.trim().slice(0, 500);
|
|
558
|
+
return `sbx ${command} exited with code ${result.exitCode}${stderr ? `: ${stderr}` : ''}`;
|
|
559
|
+
};
|
|
560
|
+
const requireSuccessfulCommand = (command, result) => {
|
|
561
|
+
if (result.exitCode !== 0) {
|
|
562
|
+
throw new SbxContractFailure(command, exitCodeFailureMessage(command, result));
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
const isQualifiedMissingSandboxResult = (result) => (result.args[0] === 'inspect'
|
|
566
|
+
&& typeof result.args[1] === 'string'
|
|
567
|
+
&& result.args[1].length > 0
|
|
568
|
+
&& result.exitCode === 1
|
|
569
|
+
&& result.stdout === ''
|
|
570
|
+
&& result.stderr === `ERROR: sandbox '${result.args[1]}' not found\n`);
|
|
571
|
+
export const createDockerSandboxAdapter = (runner, qualificationOptions = {}) => {
|
|
572
|
+
let qualification = null;
|
|
573
|
+
const waitForQualification = (operation, signal) => {
|
|
574
|
+
if (!signal) {
|
|
575
|
+
return operation;
|
|
576
|
+
}
|
|
577
|
+
if (signal.aborted) {
|
|
578
|
+
return Promise.reject(new SbxCommandFailure('cancelled', ['version'], 'sbx version qualification was cancelled'));
|
|
579
|
+
}
|
|
580
|
+
return new Promise((resolve, reject) => {
|
|
581
|
+
const onAbort = () => {
|
|
582
|
+
reject(new SbxCommandFailure('cancelled', ['version'], 'sbx version qualification was cancelled'));
|
|
583
|
+
};
|
|
584
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
585
|
+
operation.then(() => {
|
|
586
|
+
signal.removeEventListener('abort', onAbort);
|
|
587
|
+
resolve();
|
|
588
|
+
}, error => {
|
|
589
|
+
signal.removeEventListener('abort', onAbort);
|
|
590
|
+
reject(error);
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
};
|
|
594
|
+
const ensureQualified = (options) => {
|
|
595
|
+
const platform = qualificationOptions.platform ?? process.platform;
|
|
596
|
+
const architecture = qualificationOptions.architecture ?? process.arch;
|
|
597
|
+
if (!isQualifiedDockerSandboxHost(platform, architecture)) {
|
|
598
|
+
throw new SbxContractFailure('platform', `Unsupported sbx platform ${platform}/${architecture}; Archon requires linux/x64 or darwin/arm64`);
|
|
599
|
+
}
|
|
600
|
+
if (!qualification) {
|
|
601
|
+
const qualificationCommandOptions = {
|
|
602
|
+
timeoutMs: options.timeoutMs,
|
|
603
|
+
maxOutputBytes: options.maxOutputBytes,
|
|
604
|
+
stdin: options.stdin,
|
|
605
|
+
};
|
|
606
|
+
const attempt = runner.run(['version'], qualificationCommandOptions).then(result => {
|
|
607
|
+
requireSuccessfulCommand('version', result);
|
|
608
|
+
parseQualifiedSbxVersion(result.stdout);
|
|
609
|
+
});
|
|
610
|
+
qualification = attempt.catch(error => {
|
|
611
|
+
qualification = null;
|
|
612
|
+
throw error;
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
return waitForQualification(qualification, options.signal);
|
|
616
|
+
};
|
|
617
|
+
const inspect = async (name, options = {}) => {
|
|
618
|
+
await ensureQualified(options);
|
|
619
|
+
const result = await runner.run(['inspect', name, '--json'], options);
|
|
620
|
+
if (result.exitCode !== 0) {
|
|
621
|
+
if (isQualifiedMissingSandboxResult(result)) {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
throw new SbxContractFailure('inspect', exitCodeFailureMessage('inspect', result));
|
|
625
|
+
}
|
|
626
|
+
return parseDockerSandboxInspection(result.stdout);
|
|
627
|
+
};
|
|
628
|
+
const requireExpected = async (expected, options) => {
|
|
629
|
+
const observed = await inspect(expected.name, options);
|
|
630
|
+
if (!observed) {
|
|
631
|
+
throw new SbxContractFailure('inspect', `Sandbox ${expected.name} does not exist`);
|
|
632
|
+
}
|
|
633
|
+
assertInspectionMatches(expected, observed);
|
|
634
|
+
return observed;
|
|
635
|
+
};
|
|
636
|
+
const reconnect = async (expected, options = {}) => {
|
|
637
|
+
let observed = await requireExpected(expected, options);
|
|
638
|
+
if (observed.state === 'stopped') {
|
|
639
|
+
const result = await runner.run(['exec', observed.name, '--', 'true'], options);
|
|
640
|
+
requireSuccessfulCommand('exec', result);
|
|
641
|
+
observed = await requireExpected(expected, options);
|
|
642
|
+
}
|
|
643
|
+
return observed;
|
|
644
|
+
};
|
|
645
|
+
return {
|
|
646
|
+
create: async (request, options = {}) => {
|
|
647
|
+
const name = createDockerSandboxName(request.identity);
|
|
648
|
+
const existing = await inspect(name, options);
|
|
649
|
+
const expected = { name, agent: request.agent, workspacePath: request.workspacePath, template: request.template };
|
|
650
|
+
if (existing) {
|
|
651
|
+
assertInspectionMatches(expected, existing);
|
|
652
|
+
return existing.state === 'running' ? existing : reconnect(expected, options);
|
|
653
|
+
}
|
|
654
|
+
const result = await runner.run([
|
|
655
|
+
'create', '--quiet', '--name', name, '--template', request.template, request.agent, request.workspacePath,
|
|
656
|
+
], options);
|
|
657
|
+
requireSuccessfulCommand('create', result);
|
|
658
|
+
return requireExpected(expected, options);
|
|
659
|
+
},
|
|
660
|
+
inspect,
|
|
661
|
+
reconnect,
|
|
662
|
+
exec: async (expected, command, args, options = {}) => {
|
|
663
|
+
await requireExpected(expected, options);
|
|
664
|
+
const workdir = options.cwd === undefined ? [] : ['--workdir', options.cwd];
|
|
665
|
+
return runner.run(['exec', ...workdir, expected.name, '--', command, ...args], options);
|
|
666
|
+
},
|
|
667
|
+
stop: async (expected, options = {}) => {
|
|
668
|
+
let observed = await requireExpected(expected, options);
|
|
669
|
+
if (observed.state === 'running') {
|
|
670
|
+
requireSuccessfulCommand('stop', await runner.run(['stop', expected.name], options));
|
|
671
|
+
observed = await requireExpected(expected, options);
|
|
672
|
+
}
|
|
673
|
+
if (observed.state !== 'stopped') {
|
|
674
|
+
throw new DockerSandboxStopVerificationFailure(`Sandbox ${expected.name} did not reach the stopped state`);
|
|
675
|
+
}
|
|
676
|
+
},
|
|
677
|
+
remove: async (expected, options = {}) => {
|
|
678
|
+
await requireExpected(expected, options);
|
|
679
|
+
requireSuccessfulCommand('rm', await runner.run(['rm', '--force', expected.name], options));
|
|
680
|
+
},
|
|
681
|
+
};
|
|
682
|
+
};
|
|
683
|
+
export const createDockerSandboxExecutionEnvironmentLifecycle = (dependencies) => {
|
|
684
|
+
const inspect = async (environment, options = {}) => {
|
|
685
|
+
const expected = dependencies.resolveExpected(environment);
|
|
686
|
+
const observed = await dependencies.adapter.inspect(expected.name, options);
|
|
687
|
+
if (observed) {
|
|
688
|
+
assertInspectionMatches(expected, observed);
|
|
689
|
+
}
|
|
690
|
+
return observed;
|
|
691
|
+
};
|
|
692
|
+
const reconnect = async (environment, options = {}) => {
|
|
693
|
+
await dependencies.adapter.reconnect(dependencies.resolveExpected(environment), options);
|
|
694
|
+
return { ...environment, state: 'ready' };
|
|
695
|
+
};
|
|
696
|
+
return {
|
|
697
|
+
prepare: async (request) => {
|
|
698
|
+
requireExecutionCapabilities(DOCKER_SANDBOX_PROVIDER, dockerSandboxExecutionEnvironmentObservation, request.requiredCapabilities);
|
|
699
|
+
if (!request.persistent) {
|
|
700
|
+
throw new SbxContractFailure('create', 'Docker Sandbox lifecycle requires persistent environments');
|
|
701
|
+
}
|
|
702
|
+
const preparation = dependencies.resolvePreparation(request);
|
|
703
|
+
const sandbox = await dependencies.adapter.create(preparation.createRequest);
|
|
704
|
+
return {
|
|
705
|
+
id: preparation.id,
|
|
706
|
+
provider: DOCKER_SANDBOX_PROVIDER,
|
|
707
|
+
providerVersion: QUALIFIED_SBX_VERSION,
|
|
708
|
+
externalId: sandbox.name,
|
|
709
|
+
target: request.target,
|
|
710
|
+
state: 'ready',
|
|
711
|
+
};
|
|
712
|
+
},
|
|
713
|
+
validate: async (environment) => {
|
|
714
|
+
if (!await dependencies.validatePrepared(environment)) {
|
|
715
|
+
throw new SbxContractFailure('exec', `Sandbox ${environment.externalId ?? environment.id} did not pass validation`);
|
|
716
|
+
}
|
|
717
|
+
return { ...environment, state: 'ready' };
|
|
718
|
+
},
|
|
719
|
+
inspect,
|
|
720
|
+
reconnect,
|
|
721
|
+
stop: async (environment) => dependencies.adapter.stop(dependencies.resolveExpected(environment)),
|
|
722
|
+
destroy: async (environment) => dependencies.adapter.remove(dependencies.resolveExpected(environment)),
|
|
723
|
+
};
|
|
724
|
+
};
|
|
725
|
+
//# sourceMappingURL=dockerSandboxExecutionEnvironment.js.map
|