bazilion 0.16.0 → 0.17.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/cli.js +399 -83
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +1840 -132
- package/dist/daemon.js.map +1 -1
- package/dist/migrations/0001_init.sql +62 -0
- package/dist/web/client/assets/ChatPane-BI3QVX2A.js +76 -0
- package/dist/web/client/assets/{ConfirmDialog-MicTTwdy.js → ConfirmDialog-Qam6Ut08.js} +1 -1
- package/dist/web/client/assets/{FieldRow-DJ2Gx4Du.js → FieldRow-swJ5qn2h.js} +1 -1
- package/dist/web/client/assets/ProductionTeamPolicyEditor-LGw1UuF-.js +7 -0
- package/dist/web/client/assets/ProfileCommunicationEditor-raoau_YB.js +1 -0
- package/dist/web/client/assets/{ResultSourceConversation-CtzJ8QA-.js → ResultSourceConversation-BEwkhFog.js} +1 -1
- package/dist/web/client/assets/TeamTabs-BDdTLGIf.js +1 -0
- package/dist/web/client/assets/TemplatesTabs-BX5hhEJ7.js +1 -0
- package/dist/web/client/assets/{UnsavedChangesGuard-BCsLw-mU.js → UnsavedChangesGuard-CVJtmR1D.js} +1 -1
- package/dist/web/client/assets/{_id-BEDmIXVf.js → _id-BQPOE3gj.js} +1 -1
- package/dist/web/client/assets/{_id-BFM_tn_s.js → _id-BkYrUP0R.js} +1 -1
- package/dist/web/client/assets/{_id-B5Ov7cdO.js → _id-CriixlZD.js} +1 -1
- package/dist/web/client/assets/_id-DHqq2W34.js +1 -0
- package/dist/web/client/assets/{_id-DxopIW-y.js → _id-DmEVZIlx.js} +1 -1
- package/dist/web/client/assets/activity-Bzr8R18n.js +1 -0
- package/dist/web/client/assets/{agents-CMPOFcs-.js → agents-13oafeOA.js} +1 -1
- package/dist/web/client/assets/{agents-O4soKZND.js → agents-DF4NALmJ.js} +1 -1
- package/dist/web/client/assets/{approvals-CwDu_lrA.js → approvals-DsG0Ijls.js} +1 -1
- package/dist/web/client/assets/{attention-BY8MMuM_.js → attention-DG7K25mz.js} +1 -1
- package/dist/web/client/assets/chevron-right-DFqFHtLy.js +1 -0
- package/dist/web/client/assets/config-DAaDbFhS.js +3 -0
- package/dist/web/client/assets/createServerFn-Dzfjt7mA.js +3 -0
- package/dist/web/client/assets/git-review-presentation-CzHQulDk.js +1 -0
- package/dist/web/client/assets/{inbox-CWXql0K3.js → inbox-C9e7QjsX.js} +1 -1
- package/dist/web/client/assets/index-Dsstm2hf.js +50 -0
- package/dist/web/client/assets/{learning-DAm4dyyc.js → learning-23arTd21.js} +1 -1
- package/dist/web/client/assets/login-BIXnBsuj.js +1 -0
- package/dist/web/client/assets/{mcp-loLB0wuh.js → mcp-Cdx9qAl8.js} +1 -1
- package/dist/web/client/assets/{members-BJ0ikkTl.js → members-fotOz4rR.js} +1 -1
- package/dist/web/client/assets/{memory-Drn9Gv53.js → memory-C7UznVot.js} +1 -1
- package/dist/web/client/assets/policy-CactLW1X.js +1 -0
- package/dist/web/client/assets/{results-9exU6f7m.js → results-DqEuYnQH.js} +1 -1
- package/dist/web/client/assets/review-CnBSYclb.js +1 -0
- package/dist/web/client/assets/routes-COxMuwVz.js +1 -0
- package/dist/web/client/assets/{services-BLso1BhA.js → services-xEANwZAu.js} +1 -1
- package/dist/web/client/assets/{skills-BodqX3OI.js → skills-CamV6wYM.js} +1 -1
- package/dist/web/client/assets/{styles-d1yjBpud.css → styles-C6br2xxl.css} +1 -1
- package/dist/web/client/assets/{teams-ByQedXZi.js → teams-B12dMSjW.js} +1 -1
- package/dist/web/client/assets/{teams-SYsF1bOR.js → teams-CbH43zSG.js} +1 -1
- package/dist/web/client/assets/{telegram-DC9PLFbB.js → telegram-muIql4u-.js} +1 -1
- package/dist/web/client/assets/{tokens-DGy8Ijpo.js → tokens-DGFmApdF.js} +1 -1
- package/dist/web/client/assets/{triggers-BXpVmHDG.js → triggers-D8n3sCBW.js} +1 -1
- package/dist/web/client/assets/welcome-CMHWRZhs.js +1 -0
- package/dist/web/client/assets/x-BGCHwZOH.js +1 -0
- package/dist/web/server/assets/{ChatPane-CS5oI7ef.js → ChatPane-DybH39WK.js} +301 -14
- package/dist/web/server/assets/{ResultSourceConversation-BpVWBRYq.js → ResultSourceConversation-BmXTcEOA.js} +1 -1
- package/dist/web/server/assets/{TeamTabs-TCKjCvMA.js → TeamTabs-DQXh-3ij.js} +4 -0
- package/dist/web/server/assets/{_id-5pgKoid8.js → _id-BN1BPEDI.js} +1 -1
- package/dist/web/server/assets/{_id-DFHB9JFQ.js → _id-BYKpT2Tu.js} +1 -1
- package/dist/web/server/assets/{_id-B_-CDHOi2.js → _id-BqWTB8Wl.js} +1 -1
- package/dist/web/server/assets/{_id-BG2nkGxa.js → _id-CPfILjMa.js} +1 -1
- package/dist/web/server/assets/{_id-BnqodC1c2.js → _id-C_2er0_G.js} +3 -3
- package/dist/web/server/assets/{_id-DtIYuJJX.js → _id-DDyA9CGX.js} +1 -1
- package/dist/web/server/assets/{_id-DrB9uR1b.js → _id-DSPFJMzf.js} +1 -1
- package/dist/web/server/assets/{_id-DwZXhLlL.js → _id-DtJn-yzu.js} +2 -2
- package/dist/web/server/assets/{_id-CeyHEsCZ.js → _id-Dw3gKOGv.js} +1 -1
- package/dist/web/server/assets/{_id-DOnInzLb.js → _id-XkxlX036.js} +1 -1
- package/dist/web/server/assets/{_tanstack-start-manifest_v-D7gKv6m6.js → _tanstack-start-manifest_v-vOXRfKJD.js} +91 -78
- package/dist/web/server/assets/{activity-C88qrNqv.js → activity-BX1jZBPQ.js} +1 -1
- package/dist/web/server/assets/{activity-5fWKPDxj.js → activity-bozPMU2v.js} +2 -2
- package/dist/web/server/assets/{activity-0VUkStOu.js → activity-pYQPT3QT.js} +1 -1
- package/dist/web/server/assets/{agents-CSXLaM2W.js → agents-BKBfW700.js} +1 -1
- package/dist/web/server/assets/{agents-C86K6HvS.js → agents-CVCfxnE2.js} +1 -1
- package/dist/web/server/assets/{approvals-C3MysI6l.js → approvals-C3ya6B6U.js} +1 -1
- package/dist/web/server/assets/{attention-DLeLCKah.js → attention-l28hGCHS.js} +1 -1
- package/dist/web/server/assets/{auth-CDZp9aEk.js → auth-AvDFNeOv.js} +1 -1
- package/dist/web/server/assets/{config-BVTHvljR.js → config-BWKn4XII.js} +1 -1
- package/dist/web/server/assets/{daemon-client-xWyYKAcx.js → daemon-client-Cgx-YW4l.js} +30 -0
- package/dist/web/server/assets/git-review-BB3M9nYg.js +26 -0
- package/dist/web/server/assets/git-review-f_uta4Mx.js +179 -0
- package/dist/web/server/assets/git-review-presentation-TJiHJhen.js +81 -0
- package/dist/web/server/assets/{inbox-PQTrjtOw.js → inbox-BBZiOOQs.js} +1 -1
- package/dist/web/server/assets/{learning-K0cmDsZR.js → learning-CheVay1i.js} +1 -1
- package/dist/web/server/assets/{mcp-Rtraochg.js → mcp-DCBDmNtQ.js} +1 -1
- package/dist/web/server/assets/{members-grAKFWgv.js → members-C2He8mrU.js} +1 -1
- package/dist/web/server/assets/{members-CD3GiTfF.js → members-C5FQruRZ.js} +1 -1
- package/dist/web/server/assets/{members-cc5SeEaW.js → members-C_2xB9pr.js} +2 -2
- package/dist/web/server/assets/{memory-CVqnyUaM.js → memory-4tI-UbOf.js} +1 -1
- package/dist/web/server/assets/{memory-C5i_GxlY.js → memory-BTcmf0sD.js} +2 -2
- package/dist/web/server/assets/{memory-egFpBD1g.js → memory-rmiFNj7T.js} +1 -1
- package/dist/web/server/assets/{policy-Dgjb0_gK.js → policy-BGILKGHz.js} +2 -2
- package/dist/web/server/assets/{policy-bLKwvnja.js → policy-C3D2Q1EA.js} +1 -1
- package/dist/web/server/assets/{policy-ooSBgiDS.js → policy-DS7LGe--.js} +1 -1
- package/dist/web/server/assets/{results-DXdYtHaq.js → results-C6H42tjq.js} +1 -1
- package/dist/web/server/assets/{results-CfNsFncJ.js → results-Cgx7SrFt.js} +3 -3
- package/dist/web/server/assets/{results-N-kP4iFT.js → results-_6K_zXQS.js} +1 -1
- package/dist/web/server/assets/review-DHhiL-JQ.js +393 -0
- package/dist/web/server/assets/review-DSTMCwP4.js +14 -0
- package/dist/web/server/assets/{router-B6g_s7W3.js → router-ElARvZzI.js} +34 -27
- package/dist/web/server/assets/{routes-pcRy_Sf8.js → routes-B6x3pEW8.js} +1 -1
- package/dist/web/server/assets/{routes-BqSaZvlu.js → routes-BgMvLvS-.js} +1 -1
- package/dist/web/server/assets/{routes-Dsh8F_7i.js → routes-DsUzzxk4.js} +2 -2
- package/dist/web/server/assets/{services-DlZA8fRL.js → services-yHZroxsi.js} +1 -1
- package/dist/web/server/assets/{skills-i2duvgW-.js → skills--bIKID1Q.js} +1 -1
- package/dist/web/server/assets/{teams-B1AI3NfN.js → teams--a956vWL.js} +1 -1
- package/dist/web/server/assets/{teams-6RYIj1Xm.js → teams-BbRfuVXY.js} +1 -1
- package/dist/web/server/assets/{telegram-C4q0YTVn.js → telegram-4kMxRtVZ.js} +1 -1
- package/dist/web/server/assets/{tokens-RFquA2Bx.js → tokens-BYwTN9JZ.js} +1 -1
- package/dist/web/server/assets/{triggers-DVD-iE-N.js → triggers-BM6mzLuH.js} +1 -1
- package/dist/web/server/server.js +56 -28
- package/dist/worker.js +292 -75
- package/dist/worker.js.map +1 -1
- package/package.json +3 -3
- package/dist/web/client/assets/ChatPane-CQZzxOir.js +0 -76
- package/dist/web/client/assets/ProductionTeamPolicyEditor-DqUnN_ce.js +0 -7
- package/dist/web/client/assets/ProfileCommunicationEditor-DArXub0b.js +0 -1
- package/dist/web/client/assets/TeamTabs-DbIk5vvT.js +0 -1
- package/dist/web/client/assets/TemplatesTabs-CatJIjGi.js +0 -1
- package/dist/web/client/assets/_id-CrnEVcw8.js +0 -1
- package/dist/web/client/assets/activity-CrG8Bg60.js +0 -1
- package/dist/web/client/assets/chevron-right-Cf6vKeLq.js +0 -1
- package/dist/web/client/assets/config-Cn4iBJFz.js +0 -3
- package/dist/web/client/assets/index-Bq2zrG2_.js +0 -52
- package/dist/web/client/assets/login-BsABI8Ux.js +0 -1
- package/dist/web/client/assets/policy-pQadZyk0.js +0 -1
- package/dist/web/client/assets/routes-jw1PuqiR.js +0 -1
- package/dist/web/client/assets/welcome-BKsnsCxJ.js +0 -1
- package/dist/web/client/assets/x-BK4xNCzS.js +0 -1
package/dist/daemon.js
CHANGED
|
@@ -24915,14 +24915,16 @@ function safeReaddir(path) {
|
|
|
24915
24915
|
import { chmodSync, existsSync as existsSync17, mkdirSync as mkdirSync10, writeFileSync as writeFileSync14 } from "fs";
|
|
24916
24916
|
|
|
24917
24917
|
// ../daemon/src/core/repos/coding-commands.ts
|
|
24918
|
+
var CODING_COMMAND_TTL_MS = 7 * 864e5;
|
|
24919
|
+
var CODING_COMMAND_MAX_PER_TEAM = 200;
|
|
24918
24920
|
function pruneCodingCommands(db, teamId) {
|
|
24919
24921
|
db.raw.run(
|
|
24920
24922
|
"DELETE FROM coding_commands WHERE team_id = ? AND state != 'running' AND created_at < ?",
|
|
24921
|
-
[teamId, Date.now() -
|
|
24923
|
+
[teamId, Date.now() - CODING_COMMAND_TTL_MS]
|
|
24922
24924
|
);
|
|
24923
24925
|
db.raw.run(
|
|
24924
|
-
"DELETE FROM coding_commands WHERE id IN (SELECT id FROM coding_commands WHERE team_id = ? AND state != 'running' ORDER BY created_at DESC, id DESC LIMIT -1 OFFSET
|
|
24925
|
-
[teamId]
|
|
24926
|
+
"DELETE FROM coding_commands WHERE id IN (SELECT id FROM coding_commands WHERE team_id = ? AND state != 'running' ORDER BY created_at DESC, id DESC LIMIT -1 OFFSET ?)",
|
|
24927
|
+
[teamId, CODING_COMMAND_MAX_PER_TEAM]
|
|
24926
24928
|
);
|
|
24927
24929
|
}
|
|
24928
24930
|
function saveCodingCommand(db, receipt) {
|
|
@@ -28690,6 +28692,41 @@ function qmdBackend(root, options = {}) {
|
|
|
28690
28692
|
};
|
|
28691
28693
|
}
|
|
28692
28694
|
|
|
28695
|
+
// ../daemon/src/lib/coding-environment/receipt-reference.ts
|
|
28696
|
+
function codingLogReference(content) {
|
|
28697
|
+
const parsed = objectFrom(textContent(content));
|
|
28698
|
+
if (!parsed) return null;
|
|
28699
|
+
return referenceFrom(parsed) ?? referenceFrom(objectFrom(parsed.receipt));
|
|
28700
|
+
}
|
|
28701
|
+
function referenceFrom(value) {
|
|
28702
|
+
if (!value) return null;
|
|
28703
|
+
const commandId = value.id;
|
|
28704
|
+
const teamId = value.teamId;
|
|
28705
|
+
if (typeof commandId !== "string" || commandId.length === 0) return null;
|
|
28706
|
+
if (typeof teamId !== "string" || teamId.length === 0) return null;
|
|
28707
|
+
return { commandId, teamId };
|
|
28708
|
+
}
|
|
28709
|
+
function textContent(content) {
|
|
28710
|
+
if (typeof content === "string") return content;
|
|
28711
|
+
if (!Array.isArray(content)) return "";
|
|
28712
|
+
let out = "";
|
|
28713
|
+
for (const block of content) {
|
|
28714
|
+
if (block && block.type === "text" && typeof block.text === "string") out += block.text;
|
|
28715
|
+
}
|
|
28716
|
+
return out;
|
|
28717
|
+
}
|
|
28718
|
+
function objectFrom(value) {
|
|
28719
|
+
if (typeof value === "string") {
|
|
28720
|
+
try {
|
|
28721
|
+
return objectFrom(JSON.parse(value));
|
|
28722
|
+
} catch {
|
|
28723
|
+
return null;
|
|
28724
|
+
}
|
|
28725
|
+
}
|
|
28726
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
28727
|
+
return value;
|
|
28728
|
+
}
|
|
28729
|
+
|
|
28693
28730
|
// ../daemon/src/runtime/pi/events.ts
|
|
28694
28731
|
function extractAssistantText(m2) {
|
|
28695
28732
|
let out = "";
|
|
@@ -28759,14 +28796,22 @@ function piMessagesToProviderView(messages, questionResultVisible) {
|
|
|
28759
28796
|
}
|
|
28760
28797
|
case "toolResult": {
|
|
28761
28798
|
const tr = m2;
|
|
28762
|
-
if ([
|
|
28763
|
-
|
|
28764
|
-
|
|
28799
|
+
if ([
|
|
28800
|
+
"repository_context",
|
|
28801
|
+
"coding_environment",
|
|
28802
|
+
"coding_command",
|
|
28803
|
+
"coding_receipt",
|
|
28804
|
+
"coding_log",
|
|
28805
|
+
// BAZ-042: a snapshot result names withheld paths, so history must not render it.
|
|
28806
|
+
"source_snapshot"
|
|
28807
|
+
].includes(tr.toolName ?? "")) {
|
|
28808
|
+
const codingLog = codingLogReference(tr.content);
|
|
28765
28809
|
out.push({
|
|
28766
28810
|
role: "tool",
|
|
28767
28811
|
toolCallId: tr.toolCallId,
|
|
28768
28812
|
toolName: tr.toolName,
|
|
28769
|
-
content: "Coding context or command evidence retained privately. Captured transport output follows its communication approval; ask the Agent for the relevant result."
|
|
28813
|
+
content: "Coding context or command evidence retained privately. Captured transport output follows its communication approval; ask the Agent for the relevant result.",
|
|
28814
|
+
...codingLog ? { codingLog } : {}
|
|
28770
28815
|
});
|
|
28771
28816
|
break;
|
|
28772
28817
|
}
|
|
@@ -28803,14 +28848,10 @@ function extractResultReference(value) {
|
|
|
28803
28848
|
return typeof id === "string" && /^[a-f0-9-]{36}$/.test(id) ? { resultId: id } : void 0;
|
|
28804
28849
|
}
|
|
28805
28850
|
|
|
28806
|
-
// ../daemon/src/runtime/pi/coding.ts
|
|
28807
|
-
import {
|
|
28808
|
-
createLocalBashOperations
|
|
28809
|
-
} from "@earendil-works/pi-coding-agent";
|
|
28810
|
-
import { Type } from "typebox";
|
|
28811
|
-
|
|
28812
28851
|
// ../daemon/src/lib/coding-environment/diagnostics.ts
|
|
28813
28852
|
var CODING_OUTPUT_BYTES = 64 * 1024;
|
|
28853
|
+
var CODING_RETAINED_BYTES = 2 * 1024 * 1024;
|
|
28854
|
+
var CODING_LIVE_BYTES = 8 * 1024;
|
|
28814
28855
|
function diagnosticTail(value, limit) {
|
|
28815
28856
|
const bytes = Buffer.from(value);
|
|
28816
28857
|
if (bytes.length <= limit) return { text: value, truncated: false };
|
|
@@ -28822,28 +28863,47 @@ var CodingDiagnostics = class {
|
|
|
28822
28863
|
decoder = new TextDecoder();
|
|
28823
28864
|
secrets;
|
|
28824
28865
|
lookbehind;
|
|
28866
|
+
retainBytes;
|
|
28825
28867
|
pending = "";
|
|
28826
28868
|
text = "";
|
|
28827
28869
|
truncated = false;
|
|
28870
|
+
observedBytes = 0;
|
|
28871
|
+
redactions = 0;
|
|
28828
28872
|
finished = false;
|
|
28829
|
-
constructor(secrets) {
|
|
28873
|
+
constructor(secrets, retainBytes = CODING_OUTPUT_BYTES) {
|
|
28830
28874
|
this.secrets = [...new Set(secrets.filter(Boolean))].sort((a, b2) => b2.length - a.length);
|
|
28831
28875
|
this.lookbehind = Math.max(1, ...this.secrets.map((secret) => secret.length)) - 1;
|
|
28876
|
+
this.retainBytes = retainBytes;
|
|
28832
28877
|
}
|
|
28833
28878
|
append(bytes) {
|
|
28834
28879
|
if (this.finished) throw new Error("Coding diagnostics already finalized");
|
|
28880
|
+
this.observedBytes += bytes.length;
|
|
28835
28881
|
for (let offset = 0; offset < bytes.length; offset += 8192) {
|
|
28836
28882
|
this.pending += this.decoder.decode(bytes.subarray(offset, offset + 8192), { stream: true });
|
|
28837
28883
|
this.flush(false);
|
|
28838
28884
|
}
|
|
28839
28885
|
}
|
|
28886
|
+
/**
|
|
28887
|
+
* Redacted cumulative tail for a live update. Unlike `finish()` this does not seal
|
|
28888
|
+
* the diagnostics: more output may follow. It deliberately excludes the trailing
|
|
28889
|
+
* lookbehind bytes so a credential split across chunks cannot leak before the next
|
|
28890
|
+
* chunk resolves it.
|
|
28891
|
+
*/
|
|
28892
|
+
preview(limit = CODING_LIVE_BYTES) {
|
|
28893
|
+
return diagnosticTail(this.text, limit);
|
|
28894
|
+
}
|
|
28840
28895
|
finish() {
|
|
28841
28896
|
if (!this.finished) {
|
|
28842
28897
|
this.pending += this.decoder.decode();
|
|
28843
28898
|
this.flush(true);
|
|
28844
28899
|
this.finished = true;
|
|
28845
28900
|
}
|
|
28846
|
-
return {
|
|
28901
|
+
return {
|
|
28902
|
+
diagnostic: this.text,
|
|
28903
|
+
truncated: this.truncated,
|
|
28904
|
+
observedBytes: this.observedBytes,
|
|
28905
|
+
redacted: this.redactions > 0
|
|
28906
|
+
};
|
|
28847
28907
|
}
|
|
28848
28908
|
flush(final) {
|
|
28849
28909
|
let end = final ? this.pending.length : Math.max(0, this.pending.length - this.lookbehind);
|
|
@@ -28855,6 +28915,7 @@ var CodingDiagnostics = class {
|
|
|
28855
28915
|
const secret = this.secrets.find((value) => this.pending.startsWith(value, offset));
|
|
28856
28916
|
if (secret) {
|
|
28857
28917
|
safe += "[redacted]";
|
|
28918
|
+
this.redactions++;
|
|
28858
28919
|
offset += secret.length;
|
|
28859
28920
|
} else {
|
|
28860
28921
|
const code = this.pending.charCodeAt(offset);
|
|
@@ -28864,7 +28925,7 @@ var CodingDiagnostics = class {
|
|
|
28864
28925
|
}
|
|
28865
28926
|
}
|
|
28866
28927
|
this.pending = this.pending.slice(offset);
|
|
28867
|
-
const tail = diagnosticTail(this.text + safe,
|
|
28928
|
+
const tail = diagnosticTail(this.text + safe, this.retainBytes);
|
|
28868
28929
|
this.text = tail.text;
|
|
28869
28930
|
this.truncated ||= tail.truncated;
|
|
28870
28931
|
}
|
|
@@ -28888,6 +28949,12 @@ function codingSecrets(env) {
|
|
|
28888
28949
|
return [...values];
|
|
28889
28950
|
}
|
|
28890
28951
|
|
|
28952
|
+
// ../daemon/src/runtime/pi/coding.ts
|
|
28953
|
+
import {
|
|
28954
|
+
createLocalBashOperations
|
|
28955
|
+
} from "@earendil-works/pi-coding-agent";
|
|
28956
|
+
import { Type } from "typebox";
|
|
28957
|
+
|
|
28891
28958
|
// ../daemon/src/runtime/shell/approval.ts
|
|
28892
28959
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
28893
28960
|
|
|
@@ -29265,6 +29332,7 @@ function createApprovalGatedBashTool(base, host2) {
|
|
|
29265
29332
|
|
|
29266
29333
|
// ../daemon/src/runtime/pi/coding.ts
|
|
29267
29334
|
function codingTools(input) {
|
|
29335
|
+
const readSecrets = () => typeof input.secrets === "function" ? input.secrets() : input.secrets ?? [];
|
|
29268
29336
|
const hostOperations = input.docker ? null : createLocalBashOperations();
|
|
29269
29337
|
return [
|
|
29270
29338
|
{
|
|
@@ -29305,7 +29373,7 @@ function codingTools(input) {
|
|
|
29305
29373
|
{ additionalProperties: false }
|
|
29306
29374
|
),
|
|
29307
29375
|
executionMode: "sequential",
|
|
29308
|
-
async execute(toolCallId, params, signal) {
|
|
29376
|
+
async execute(toolCallId, params, signal, onUpdate) {
|
|
29309
29377
|
const command = validateCodingCommand(params);
|
|
29310
29378
|
const context = await input.context(command.cwd);
|
|
29311
29379
|
if (context.instructions.state !== "complete")
|
|
@@ -29315,7 +29383,27 @@ function codingTools(input) {
|
|
|
29315
29383
|
toolCallId,
|
|
29316
29384
|
input: command
|
|
29317
29385
|
});
|
|
29318
|
-
const output = new CodingDiagnostics(
|
|
29386
|
+
const output = new CodingDiagnostics(readSecrets());
|
|
29387
|
+
let lastProgressAt = 0;
|
|
29388
|
+
const emitProgress = (force) => {
|
|
29389
|
+
if (!onUpdate) return;
|
|
29390
|
+
const now = Date.now();
|
|
29391
|
+
if (!force && now - lastProgressAt < 400) return;
|
|
29392
|
+
lastProgressAt = now;
|
|
29393
|
+
const preview = output.preview(CODING_LIVE_BYTES);
|
|
29394
|
+
onUpdate({
|
|
29395
|
+
content: [{ type: "text", text: preview.text }],
|
|
29396
|
+
details: {
|
|
29397
|
+
codingProgress: {
|
|
29398
|
+
id: toolCallId,
|
|
29399
|
+
commandId: started.id,
|
|
29400
|
+
output: preview.text,
|
|
29401
|
+
truncated: preview.truncated,
|
|
29402
|
+
elapsedMs: Math.max(0, now - started.startedAt)
|
|
29403
|
+
}
|
|
29404
|
+
}
|
|
29405
|
+
});
|
|
29406
|
+
};
|
|
29319
29407
|
let outcome;
|
|
29320
29408
|
try {
|
|
29321
29409
|
if (input.approval)
|
|
@@ -29336,8 +29424,16 @@ function codingTools(input) {
|
|
|
29336
29424
|
const result = await operations.exec(
|
|
29337
29425
|
command.command,
|
|
29338
29426
|
input.docker ? input.root : cwd.path,
|
|
29339
|
-
{
|
|
29427
|
+
{
|
|
29428
|
+
onData: (bytes) => {
|
|
29429
|
+
output.append(bytes);
|
|
29430
|
+
emitProgress(false);
|
|
29431
|
+
},
|
|
29432
|
+
signal,
|
|
29433
|
+
timeout: command.timeoutSeconds
|
|
29434
|
+
}
|
|
29340
29435
|
);
|
|
29436
|
+
emitProgress(true);
|
|
29341
29437
|
outcome = {
|
|
29342
29438
|
state: result.exitCode === 0 ? "succeeded" : result.exitCode === null ? "interrupted" : "failed",
|
|
29343
29439
|
exitCode: result.exitCode,
|
|
@@ -29382,6 +29478,74 @@ function codingTools(input) {
|
|
|
29382
29478
|
details: { codingEvidence: value }
|
|
29383
29479
|
};
|
|
29384
29480
|
}
|
|
29481
|
+
},
|
|
29482
|
+
{
|
|
29483
|
+
name: "coding_log",
|
|
29484
|
+
label: "Read retained command output",
|
|
29485
|
+
description: "Read a bounded page of retained diagnostic output for a coding receipt, or search it. Producing Agents may read their own logs; a teammate must pass the id of the policy-authorized message that carried coding-receipt:<id>. Output is bounded and may be truncated, expired, deleted or unavailable \u2014 report that state honestly and do not infer completeness from an exit code.",
|
|
29486
|
+
parameters: Type.Object(
|
|
29487
|
+
{
|
|
29488
|
+
id: Type.String({ maxLength: 64 }),
|
|
29489
|
+
messageId: Type.Optional(Type.String({ maxLength: 64 })),
|
|
29490
|
+
search: Type.Optional(Type.String({ minLength: 1, maxLength: 256 })),
|
|
29491
|
+
offset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
29492
|
+
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 65536 }))
|
|
29493
|
+
},
|
|
29494
|
+
{ additionalProperties: false }
|
|
29495
|
+
),
|
|
29496
|
+
executionMode: "sequential",
|
|
29497
|
+
async execute(_id, params) {
|
|
29498
|
+
const values = params;
|
|
29499
|
+
if (values.search !== void 0) {
|
|
29500
|
+
const result = await input.host.invoke({
|
|
29501
|
+
action: "log-search",
|
|
29502
|
+
id: values.id,
|
|
29503
|
+
query: values.search,
|
|
29504
|
+
...values.messageId !== void 0 ? { messageId: values.messageId } : {}
|
|
29505
|
+
});
|
|
29506
|
+
return {
|
|
29507
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
29508
|
+
details: { codingLogSearch: result }
|
|
29509
|
+
};
|
|
29510
|
+
}
|
|
29511
|
+
const value = await input.host.invoke({
|
|
29512
|
+
action: "log",
|
|
29513
|
+
id: values.id,
|
|
29514
|
+
...values.messageId !== void 0 ? { messageId: values.messageId } : {},
|
|
29515
|
+
...values.offset !== void 0 ? { offset: values.offset } : {},
|
|
29516
|
+
...values.limit !== void 0 ? { limit: values.limit } : {}
|
|
29517
|
+
});
|
|
29518
|
+
return {
|
|
29519
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
29520
|
+
details: { codingLog: value }
|
|
29521
|
+
};
|
|
29522
|
+
}
|
|
29523
|
+
},
|
|
29524
|
+
{
|
|
29525
|
+
name: "source_snapshot",
|
|
29526
|
+
label: "Capture the current source state",
|
|
29527
|
+
description: "Capture a bounded snapshot of the repository source you are about to work from, and quote its reference in your summary. Call this once before you start editing: the reference is how a later change or verification is tied to the version it actually applies to. It is read-only and never changes the repository. The result may be incomplete \u2014 when it is, say so, because an incomplete snapshot cannot support a claim that code is unchanged. Untracked file content is never included unless you name each path.",
|
|
29528
|
+
parameters: Type.Object(
|
|
29529
|
+
{
|
|
29530
|
+
includeUntracked: Type.Optional(
|
|
29531
|
+
Type.Array(Type.String({ maxLength: 4096 }), { maxItems: 1e3 })
|
|
29532
|
+
)
|
|
29533
|
+
},
|
|
29534
|
+
{ additionalProperties: false }
|
|
29535
|
+
),
|
|
29536
|
+
executionMode: "sequential",
|
|
29537
|
+
async execute(toolCallId, params) {
|
|
29538
|
+
const values = params;
|
|
29539
|
+
const value = await input.host.invoke({
|
|
29540
|
+
action: "snapshot",
|
|
29541
|
+
toolCallId,
|
|
29542
|
+
...values.includeUntracked ? { includeUntracked: values.includeUntracked } : {}
|
|
29543
|
+
});
|
|
29544
|
+
return {
|
|
29545
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
29546
|
+
details: { sourceSnapshot: value }
|
|
29547
|
+
};
|
|
29548
|
+
}
|
|
29385
29549
|
}
|
|
29386
29550
|
];
|
|
29387
29551
|
}
|
|
@@ -29719,6 +29883,20 @@ function fallbackApi(providerName) {
|
|
|
29719
29883
|
return "openai-completions";
|
|
29720
29884
|
}
|
|
29721
29885
|
}
|
|
29886
|
+
var UnknownModelError = class extends Error {
|
|
29887
|
+
providerName;
|
|
29888
|
+
modelId;
|
|
29889
|
+
// Fields are declared explicitly, not as constructor parameter properties:
|
|
29890
|
+
// worker subprocesses run Node's strip-only TypeScript, which rejects them.
|
|
29891
|
+
constructor(providerName, modelId) {
|
|
29892
|
+
super(
|
|
29893
|
+
`Unknown model "${modelId}" for provider "${providerName}": it is not in that provider's catalog, and no endpoint is configured for it. Choose a catalogued model, or configure an endpoint for a custom one.`
|
|
29894
|
+
);
|
|
29895
|
+
this.name = "UnknownModelError";
|
|
29896
|
+
this.providerName = providerName;
|
|
29897
|
+
this.modelId = modelId;
|
|
29898
|
+
}
|
|
29899
|
+
};
|
|
29722
29900
|
function fallbackPiModel(providerName, modelId, baseUrl) {
|
|
29723
29901
|
return {
|
|
29724
29902
|
id: modelId,
|
|
@@ -29779,6 +29957,7 @@ async function createBazilionPiRuntime(options) {
|
|
|
29779
29957
|
function resolvePiModel(runtime, providerName, modelId, baseUrl) {
|
|
29780
29958
|
const known = runtime.getModel(piProviderName(providerName), modelId);
|
|
29781
29959
|
if (known) return baseUrl ? { ...known, baseUrl } : known;
|
|
29960
|
+
if (!baseUrl) throw new UnknownModelError(providerName, modelId);
|
|
29782
29961
|
return fallbackPiModel(providerName, modelId, baseUrl);
|
|
29783
29962
|
}
|
|
29784
29963
|
|
|
@@ -33218,6 +33397,7 @@ async function createBazilionSession(opts) {
|
|
|
33218
33397
|
if (repository) bazilionTools.push(repository.tool);
|
|
33219
33398
|
if (repository && opts.codingHost && shellTools && shellTools.config.sandboxMode === "docker" && !opts.preparedDocker)
|
|
33220
33399
|
throw new Error("Coding commands require the admitted Docker runtime");
|
|
33400
|
+
const redactionSecrets = uniqueSecrets(codingSecrets(env));
|
|
33221
33401
|
if (repository && opts.codingHost && shellTools)
|
|
33222
33402
|
bazilionTools.push(
|
|
33223
33403
|
...codingTools({
|
|
@@ -33228,7 +33408,7 @@ async function createBazilionSession(opts) {
|
|
|
33228
33408
|
lifecycle: opts.dockerLifecycle,
|
|
33229
33409
|
approval: shellTools.config.approvalMode === "dangerous",
|
|
33230
33410
|
approvalHost: bashApprovalHost,
|
|
33231
|
-
secrets:
|
|
33411
|
+
secrets: () => redactionSecrets
|
|
33232
33412
|
})
|
|
33233
33413
|
);
|
|
33234
33414
|
const customTools = shellTools?.customBash ? [...bazilionTools, shellTools.customBash] : bazilionTools;
|
|
@@ -33250,7 +33430,9 @@ async function createBazilionSession(opts) {
|
|
|
33250
33430
|
session.agent.getApiKey = async (requestedProvider) => {
|
|
33251
33431
|
if (requestedProvider !== piProviderName(providerName)) return void 0;
|
|
33252
33432
|
try {
|
|
33253
|
-
|
|
33433
|
+
const token = await refreshApiKey(providerName);
|
|
33434
|
+
if (token && !redactionSecrets.includes(token)) redactionSecrets.push(token);
|
|
33435
|
+
return token;
|
|
33254
33436
|
} catch {
|
|
33255
33437
|
return void 0;
|
|
33256
33438
|
}
|
|
@@ -33263,6 +33445,9 @@ async function createBazilionSession(opts) {
|
|
|
33263
33445
|
}
|
|
33264
33446
|
};
|
|
33265
33447
|
}
|
|
33448
|
+
function uniqueSecrets(values) {
|
|
33449
|
+
return [...new Set(values.filter(Boolean))];
|
|
33450
|
+
}
|
|
33266
33451
|
function splitModelString(s) {
|
|
33267
33452
|
const idx = s.indexOf(":");
|
|
33268
33453
|
if (idx === -1) {
|
|
@@ -34875,6 +35060,223 @@ function createBrowserHost(config2) {
|
|
|
34875
35060
|
// ../daemon/src/lib/coding-environment/agent-host.ts
|
|
34876
35061
|
import { createHash as createHash10, randomUUID as randomUUID30 } from "crypto";
|
|
34877
35062
|
|
|
35063
|
+
// ../daemon/src/core/repos/coding-command-logs.ts
|
|
35064
|
+
var CODING_LOG_BYTES = 2 * 1024 * 1024;
|
|
35065
|
+
var CODING_LOG_HOME_BYTES = 256 * 1024 * 1024;
|
|
35066
|
+
var CODING_LOG_TTL_MS = 7 * 864e5;
|
|
35067
|
+
var CODING_LOG_PAGE_BYTES = 64 * 1024;
|
|
35068
|
+
var CODING_LOG_SEARCH_HITS = 50;
|
|
35069
|
+
var CODING_LOG_SEARCH_SCAN_BYTES = 512 * 1024;
|
|
35070
|
+
function headBytes(value, limit) {
|
|
35071
|
+
const bytes = Buffer.from(value);
|
|
35072
|
+
if (bytes.length <= limit) return value;
|
|
35073
|
+
let end = limit;
|
|
35074
|
+
while (end < bytes.length && ((bytes[end] ?? 0) & 192) === 128) end++;
|
|
35075
|
+
return bytes.subarray(0, end).toString("utf8");
|
|
35076
|
+
}
|
|
35077
|
+
function mayDisclose(view2, audience) {
|
|
35078
|
+
return audience === "producer" || view2.releasedAt !== null;
|
|
35079
|
+
}
|
|
35080
|
+
var COLUMNS = `command_id, team_id, agent_id, turn_id, tool_call_id, state, text,
|
|
35081
|
+
byte_length, observed_bytes, redacted, truncated, created_at, expires_at, released_at, retired_at`;
|
|
35082
|
+
function expiryDue(row, now) {
|
|
35083
|
+
return row.state === "retained" && row.expires_at <= now;
|
|
35084
|
+
}
|
|
35085
|
+
function liveText(row, now) {
|
|
35086
|
+
return row.state === "retained" && !expiryDue(row, now) ? row.text : null;
|
|
35087
|
+
}
|
|
35088
|
+
function availabilityOf(row, now) {
|
|
35089
|
+
if (row.state === "deleted") return "deleted";
|
|
35090
|
+
if (row.state === "expired" || expiryDue(row, now)) return "expired";
|
|
35091
|
+
return row.truncated === 1 ? "truncated" : "available";
|
|
35092
|
+
}
|
|
35093
|
+
function toView(row, now) {
|
|
35094
|
+
return {
|
|
35095
|
+
commandId: row.command_id,
|
|
35096
|
+
teamId: row.team_id,
|
|
35097
|
+
agentId: row.agent_id,
|
|
35098
|
+
turnId: row.turn_id,
|
|
35099
|
+
toolCallId: row.tool_call_id,
|
|
35100
|
+
availability: availabilityOf(row, now),
|
|
35101
|
+
byteLength: row.byte_length,
|
|
35102
|
+
observedBytes: row.observed_bytes,
|
|
35103
|
+
redacted: row.redacted === 1,
|
|
35104
|
+
createdAt: row.created_at,
|
|
35105
|
+
expiresAt: row.expires_at,
|
|
35106
|
+
releasedAt: row.released_at,
|
|
35107
|
+
retiredAt: row.retired_at
|
|
35108
|
+
};
|
|
35109
|
+
}
|
|
35110
|
+
function rowFor(db, commandId) {
|
|
35111
|
+
return db.raw.query(`SELECT ${COLUMNS} FROM coding_command_logs WHERE command_id = ?`).get(commandId) ?? null;
|
|
35112
|
+
}
|
|
35113
|
+
function unavailableLog(commandId) {
|
|
35114
|
+
return {
|
|
35115
|
+
commandId,
|
|
35116
|
+
teamId: "",
|
|
35117
|
+
agentId: "",
|
|
35118
|
+
turnId: "",
|
|
35119
|
+
toolCallId: "",
|
|
35120
|
+
availability: "unavailable",
|
|
35121
|
+
byteLength: 0,
|
|
35122
|
+
observedBytes: 0,
|
|
35123
|
+
redacted: false,
|
|
35124
|
+
createdAt: 0,
|
|
35125
|
+
expiresAt: 0,
|
|
35126
|
+
releasedAt: null,
|
|
35127
|
+
retiredAt: null
|
|
35128
|
+
};
|
|
35129
|
+
}
|
|
35130
|
+
function getCodingCommandLog(db, commandId, now = Date.now()) {
|
|
35131
|
+
const row = rowFor(db, commandId);
|
|
35132
|
+
return row ? toView(row, now) : unavailableLog(commandId);
|
|
35133
|
+
}
|
|
35134
|
+
function saveCodingCommandLog(db, input) {
|
|
35135
|
+
const now = input.now ?? Date.now();
|
|
35136
|
+
const retained = diagnosticTail(input.diagnostic, CODING_LOG_BYTES);
|
|
35137
|
+
const byteLength = Buffer.byteLength(retained.text);
|
|
35138
|
+
const observed = Math.max(input.observedBytes, byteLength);
|
|
35139
|
+
const truncated = retained.truncated || input.truncated || !input.redacted && input.observedBytes > byteLength;
|
|
35140
|
+
db.raw.run(
|
|
35141
|
+
`INSERT INTO coding_command_logs (${COLUMNS})
|
|
35142
|
+
VALUES (?, ?, ?, ?, ?, 'retained', ?, ?, ?, ?, ?, ?, ?, NULL, NULL)
|
|
35143
|
+
ON CONFLICT(command_id) DO UPDATE SET
|
|
35144
|
+
state = 'retained',
|
|
35145
|
+
text = excluded.text,
|
|
35146
|
+
byte_length = excluded.byte_length,
|
|
35147
|
+
observed_bytes = excluded.observed_bytes,
|
|
35148
|
+
redacted = excluded.redacted,
|
|
35149
|
+
truncated = excluded.truncated,
|
|
35150
|
+
-- created_at/expires_at stay pinned to the first write: a later update must not
|
|
35151
|
+
-- extend the retention window. released_at is deliberately cleared, because
|
|
35152
|
+
-- changed output cannot inherit an earlier approval to disclose it.
|
|
35153
|
+
released_at = NULL,
|
|
35154
|
+
retired_at = NULL`,
|
|
35155
|
+
[
|
|
35156
|
+
input.commandId,
|
|
35157
|
+
input.teamId,
|
|
35158
|
+
input.agentId,
|
|
35159
|
+
input.turnId,
|
|
35160
|
+
input.toolCallId,
|
|
35161
|
+
retained.text,
|
|
35162
|
+
byteLength,
|
|
35163
|
+
observed,
|
|
35164
|
+
input.redacted ? 1 : 0,
|
|
35165
|
+
truncated ? 1 : 0,
|
|
35166
|
+
now,
|
|
35167
|
+
now + CODING_LOG_TTL_MS
|
|
35168
|
+
]
|
|
35169
|
+
);
|
|
35170
|
+
pruneCodingCommandLogs(db, now);
|
|
35171
|
+
return getCodingCommandLog(db, input.commandId, now);
|
|
35172
|
+
}
|
|
35173
|
+
function codingCommandIdFromResult(result) {
|
|
35174
|
+
try {
|
|
35175
|
+
const parsed = JSON.parse(result);
|
|
35176
|
+
return typeof parsed.id === "string" && parsed.id.length > 0 ? parsed.id : null;
|
|
35177
|
+
} catch {
|
|
35178
|
+
return null;
|
|
35179
|
+
}
|
|
35180
|
+
}
|
|
35181
|
+
function releaseCodingCommandLog(db, commandId, now = Date.now()) {
|
|
35182
|
+
db.raw.run(
|
|
35183
|
+
`UPDATE coding_command_logs SET released_at = COALESCE(released_at, ?)
|
|
35184
|
+
WHERE command_id = ? AND state = 'retained'`,
|
|
35185
|
+
[now, commandId]
|
|
35186
|
+
);
|
|
35187
|
+
}
|
|
35188
|
+
function readCodingCommandLog(db, commandId, options, now = Date.now()) {
|
|
35189
|
+
const view2 = getCodingCommandLog(db, commandId, now);
|
|
35190
|
+
if (!mayDisclose(view2, options.audience)) return null;
|
|
35191
|
+
const row = rowFor(db, commandId);
|
|
35192
|
+
const limit = Math.min(Math.max(options.limit ?? CODING_LOG_PAGE_BYTES, 1), CODING_LOG_BYTES);
|
|
35193
|
+
const text3 = row ? liveText(row, now) : null;
|
|
35194
|
+
if (!row || text3 === null) {
|
|
35195
|
+
return {
|
|
35196
|
+
commandId,
|
|
35197
|
+
availability: view2.availability,
|
|
35198
|
+
offset: 0,
|
|
35199
|
+
text: "",
|
|
35200
|
+
hasMore: false,
|
|
35201
|
+
byteLength: view2.byteLength
|
|
35202
|
+
};
|
|
35203
|
+
}
|
|
35204
|
+
const bytes = Buffer.from(text3);
|
|
35205
|
+
let offset = Math.min(Math.max(options.offset ?? 0, 0), bytes.length);
|
|
35206
|
+
while (offset < bytes.length && ((bytes[offset] ?? 0) & 192) === 128) offset++;
|
|
35207
|
+
let end = Math.min(offset + limit, bytes.length);
|
|
35208
|
+
while (end < bytes.length && ((bytes[end] ?? 0) & 192) === 128) end++;
|
|
35209
|
+
return {
|
|
35210
|
+
commandId,
|
|
35211
|
+
availability: view2.availability,
|
|
35212
|
+
offset,
|
|
35213
|
+
text: bytes.subarray(offset, end).toString("utf8"),
|
|
35214
|
+
hasMore: end < bytes.length,
|
|
35215
|
+
byteLength: view2.byteLength
|
|
35216
|
+
};
|
|
35217
|
+
}
|
|
35218
|
+
function searchCodingCommandLog(db, commandId, needle, options, now = Date.now()) {
|
|
35219
|
+
const view2 = getCodingCommandLog(db, commandId, now);
|
|
35220
|
+
if (!mayDisclose(view2, options.audience)) return null;
|
|
35221
|
+
const row = rowFor(db, commandId);
|
|
35222
|
+
const empty = {
|
|
35223
|
+
commandId,
|
|
35224
|
+
availability: view2.availability,
|
|
35225
|
+
matches: [],
|
|
35226
|
+
bounded: false
|
|
35227
|
+
};
|
|
35228
|
+
const text3 = row ? liveText(row, now) : null;
|
|
35229
|
+
if (text3 === null || !needle) return empty;
|
|
35230
|
+
const scanned = Buffer.byteLength(text3) > CODING_LOG_SEARCH_SCAN_BYTES;
|
|
35231
|
+
const haystack = scanned ? headBytes(text3, CODING_LOG_SEARCH_SCAN_BYTES) : text3;
|
|
35232
|
+
const matches = [];
|
|
35233
|
+
let from = 0;
|
|
35234
|
+
let hitLimit = false;
|
|
35235
|
+
for (; ; ) {
|
|
35236
|
+
if (matches.length === CODING_LOG_SEARCH_HITS) {
|
|
35237
|
+
hitLimit = true;
|
|
35238
|
+
break;
|
|
35239
|
+
}
|
|
35240
|
+
const at = haystack.indexOf(needle, from);
|
|
35241
|
+
if (at < 0) break;
|
|
35242
|
+
const prefix = haystack.slice(0, at);
|
|
35243
|
+
matches.push({
|
|
35244
|
+
offset: Buffer.byteLength(prefix),
|
|
35245
|
+
line: prefix.split("\n").length,
|
|
35246
|
+
excerpt: haystack.slice(Math.max(0, at - 40), at + needle.length + 40)
|
|
35247
|
+
});
|
|
35248
|
+
from = at + needle.length;
|
|
35249
|
+
}
|
|
35250
|
+
return {
|
|
35251
|
+
commandId,
|
|
35252
|
+
availability: view2.availability,
|
|
35253
|
+
matches,
|
|
35254
|
+
bounded: scanned || hitLimit
|
|
35255
|
+
};
|
|
35256
|
+
}
|
|
35257
|
+
function pruneCodingCommandLogs(db, now = Date.now(), budgetBytes = CODING_LOG_HOME_BYTES) {
|
|
35258
|
+
db.raw.run(
|
|
35259
|
+
`UPDATE coding_command_logs SET state = 'expired', text = NULL, byte_length = 0, retired_at = ?
|
|
35260
|
+
WHERE state = 'retained' AND expires_at <= ?`,
|
|
35261
|
+
[now, now]
|
|
35262
|
+
);
|
|
35263
|
+
for (; ; ) {
|
|
35264
|
+
const total = db.raw.query(
|
|
35265
|
+
"SELECT SUM(byte_length) AS bytes FROM coding_command_logs"
|
|
35266
|
+
).get()?.bytes ?? 0;
|
|
35267
|
+
if (total <= budgetBytes) return;
|
|
35268
|
+
const oldest = db.raw.query(
|
|
35269
|
+
`SELECT command_id FROM coding_command_logs
|
|
35270
|
+
WHERE byte_length > 0 AND command_id NOT IN (
|
|
35271
|
+
SELECT id FROM coding_commands WHERE state = 'running'
|
|
35272
|
+
)
|
|
35273
|
+
ORDER BY created_at ASC, command_id ASC LIMIT 1`
|
|
35274
|
+
).get();
|
|
35275
|
+
if (!oldest) return;
|
|
35276
|
+
db.raw.run("DELETE FROM coding_command_logs WHERE command_id = ?", [oldest.command_id]);
|
|
35277
|
+
}
|
|
35278
|
+
}
|
|
35279
|
+
|
|
34878
35280
|
// ../daemon/src/lib/communication.ts
|
|
34879
35281
|
import { randomUUID as randomUUID29 } from "crypto";
|
|
34880
35282
|
|
|
@@ -35273,11 +35675,15 @@ function authorizeHttpChatFrame(db, agentId, requestAttemptId, frameIndex, frame
|
|
|
35273
35675
|
if (frame.kind === "event" && frame.event.type === "file") {
|
|
35274
35676
|
releaseResultFile(db, agentId, frame.event.result);
|
|
35275
35677
|
}
|
|
35678
|
+
if (frame.kind === "event" && frame.event.type === "tool_result" && frame.event.name === "coding_command") {
|
|
35679
|
+
const id = codingCommandIdFromResult(frame.event.result);
|
|
35680
|
+
if (id) releaseCodingCommandLog(db, id);
|
|
35681
|
+
}
|
|
35276
35682
|
}
|
|
35277
35683
|
function isUserFacingFrame(frame) {
|
|
35278
35684
|
if (frame.kind === "fatal") return true;
|
|
35279
35685
|
if (frame.kind !== "event") return false;
|
|
35280
|
-
return frame.event.type === "assistant_message" || frame.event.type === "assistant_delta" || frame.event.type === "file" || frame.event.type === "tool_result" && (frame.event.name === "repository_context" || Boolean(frame.event.images?.length)) || frame.event.type === "error";
|
|
35686
|
+
return frame.event.type === "assistant_message" || frame.event.type === "assistant_delta" || frame.event.type === "coding_progress" || frame.event.type === "file" || frame.event.type === "tool_result" && (frame.event.name === "repository_context" || frame.event.name === "coding_command" || Boolean(frame.event.images?.length)) || frame.event.type === "error";
|
|
35281
35687
|
}
|
|
35282
35688
|
function sendAgentMessage(db, input) {
|
|
35283
35689
|
const attemptKind = input.attemptKind ?? "agent_tool";
|
|
@@ -35608,48 +36014,112 @@ function claimDeliverableInbox(db, agentId, onAllowed, conversationId) {
|
|
|
35608
36014
|
return outcome.allowed;
|
|
35609
36015
|
}
|
|
35610
36016
|
|
|
35611
|
-
// ../daemon/src/
|
|
36017
|
+
// ../daemon/src/core/repos/source-snapshots.ts
|
|
36018
|
+
var SOURCE_SNAPSHOT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
36019
|
+
var COLUMNS2 = "snapshot_id, team_id, captured_by, agent_id, turn_id, tool_call_id, complete, head, base_oid, entry_count, captured_content_bytes, manifest_json, created_at, expires_at";
|
|
36020
|
+
function toRecord(row) {
|
|
36021
|
+
return {
|
|
36022
|
+
snapshotId: row.snapshot_id,
|
|
36023
|
+
teamId: row.team_id,
|
|
36024
|
+
capturedBy: row.captured_by === "agent" ? "agent" : "operator",
|
|
36025
|
+
agentId: row.agent_id,
|
|
36026
|
+
turnId: row.turn_id,
|
|
36027
|
+
toolCallId: row.tool_call_id,
|
|
36028
|
+
complete: row.complete === 1,
|
|
36029
|
+
head: row.head,
|
|
36030
|
+
baseOid: row.base_oid,
|
|
36031
|
+
entryCount: row.entry_count,
|
|
36032
|
+
capturedContentBytes: row.captured_content_bytes,
|
|
36033
|
+
manifestJson: row.manifest_json,
|
|
36034
|
+
createdAt: row.created_at,
|
|
36035
|
+
expiresAt: row.expires_at
|
|
36036
|
+
};
|
|
36037
|
+
}
|
|
36038
|
+
function saveSourceSnapshot(db, input) {
|
|
36039
|
+
const now = input.now ?? Date.now();
|
|
36040
|
+
db.raw.run(
|
|
36041
|
+
`INSERT INTO source_snapshots (${COLUMNS2})
|
|
36042
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
36043
|
+
ON CONFLICT(snapshot_id, team_id) DO NOTHING`,
|
|
36044
|
+
[
|
|
36045
|
+
input.snapshotId,
|
|
36046
|
+
input.teamId,
|
|
36047
|
+
input.capturedBy,
|
|
36048
|
+
input.agentId,
|
|
36049
|
+
input.turnId,
|
|
36050
|
+
input.toolCallId,
|
|
36051
|
+
input.complete ? 1 : 0,
|
|
36052
|
+
input.head,
|
|
36053
|
+
input.baseOid,
|
|
36054
|
+
input.entryCount,
|
|
36055
|
+
input.capturedContentBytes,
|
|
36056
|
+
input.manifestJson,
|
|
36057
|
+
now,
|
|
36058
|
+
now + SOURCE_SNAPSHOT_TTL_MS
|
|
36059
|
+
]
|
|
36060
|
+
);
|
|
36061
|
+
pruneSourceSnapshots(db, now);
|
|
36062
|
+
}
|
|
36063
|
+
function getSourceSnapshot(db, teamId, snapshotId, now = Date.now()) {
|
|
36064
|
+
const row = db.raw.query(
|
|
36065
|
+
`SELECT ${COLUMNS2} FROM source_snapshots
|
|
36066
|
+
WHERE team_id = ? AND snapshot_id = ? AND expires_at > ?`
|
|
36067
|
+
).get(teamId, snapshotId, now);
|
|
36068
|
+
return row ? toRecord(row) : null;
|
|
36069
|
+
}
|
|
36070
|
+
function pruneSourceSnapshots(db, now = Date.now()) {
|
|
36071
|
+
const result = db.raw.run("DELETE FROM source_snapshots WHERE expires_at <= ?", [now]);
|
|
36072
|
+
return Number(result.changes);
|
|
36073
|
+
}
|
|
36074
|
+
function listSourceSnapshots(db, teamId, limit = 50, now = Date.now()) {
|
|
36075
|
+
return db.raw.query(
|
|
36076
|
+
`SELECT ${COLUMNS2} FROM source_snapshots
|
|
36077
|
+
WHERE team_id = ? AND expires_at > ?
|
|
36078
|
+
ORDER BY created_at DESC, snapshot_id LIMIT ?`
|
|
36079
|
+
).all(teamId, now, limit).map(toRecord);
|
|
36080
|
+
}
|
|
36081
|
+
|
|
36082
|
+
// ../daemon/src/lib/git/capture.ts
|
|
35612
36083
|
import { execFile } from "child_process";
|
|
35613
36084
|
import { mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync4, rmSync as rmSync14, writeFileSync as writeFileSync12 } from "fs";
|
|
35614
36085
|
import { tmpdir as tmpdir4 } from "os";
|
|
35615
36086
|
import { join as join25 } from "path";
|
|
35616
|
-
var
|
|
35617
|
-
|
|
35618
|
-
|
|
35619
|
-
|
|
35620
|
-
|
|
35621
|
-
|
|
35622
|
-
|
|
35623
|
-
|
|
35624
|
-
|
|
35625
|
-
|
|
35626
|
-
|
|
35627
|
-
|
|
35628
|
-
|
|
35629
|
-
|
|
35630
|
-
let
|
|
36087
|
+
var INSPECTION_BUDGET_MS = 5e3;
|
|
36088
|
+
var METADATA_BUDGET_MS = 2500;
|
|
36089
|
+
var METADATA_BYTES = 64 * 1024 * 1024;
|
|
36090
|
+
var METADATA_ENTRIES = 16384;
|
|
36091
|
+
var METADATA_DEPTH = 32;
|
|
36092
|
+
var METADATA_READ = 64 * 1024;
|
|
36093
|
+
var METADATA_READ_LARGE = 16 * 1024 * 1024;
|
|
36094
|
+
var RUN_MAX_BUFFER = 1024 * 1024;
|
|
36095
|
+
function findRepositoryRoot(ancestry) {
|
|
36096
|
+
return [...ancestry].reverse().find((dir) => dir.entry(".git") !== null) ?? null;
|
|
36097
|
+
}
|
|
36098
|
+
async function captureRepositoryGit(root, budgetMs = INSPECTION_BUDGET_MS) {
|
|
36099
|
+
const start = Date.now();
|
|
36100
|
+
let bytes = 0;
|
|
36101
|
+
let count3 = 0;
|
|
36102
|
+
const marker = root.entry(".git");
|
|
36103
|
+
if (!marker?.isDirectory() || marker.isSymbolicLink())
|
|
36104
|
+
throw new ContextReadError("unsupported_git_metadata");
|
|
36105
|
+
const git = root.directory(".git");
|
|
36106
|
+
if (git.entry("commondir") || git.entry("gitdir")) {
|
|
36107
|
+
throw new ContextReadError("unsupported_git_metadata");
|
|
36108
|
+
}
|
|
36109
|
+
const info = git.entry("info");
|
|
36110
|
+
if (info) {
|
|
36111
|
+
const dir = git.directory("info");
|
|
36112
|
+
if (dir.entry("sparse-checkout")) throw new ContextReadError("unsupported_git_metadata");
|
|
36113
|
+
}
|
|
36114
|
+
const scratch = mkdtempSync4(join25(tmpdir4(), "bazilion-context-git-"));
|
|
36115
|
+
let cleaned = false;
|
|
36116
|
+
const cleanup = () => {
|
|
36117
|
+
if (cleaned) return;
|
|
36118
|
+
cleaned = true;
|
|
36119
|
+
rmSync14(scratch, { recursive: true, force: true });
|
|
36120
|
+
};
|
|
35631
36121
|
try {
|
|
35632
|
-
const
|
|
35633
|
-
if (!root) return report;
|
|
35634
|
-
report.root = root.label;
|
|
35635
|
-
report.state = "unavailable";
|
|
35636
|
-
const marker = root.entry(".git");
|
|
35637
|
-
if (!marker?.isDirectory() || marker.isSymbolicLink())
|
|
35638
|
-
throw new ContextReadError("unsupported_git_metadata");
|
|
35639
|
-
const git = root.directory(".git");
|
|
35640
|
-
if (git.entry("commondir") || git.entry("gitdir")) {
|
|
35641
|
-
throw new ContextReadError("unsupported_git_metadata");
|
|
35642
|
-
}
|
|
35643
|
-
const info = git.entry("info");
|
|
35644
|
-
if (info) {
|
|
35645
|
-
const dir = git.directory("info");
|
|
35646
|
-
if (dir.entry("sparse-checkout")) throw new ContextReadError("unsupported_git_metadata");
|
|
35647
|
-
}
|
|
35648
|
-
const start = Date.now();
|
|
35649
|
-
let bytes = 0;
|
|
35650
|
-
let count3 = 0;
|
|
35651
|
-
scratch = mkdtempSync4(join25(tmpdir4(), "bazilion-context-git-"));
|
|
35652
|
-
const runGit = (args) => new Promise((resolve8, reject2) => {
|
|
36122
|
+
const rawGit = (args) => new Promise((resolve8, reject2) => {
|
|
35653
36123
|
execFile(
|
|
35654
36124
|
"git",
|
|
35655
36125
|
args,
|
|
@@ -35666,21 +36136,40 @@ async function inspectGit(ancestry) {
|
|
|
35666
36136
|
GIT_OPTIONAL_LOCKS: "0",
|
|
35667
36137
|
LC_ALL: "C"
|
|
35668
36138
|
},
|
|
35669
|
-
timeout: Math.max(1,
|
|
35670
|
-
maxBuffer:
|
|
36139
|
+
timeout: Math.max(1, budgetMs - (Date.now() - start)),
|
|
36140
|
+
maxBuffer: RUN_MAX_BUFFER,
|
|
35671
36141
|
encoding: "utf8"
|
|
35672
36142
|
},
|
|
35673
36143
|
(error2, stdout) => error2 ? reject2(error2) : resolve8(stdout)
|
|
35674
36144
|
);
|
|
35675
36145
|
});
|
|
35676
|
-
const
|
|
36146
|
+
const inspectionArgs = (args) => [
|
|
36147
|
+
"--no-optional-locks",
|
|
36148
|
+
`--git-dir=${scratch}`,
|
|
36149
|
+
`--work-tree=/proc/${process.pid}/fd/${root.fd}`,
|
|
36150
|
+
"-c",
|
|
36151
|
+
"core.fsmonitor=false",
|
|
36152
|
+
"-c",
|
|
36153
|
+
"core.untrackedCache=false",
|
|
36154
|
+
"-c",
|
|
36155
|
+
"core.hooksPath=/dev/null",
|
|
36156
|
+
"-c",
|
|
36157
|
+
"core.excludesFile=/dev/null",
|
|
36158
|
+
"-c",
|
|
36159
|
+
"diff.renames=false",
|
|
36160
|
+
"-c",
|
|
36161
|
+
"submodule.recurse=false",
|
|
36162
|
+
...args
|
|
36163
|
+
];
|
|
36164
|
+
const runGit = (args) => rawGit(inspectionArgs(args));
|
|
36165
|
+
const capturedConfig = git.read("config", METADATA_READ);
|
|
35677
36166
|
const configLines = ["[core]", "repositoryformatversion = 0", "bare = false", "filemode = true"];
|
|
35678
36167
|
if (capturedConfig) {
|
|
35679
36168
|
bytes += capturedConfig.length;
|
|
35680
36169
|
count3++;
|
|
35681
36170
|
const configPath = join25(scratch, "captured-config");
|
|
35682
36171
|
writeFileSync12(configPath, capturedConfig, { mode: 384 });
|
|
35683
|
-
const config2 = await
|
|
36172
|
+
const config2 = await rawGit([
|
|
35684
36173
|
"config",
|
|
35685
36174
|
"--no-includes",
|
|
35686
36175
|
"--file",
|
|
@@ -35728,7 +36217,7 @@ objectformat = ${value}`
|
|
|
35728
36217
|
}
|
|
35729
36218
|
}
|
|
35730
36219
|
if (info) {
|
|
35731
|
-
const exclude = git.directory("info").read("exclude",
|
|
36220
|
+
const exclude = git.directory("info").read("exclude", METADATA_READ);
|
|
35732
36221
|
if (exclude) {
|
|
35733
36222
|
bytes += exclude.length;
|
|
35734
36223
|
mkdirSync9(join25(scratch, "info"), { mode: 448 });
|
|
@@ -35736,9 +36225,9 @@ objectformat = ${value}`
|
|
|
35736
36225
|
}
|
|
35737
36226
|
}
|
|
35738
36227
|
const copy = (source, destination, depth) => {
|
|
35739
|
-
if (depth >
|
|
36228
|
+
if (depth > METADATA_DEPTH) throw new ContextReadError("git_metadata_limit");
|
|
35740
36229
|
for (const name of source.list()) {
|
|
35741
|
-
if (++count3 >
|
|
36230
|
+
if (++count3 > METADATA_ENTRIES || Date.now() - start > METADATA_BUDGET_MS)
|
|
35742
36231
|
throw new ContextReadError("git_metadata_limit");
|
|
35743
36232
|
const entry = source.entry(name);
|
|
35744
36233
|
if (!entry || entry.isSymbolicLink()) throw new ContextReadError("unsupported_git_metadata");
|
|
@@ -35748,7 +36237,7 @@ objectformat = ${value}`
|
|
|
35748
36237
|
mkdirSync9(join25(destination, name), { mode: 448 });
|
|
35749
36238
|
copy(source.directory(name), join25(destination, name), depth + 1);
|
|
35750
36239
|
} else {
|
|
35751
|
-
const content = source.read(name,
|
|
36240
|
+
const content = source.read(name, METADATA_BYTES - bytes);
|
|
35752
36241
|
if (!content) throw new ContextReadError("source_changed");
|
|
35753
36242
|
bytes += content.length;
|
|
35754
36243
|
writeFileSync12(join25(destination, name), content, { mode: 384 });
|
|
@@ -35756,38 +36245,873 @@ objectformat = ${value}`
|
|
|
35756
36245
|
}
|
|
35757
36246
|
};
|
|
35758
36247
|
for (const name of ["HEAD", "index", "packed-refs", "shallow"]) {
|
|
35759
|
-
const content = git.read(name, Math.min(
|
|
36248
|
+
const content = git.read(name, Math.min(METADATA_READ_LARGE, METADATA_BYTES - bytes));
|
|
35760
36249
|
if (content) {
|
|
35761
36250
|
count3++;
|
|
35762
36251
|
bytes += content.length;
|
|
35763
36252
|
writeFileSync12(join25(scratch, name), content, { mode: 384 });
|
|
35764
36253
|
}
|
|
35765
36254
|
}
|
|
35766
|
-
for (const name of ["refs", "objects"]) {
|
|
35767
|
-
mkdirSync9(join25(scratch, name), { mode: 448 });
|
|
35768
|
-
if (git.entry(name)) copy(git.directory(name), join25(scratch, name), 0);
|
|
36255
|
+
for (const name of ["refs", "objects"]) {
|
|
36256
|
+
mkdirSync9(join25(scratch, name), { mode: 448 });
|
|
36257
|
+
if (git.entry(name)) copy(git.directory(name), join25(scratch, name), 0);
|
|
36258
|
+
}
|
|
36259
|
+
if (git.list().some((name) => name.startsWith("sharedindex.")))
|
|
36260
|
+
throw new ContextReadError("unsupported_git_metadata");
|
|
36261
|
+
writeFileSync12(join25(scratch, "config"), `${configLines.join("\n")}
|
|
36262
|
+
`, { mode: 384 });
|
|
36263
|
+
const validate2 = () => git.validate();
|
|
36264
|
+
validate2();
|
|
36265
|
+
return { root, runGit, validate: validate2, cleanup };
|
|
36266
|
+
} catch (error2) {
|
|
36267
|
+
cleanup();
|
|
36268
|
+
throw error2;
|
|
36269
|
+
}
|
|
36270
|
+
}
|
|
36271
|
+
|
|
36272
|
+
// ../../packages/api-types/src/entities.ts
|
|
36273
|
+
var REASONING_LEVELS2 = [
|
|
36274
|
+
"off",
|
|
36275
|
+
"minimal",
|
|
36276
|
+
"low",
|
|
36277
|
+
"medium",
|
|
36278
|
+
"high",
|
|
36279
|
+
"xhigh"
|
|
36280
|
+
];
|
|
36281
|
+
|
|
36282
|
+
// ../../packages/api-types/src/git-review.ts
|
|
36283
|
+
var REVIEW_LIMITS = {
|
|
36284
|
+
files: 1e3,
|
|
36285
|
+
fileBytes: 1024 * 1024,
|
|
36286
|
+
totalBytes: 16 * 1024 * 1024,
|
|
36287
|
+
patchBytes: 256 * 1024
|
|
36288
|
+
};
|
|
36289
|
+
|
|
36290
|
+
// ../../packages/api-types/src/index.ts
|
|
36291
|
+
var PROFILE_FILES = [
|
|
36292
|
+
"profile.json",
|
|
36293
|
+
"SOUL.md",
|
|
36294
|
+
"IDENTITY.md",
|
|
36295
|
+
"BOOTSTRAP.md",
|
|
36296
|
+
"AGENTS.md",
|
|
36297
|
+
"TOOLS.md"
|
|
36298
|
+
];
|
|
36299
|
+
|
|
36300
|
+
// ../daemon/src/lib/git-review/refs.ts
|
|
36301
|
+
var REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,254}$/;
|
|
36302
|
+
function isCommitOid(value) {
|
|
36303
|
+
return /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(value);
|
|
36304
|
+
}
|
|
36305
|
+
var ReviewRefError = class extends Error {
|
|
36306
|
+
value;
|
|
36307
|
+
constructor(value) {
|
|
36308
|
+
super("Comparison base must be a plain branch, tag or commit id.");
|
|
36309
|
+
this.name = "ReviewRefError";
|
|
36310
|
+
this.value = value;
|
|
36311
|
+
}
|
|
36312
|
+
};
|
|
36313
|
+
function validateRefName(value) {
|
|
36314
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 255) {
|
|
36315
|
+
throw new ReviewRefError(typeof value === "string" ? value : "");
|
|
36316
|
+
}
|
|
36317
|
+
if (!REF_PATTERN.test(value)) throw new ReviewRefError(value);
|
|
36318
|
+
if (value === "." || value === "..") throw new ReviewRefError(value);
|
|
36319
|
+
if (value.includes("..") || value.includes("@{") || value.includes("//")) {
|
|
36320
|
+
throw new ReviewRefError(value);
|
|
36321
|
+
}
|
|
36322
|
+
if (value.endsWith("/") || value.endsWith(".") || value.endsWith(".lock")) {
|
|
36323
|
+
throw new ReviewRefError(value);
|
|
36324
|
+
}
|
|
36325
|
+
return value;
|
|
36326
|
+
}
|
|
36327
|
+
|
|
36328
|
+
// ../daemon/src/lib/git-review/identity.ts
|
|
36329
|
+
var ReviewBaseError = class extends Error {
|
|
36330
|
+
code;
|
|
36331
|
+
constructor(code, message) {
|
|
36332
|
+
super(message);
|
|
36333
|
+
this.name = "ReviewBaseError";
|
|
36334
|
+
this.code = code;
|
|
36335
|
+
}
|
|
36336
|
+
};
|
|
36337
|
+
var BRANCH_MAX = 4096;
|
|
36338
|
+
async function readRepositoryIdentity(captured) {
|
|
36339
|
+
const branch = await readOrNull(captured, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
|
|
36340
|
+
const head = await readOrNull(captured, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
|
|
36341
|
+
if (branch !== null && branch.length > BRANCH_MAX) {
|
|
36342
|
+
throw new ReviewBaseError("git_output_limit", "Repository identity exceeds its bound.");
|
|
36343
|
+
}
|
|
36344
|
+
const resolvedHead = head !== null && isCommitOid(head) ? head : null;
|
|
36345
|
+
return {
|
|
36346
|
+
branch,
|
|
36347
|
+
head: resolvedHead,
|
|
36348
|
+
headState: resolvedHead ? branch ? "branch" : "detached" : "unborn"
|
|
36349
|
+
};
|
|
36350
|
+
}
|
|
36351
|
+
async function resolveComparisonBase(captured, requestedRef = "HEAD") {
|
|
36352
|
+
let ref;
|
|
36353
|
+
try {
|
|
36354
|
+
ref = validateRefName(requestedRef);
|
|
36355
|
+
} catch (error2) {
|
|
36356
|
+
if (error2 instanceof ReviewRefError) {
|
|
36357
|
+
throw new ReviewBaseError("invalid_base", error2.message);
|
|
36358
|
+
}
|
|
36359
|
+
throw error2;
|
|
36360
|
+
}
|
|
36361
|
+
const resolved = await readOrNull(captured, [
|
|
36362
|
+
"rev-parse",
|
|
36363
|
+
"--verify",
|
|
36364
|
+
"--quiet",
|
|
36365
|
+
"--end-of-options",
|
|
36366
|
+
`${ref}^{commit}`
|
|
36367
|
+
]);
|
|
36368
|
+
if (resolved === null || !isCommitOid(resolved)) {
|
|
36369
|
+
throw new ReviewBaseError(
|
|
36370
|
+
"unknown_base",
|
|
36371
|
+
`Comparison base "${ref}" does not name a commit in this repository.`
|
|
36372
|
+
);
|
|
36373
|
+
}
|
|
36374
|
+
return { requestedRef: ref, resolvedOid: resolved };
|
|
36375
|
+
}
|
|
36376
|
+
async function readOrNull(captured, args) {
|
|
36377
|
+
try {
|
|
36378
|
+
const output = await captured.runGit(args);
|
|
36379
|
+
const trimmed = output.trim();
|
|
36380
|
+
return trimmed.length === 0 ? null : trimmed;
|
|
36381
|
+
} catch (error2) {
|
|
36382
|
+
const code = error2.code;
|
|
36383
|
+
if (code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || code === "ETIMEDOUT" || error2.killed) {
|
|
36384
|
+
throw new ReviewBaseError("git_output_limit", "Repository identity exceeds its bound.");
|
|
36385
|
+
}
|
|
36386
|
+
return null;
|
|
36387
|
+
}
|
|
36388
|
+
}
|
|
36389
|
+
|
|
36390
|
+
// ../daemon/src/lib/git-review/scope.ts
|
|
36391
|
+
var BAZILION_STATE_PREFIXES = ["memory/"];
|
|
36392
|
+
var CREDENTIAL_SEGMENTS = /* @__PURE__ */ new Set([".ssh", ".gnupg", ".aws", ".docker"]);
|
|
36393
|
+
var CREDENTIAL_BASENAME_PATTERNS = [
|
|
36394
|
+
/^\.env(\..*)?$/,
|
|
36395
|
+
/^\.netrc$/,
|
|
36396
|
+
/^_netrc$/,
|
|
36397
|
+
/^\.npmrc$/,
|
|
36398
|
+
/^\.pgpass$/,
|
|
36399
|
+
/^\.git-credentials$/,
|
|
36400
|
+
/^credentials(\..*)?$/,
|
|
36401
|
+
/^secrets(\..*)?$/,
|
|
36402
|
+
/^id_(rsa|dsa|ecdsa|ed25519)(\.pub)?$/
|
|
36403
|
+
];
|
|
36404
|
+
var CREDENTIAL_EXTENSIONS = [
|
|
36405
|
+
".pem",
|
|
36406
|
+
".key",
|
|
36407
|
+
".p12",
|
|
36408
|
+
".pfx",
|
|
36409
|
+
".jks",
|
|
36410
|
+
".keystore",
|
|
36411
|
+
".kdbx",
|
|
36412
|
+
".ppk",
|
|
36413
|
+
".asc"
|
|
36414
|
+
];
|
|
36415
|
+
function reviewScope(path) {
|
|
36416
|
+
const segments = path.split("/");
|
|
36417
|
+
const basename6 = segments[segments.length - 1] ?? "";
|
|
36418
|
+
for (const prefix of BAZILION_STATE_PREFIXES) {
|
|
36419
|
+
if (path === prefix.slice(0, -1) || path.startsWith(prefix)) {
|
|
36420
|
+
return { included: false, reason: "bazilion_state" };
|
|
36421
|
+
}
|
|
36422
|
+
}
|
|
36423
|
+
if (segments.some((segment) => CREDENTIAL_SEGMENTS.has(segment.toLowerCase()))) {
|
|
36424
|
+
return { included: false, reason: "credential_shaped" };
|
|
36425
|
+
}
|
|
36426
|
+
const lower = basename6.toLowerCase();
|
|
36427
|
+
if (CREDENTIAL_BASENAME_PATTERNS.some((pattern) => pattern.test(lower))) {
|
|
36428
|
+
return { included: false, reason: "credential_shaped" };
|
|
36429
|
+
}
|
|
36430
|
+
if (CREDENTIAL_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
|
|
36431
|
+
return { included: false, reason: "credential_shaped" };
|
|
36432
|
+
}
|
|
36433
|
+
return { included: true };
|
|
36434
|
+
}
|
|
36435
|
+
|
|
36436
|
+
// ../daemon/src/lib/git-review/changes.ts
|
|
36437
|
+
async function listChanges(captured, base, identity2, limits = REVIEW_LIMITS) {
|
|
36438
|
+
const issues = [];
|
|
36439
|
+
const raw2 = await readDiff(captured, base, [
|
|
36440
|
+
"--raw",
|
|
36441
|
+
"-M",
|
|
36442
|
+
"--no-ext-diff",
|
|
36443
|
+
"--no-textconv",
|
|
36444
|
+
"-z"
|
|
36445
|
+
]);
|
|
36446
|
+
const numstat = await readDiff(captured, base, [
|
|
36447
|
+
"--numstat",
|
|
36448
|
+
"-M",
|
|
36449
|
+
"--no-ext-diff",
|
|
36450
|
+
"--no-textconv",
|
|
36451
|
+
"-z"
|
|
36452
|
+
]);
|
|
36453
|
+
const counts = parseNumstat(numstat);
|
|
36454
|
+
const entries = [];
|
|
36455
|
+
let excludedTracked = 0;
|
|
36456
|
+
for (const header of parseRaw(raw2)) {
|
|
36457
|
+
const scoped = reviewScope(header.path);
|
|
36458
|
+
if (!scoped.included) excludedTracked++;
|
|
36459
|
+
const count3 = counts.get(header.path) ?? null;
|
|
36460
|
+
entries.push({
|
|
36461
|
+
path: header.path,
|
|
36462
|
+
previousPath: header.previousPath,
|
|
36463
|
+
status: header.status,
|
|
36464
|
+
binary: count3?.binary ?? false,
|
|
36465
|
+
addedLines: count3?.added ?? null,
|
|
36466
|
+
deletedLines: count3?.deleted ?? null,
|
|
36467
|
+
oldMode: header.oldMode,
|
|
36468
|
+
newMode: header.newMode,
|
|
36469
|
+
patch: null,
|
|
36470
|
+
patchTruncated: false,
|
|
36471
|
+
// A credential-shaped or Bazilion-owned tracked file may be listed, but never captured.
|
|
36472
|
+
contentOmitted: scoped.included ? count3?.binary ? "binary" : null : "excluded",
|
|
36473
|
+
excludedReason: scoped.included ? null : scoped.reason
|
|
36474
|
+
});
|
|
36475
|
+
}
|
|
36476
|
+
const untrackedNames = parseNameList(
|
|
36477
|
+
await captured.runGit(["ls-files", "--others", "--exclude-standard", "-z"])
|
|
36478
|
+
);
|
|
36479
|
+
let excludedUntracked = 0;
|
|
36480
|
+
for (const path of untrackedNames) {
|
|
36481
|
+
const scoped = reviewScope(path);
|
|
36482
|
+
if (!scoped.included) {
|
|
36483
|
+
excludedUntracked++;
|
|
36484
|
+
continue;
|
|
36485
|
+
}
|
|
36486
|
+
entries.push({
|
|
36487
|
+
path,
|
|
36488
|
+
previousPath: null,
|
|
36489
|
+
status: "untracked",
|
|
36490
|
+
binary: false,
|
|
36491
|
+
addedLines: null,
|
|
36492
|
+
deletedLines: null,
|
|
36493
|
+
oldMode: null,
|
|
36494
|
+
newMode: null,
|
|
36495
|
+
patch: null,
|
|
36496
|
+
patchTruncated: false,
|
|
36497
|
+
contentOmitted: "untracked_not_selected",
|
|
36498
|
+
excludedReason: null
|
|
36499
|
+
});
|
|
36500
|
+
}
|
|
36501
|
+
entries.sort((a, b2) => a.path < b2.path ? -1 : a.path > b2.path ? 1 : 0);
|
|
36502
|
+
const truncated = entries.length > limits.files;
|
|
36503
|
+
const kept = truncated ? entries.slice(0, limits.files) : entries;
|
|
36504
|
+
if (truncated) {
|
|
36505
|
+
issues.push({
|
|
36506
|
+
code: "file_limit",
|
|
36507
|
+
message: `The change list exceeded ${limits.files} files; it is incomplete.`
|
|
36508
|
+
});
|
|
36509
|
+
}
|
|
36510
|
+
return {
|
|
36511
|
+
base,
|
|
36512
|
+
identity: identity2,
|
|
36513
|
+
changes: kept,
|
|
36514
|
+
truncated,
|
|
36515
|
+
// Counts of what was *not* captured, so an omission is never invisible. Untracked
|
|
36516
|
+
// credential-shaped names are counted here but never listed by name.
|
|
36517
|
+
withheld: {
|
|
36518
|
+
untracked: excludedUntracked,
|
|
36519
|
+
excluded: excludedTracked,
|
|
36520
|
+
binary: kept.filter((change) => change.binary).length,
|
|
36521
|
+
tooLarge: 0
|
|
36522
|
+
},
|
|
36523
|
+
issues
|
|
36524
|
+
};
|
|
36525
|
+
}
|
|
36526
|
+
async function readChangePatch(captured, base, change, patchBytes = REVIEW_LIMITS.patchBytes) {
|
|
36527
|
+
if (change.excludedReason) return { patch: null, truncated: false, omitted: "excluded" };
|
|
36528
|
+
if (change.binary) return { patch: null, truncated: false, omitted: "binary" };
|
|
36529
|
+
if (change.status === "untracked") {
|
|
36530
|
+
return { patch: null, truncated: false, omitted: "untracked_not_selected" };
|
|
36531
|
+
}
|
|
36532
|
+
const target = change.status === "deleted" ? change.previousPath ?? change.path : change.path;
|
|
36533
|
+
let output;
|
|
36534
|
+
try {
|
|
36535
|
+
output = await captured.runGit([
|
|
36536
|
+
"diff",
|
|
36537
|
+
"--no-ext-diff",
|
|
36538
|
+
"--no-textconv",
|
|
36539
|
+
"--no-color",
|
|
36540
|
+
"--unified=3",
|
|
36541
|
+
"-M",
|
|
36542
|
+
base.resolvedOid,
|
|
36543
|
+
"--",
|
|
36544
|
+
target
|
|
36545
|
+
]);
|
|
36546
|
+
} catch (error2) {
|
|
36547
|
+
if (error2 instanceof ReviewBaseError) throw error2;
|
|
36548
|
+
return { patch: null, truncated: false, omitted: "unavailable" };
|
|
36549
|
+
}
|
|
36550
|
+
if (output.length <= patchBytes) return { patch: output, truncated: false, omitted: null };
|
|
36551
|
+
return {
|
|
36552
|
+
patch: `${output.slice(0, patchBytes)}
|
|
36553
|
+
[truncated: diff exceeds ${patchBytes} bytes]`,
|
|
36554
|
+
truncated: true,
|
|
36555
|
+
omitted: null
|
|
36556
|
+
};
|
|
36557
|
+
}
|
|
36558
|
+
async function attachPatches(captured, report, limits = REVIEW_LIMITS, onlyPath) {
|
|
36559
|
+
let total = 0;
|
|
36560
|
+
let tooLarge = 0;
|
|
36561
|
+
const changes = [];
|
|
36562
|
+
for (const change of report.changes) {
|
|
36563
|
+
if (onlyPath !== void 0 && change.path !== onlyPath) {
|
|
36564
|
+
changes.push(change);
|
|
36565
|
+
continue;
|
|
36566
|
+
}
|
|
36567
|
+
if (change.contentOmitted && change.contentOmitted !== "binary") {
|
|
36568
|
+
changes.push(change);
|
|
36569
|
+
continue;
|
|
36570
|
+
}
|
|
36571
|
+
if (total >= limits.totalBytes) {
|
|
36572
|
+
changes.push({ ...change, contentOmitted: "total_limit" });
|
|
36573
|
+
continue;
|
|
36574
|
+
}
|
|
36575
|
+
const read = await readChangePatch(captured, report.base, change, limits.patchBytes);
|
|
36576
|
+
const bytes = read.patch ? Buffer.byteLength(read.patch) : 0;
|
|
36577
|
+
if (bytes > limits.fileBytes && !read.truncated) {
|
|
36578
|
+
tooLarge++;
|
|
36579
|
+
changes.push({ ...change, patch: null, contentOmitted: "too_large" });
|
|
36580
|
+
continue;
|
|
36581
|
+
}
|
|
36582
|
+
total += bytes;
|
|
36583
|
+
changes.push({
|
|
36584
|
+
...change,
|
|
36585
|
+
patch: read.patch,
|
|
36586
|
+
patchTruncated: read.truncated,
|
|
36587
|
+
contentOmitted: read.omitted
|
|
36588
|
+
});
|
|
36589
|
+
}
|
|
36590
|
+
return { ...report, changes, withheld: { ...report.withheld, tooLarge } };
|
|
36591
|
+
}
|
|
36592
|
+
async function readDiff(captured, base, args) {
|
|
36593
|
+
return captured.runGit(["diff", base.resolvedOid, ...args]);
|
|
36594
|
+
}
|
|
36595
|
+
function parseNameList(output) {
|
|
36596
|
+
return output.split("\0").filter((name) => name.length > 0);
|
|
36597
|
+
}
|
|
36598
|
+
var RAW_HEADER = /^:(\d{6}) (\d{6}) ([0-9a-f]+) ([0-9a-f]+) ([A-Z])(\d*)$/;
|
|
36599
|
+
function parseRaw(output) {
|
|
36600
|
+
const records2 = output.split("\0");
|
|
36601
|
+
const headers = [];
|
|
36602
|
+
for (let i = 0; i < records2.length; i++) {
|
|
36603
|
+
const record3 = records2[i] ?? "";
|
|
36604
|
+
if (record3.length === 0) continue;
|
|
36605
|
+
const match = RAW_HEADER.exec(record3);
|
|
36606
|
+
if (!match) continue;
|
|
36607
|
+
const status2 = statusFrom(match[5] ?? "");
|
|
36608
|
+
const paired = status2 === "renamed" || status2 === "copied";
|
|
36609
|
+
const first = records2[++i] ?? "";
|
|
36610
|
+
if (paired) {
|
|
36611
|
+
const second = records2[++i] ?? "";
|
|
36612
|
+
if (first.length === 0 || second.length === 0) continue;
|
|
36613
|
+
headers.push({
|
|
36614
|
+
path: second,
|
|
36615
|
+
previousPath: first,
|
|
36616
|
+
status: status2,
|
|
36617
|
+
oldMode: match[1] ?? null,
|
|
36618
|
+
newMode: match[2] ?? null
|
|
36619
|
+
});
|
|
36620
|
+
continue;
|
|
36621
|
+
}
|
|
36622
|
+
if (first.length === 0) continue;
|
|
36623
|
+
headers.push({
|
|
36624
|
+
path: first,
|
|
36625
|
+
previousPath: null,
|
|
36626
|
+
status: status2,
|
|
36627
|
+
oldMode: match[1] ?? null,
|
|
36628
|
+
newMode: match[2] ?? null
|
|
36629
|
+
});
|
|
36630
|
+
}
|
|
36631
|
+
return headers;
|
|
36632
|
+
}
|
|
36633
|
+
function statusFrom(letter) {
|
|
36634
|
+
switch (letter) {
|
|
36635
|
+
case "A":
|
|
36636
|
+
return "added";
|
|
36637
|
+
case "M":
|
|
36638
|
+
return "modified";
|
|
36639
|
+
case "D":
|
|
36640
|
+
return "deleted";
|
|
36641
|
+
case "R":
|
|
36642
|
+
return "renamed";
|
|
36643
|
+
case "C":
|
|
36644
|
+
return "copied";
|
|
36645
|
+
case "T":
|
|
36646
|
+
return "type_changed";
|
|
36647
|
+
case "U":
|
|
36648
|
+
return "unmerged";
|
|
36649
|
+
default:
|
|
36650
|
+
return "unknown";
|
|
36651
|
+
}
|
|
36652
|
+
}
|
|
36653
|
+
function parseNumstat(output) {
|
|
36654
|
+
const records2 = output.split("\0");
|
|
36655
|
+
const counts = /* @__PURE__ */ new Map();
|
|
36656
|
+
for (let i = 0; i < records2.length; i++) {
|
|
36657
|
+
const record3 = records2[i] ?? "";
|
|
36658
|
+
if (record3.length === 0) continue;
|
|
36659
|
+
const firstTab = record3.indexOf(" ");
|
|
36660
|
+
if (firstTab < 0) continue;
|
|
36661
|
+
const secondTab = record3.indexOf(" ", firstTab + 1);
|
|
36662
|
+
if (secondTab < 0) continue;
|
|
36663
|
+
const addedField = record3.slice(0, firstTab);
|
|
36664
|
+
const deletedField = record3.slice(firstTab + 1, secondTab);
|
|
36665
|
+
let path = record3.slice(secondTab + 1);
|
|
36666
|
+
if (path.length === 0) {
|
|
36667
|
+
i++;
|
|
36668
|
+
path = records2[i + 1] ?? "";
|
|
36669
|
+
i++;
|
|
36670
|
+
if (path.length === 0) continue;
|
|
36671
|
+
}
|
|
36672
|
+
const binary = addedField === "-" || deletedField === "-";
|
|
36673
|
+
counts.set(path, {
|
|
36674
|
+
added: binary ? null : Number.parseInt(addedField, 10),
|
|
36675
|
+
deleted: binary ? null : Number.parseInt(deletedField, 10),
|
|
36676
|
+
binary
|
|
36677
|
+
});
|
|
36678
|
+
}
|
|
36679
|
+
return counts;
|
|
36680
|
+
}
|
|
36681
|
+
|
|
36682
|
+
// ../daemon/src/lib/git-review/snapshot.ts
|
|
36683
|
+
function snapshotReference(snapshot) {
|
|
36684
|
+
return { id: snapshot.id, complete: snapshot.complete, capturedAt: snapshot.capturedAt };
|
|
36685
|
+
}
|
|
36686
|
+
function compareSnapshots(before, after) {
|
|
36687
|
+
if (!before.complete || !after.complete) return "unknown";
|
|
36688
|
+
return before.id === after.id ? "identical" : "changed";
|
|
36689
|
+
}
|
|
36690
|
+
async function captureSourceSnapshot(captured, base, identity2, input = {}) {
|
|
36691
|
+
const limits = input.limits ?? REVIEW_LIMITS;
|
|
36692
|
+
const issues = [];
|
|
36693
|
+
const withheld = { excluded: 0, tooLarge: 0, notRegular: 0, unstable: 0 };
|
|
36694
|
+
let incomplete = false;
|
|
36695
|
+
const fail3 = (code, message) => {
|
|
36696
|
+
incomplete = true;
|
|
36697
|
+
issues.push({ code, message });
|
|
36698
|
+
};
|
|
36699
|
+
const index = await readIndexDigest(captured);
|
|
36700
|
+
if (index === null) fail3("unstable", "The index could not be read at snapshot time.");
|
|
36701
|
+
if (index && index.entries > limits.files)
|
|
36702
|
+
fail3(
|
|
36703
|
+
"file_limit",
|
|
36704
|
+
`The index holds more than ${limits.files} entries; the snapshot is incomplete.`
|
|
36705
|
+
);
|
|
36706
|
+
const changes = await listChanges(captured, base, identity2, limits);
|
|
36707
|
+
if (changes.truncated)
|
|
36708
|
+
fail3("file_limit", "The change list was truncated; the snapshot is incomplete.");
|
|
36709
|
+
const entries = [];
|
|
36710
|
+
let totalBytes = 0;
|
|
36711
|
+
for (const change of changes.changes) {
|
|
36712
|
+
if (change.status === "untracked") continue;
|
|
36713
|
+
if (change.path === ".git") continue;
|
|
36714
|
+
if (change.status === "deleted") {
|
|
36715
|
+
entries.push({
|
|
36716
|
+
path: change.path,
|
|
36717
|
+
layer: "worktree",
|
|
36718
|
+
kind: "deleted",
|
|
36719
|
+
digest: null,
|
|
36720
|
+
bytes: null
|
|
36721
|
+
});
|
|
36722
|
+
continue;
|
|
36723
|
+
}
|
|
36724
|
+
const read = readWorktreeFile(captured.root, change.path, limits.fileBytes);
|
|
36725
|
+
switch (read.kind) {
|
|
36726
|
+
case "file":
|
|
36727
|
+
totalBytes += read.bytes;
|
|
36728
|
+
if (totalBytes > limits.totalBytes) {
|
|
36729
|
+
fail3(
|
|
36730
|
+
"file_limit",
|
|
36731
|
+
`Captured content exceeded ${limits.totalBytes} bytes; the snapshot is incomplete.`
|
|
36732
|
+
);
|
|
36733
|
+
entries.push({
|
|
36734
|
+
path: change.path,
|
|
36735
|
+
layer: "worktree",
|
|
36736
|
+
kind: "too_large",
|
|
36737
|
+
digest: null,
|
|
36738
|
+
bytes: read.bytes
|
|
36739
|
+
});
|
|
36740
|
+
break;
|
|
36741
|
+
}
|
|
36742
|
+
entries.push({
|
|
36743
|
+
path: change.path,
|
|
36744
|
+
layer: "worktree",
|
|
36745
|
+
kind: "file",
|
|
36746
|
+
digest: read.digest,
|
|
36747
|
+
bytes: read.bytes
|
|
36748
|
+
});
|
|
36749
|
+
break;
|
|
36750
|
+
case "deleted":
|
|
36751
|
+
entries.push({
|
|
36752
|
+
path: change.path,
|
|
36753
|
+
layer: "worktree",
|
|
36754
|
+
kind: "deleted",
|
|
36755
|
+
digest: null,
|
|
36756
|
+
bytes: null
|
|
36757
|
+
});
|
|
36758
|
+
break;
|
|
36759
|
+
case "too_large":
|
|
36760
|
+
withheld.tooLarge++;
|
|
36761
|
+
fail3(
|
|
36762
|
+
"file_limit",
|
|
36763
|
+
`A changed file exceeds ${limits.fileBytes} bytes; the snapshot is incomplete.`
|
|
36764
|
+
);
|
|
36765
|
+
entries.push({
|
|
36766
|
+
path: change.path,
|
|
36767
|
+
layer: "worktree",
|
|
36768
|
+
kind: "too_large",
|
|
36769
|
+
digest: null,
|
|
36770
|
+
bytes: null
|
|
36771
|
+
});
|
|
36772
|
+
break;
|
|
36773
|
+
case "not_regular":
|
|
36774
|
+
withheld.notRegular++;
|
|
36775
|
+
fail3(
|
|
36776
|
+
"unstable",
|
|
36777
|
+
"A changed path is not a regular file (symlink or special); its content cannot be fingerprinted."
|
|
36778
|
+
);
|
|
36779
|
+
entries.push({
|
|
36780
|
+
path: change.path,
|
|
36781
|
+
layer: "worktree",
|
|
36782
|
+
kind: "not_regular",
|
|
36783
|
+
digest: null,
|
|
36784
|
+
bytes: null
|
|
36785
|
+
});
|
|
36786
|
+
break;
|
|
36787
|
+
case "unstable":
|
|
36788
|
+
withheld.unstable++;
|
|
36789
|
+
fail3(
|
|
36790
|
+
"unstable",
|
|
36791
|
+
"A changed file changed while it was being read; the snapshot is incomplete."
|
|
36792
|
+
);
|
|
36793
|
+
entries.push({
|
|
36794
|
+
path: change.path,
|
|
36795
|
+
layer: "worktree",
|
|
36796
|
+
kind: "unstable",
|
|
36797
|
+
digest: null,
|
|
36798
|
+
bytes: null
|
|
36799
|
+
});
|
|
36800
|
+
break;
|
|
36801
|
+
}
|
|
36802
|
+
}
|
|
36803
|
+
const untrackedIncluded = [];
|
|
36804
|
+
const exclusions = [];
|
|
36805
|
+
for (const path of [...new Set(input.includeUntracked ?? [])].sort()) {
|
|
36806
|
+
const scoped = reviewScope(path);
|
|
36807
|
+
if (!scoped.included) {
|
|
36808
|
+
exclusions.push({ path, reason: scoped.reason });
|
|
36809
|
+
withheld.excluded++;
|
|
36810
|
+
continue;
|
|
36811
|
+
}
|
|
36812
|
+
if (totalBytes >= limits.totalBytes) {
|
|
36813
|
+
fail3(
|
|
36814
|
+
"file_limit",
|
|
36815
|
+
`Captured content exceeded ${limits.totalBytes} bytes; the snapshot is incomplete.`
|
|
36816
|
+
);
|
|
36817
|
+
continue;
|
|
36818
|
+
}
|
|
36819
|
+
const read = readWorktreeFile(captured.root, path, limits.fileBytes);
|
|
36820
|
+
if (read.kind === "file") {
|
|
36821
|
+
totalBytes += read.bytes;
|
|
36822
|
+
untrackedIncluded.push(path);
|
|
36823
|
+
entries.push({
|
|
36824
|
+
path,
|
|
36825
|
+
layer: "untracked",
|
|
36826
|
+
kind: "file",
|
|
36827
|
+
digest: read.digest,
|
|
36828
|
+
bytes: read.bytes
|
|
36829
|
+
});
|
|
36830
|
+
continue;
|
|
36831
|
+
}
|
|
36832
|
+
if (read.kind === "too_large") withheld.tooLarge++;
|
|
36833
|
+
else if (read.kind === "not_regular") withheld.notRegular++;
|
|
36834
|
+
else withheld.unstable++;
|
|
36835
|
+
fail3(
|
|
36836
|
+
read.kind === "too_large" ? "file_limit" : "unstable",
|
|
36837
|
+
`Included untracked path "${path}" could not be fingerprinted (${read.kind}); the snapshot is incomplete.`
|
|
36838
|
+
);
|
|
36839
|
+
entries.push({
|
|
36840
|
+
path,
|
|
36841
|
+
layer: "untracked",
|
|
36842
|
+
kind: read.kind === "too_large" ? "too_large" : read.kind === "not_regular" ? "not_regular" : "unstable",
|
|
36843
|
+
digest: null,
|
|
36844
|
+
bytes: null
|
|
36845
|
+
});
|
|
36846
|
+
}
|
|
36847
|
+
entries.sort(
|
|
36848
|
+
(a, b2) => a.layer === b2.layer ? a.path < b2.path ? -1 : a.path > b2.path ? 1 : 0 : a.layer < b2.layer ? -1 : 1
|
|
36849
|
+
);
|
|
36850
|
+
exclusions.sort((a, b2) => a.path < b2.path ? -1 : a.path > b2.path ? 1 : 0);
|
|
36851
|
+
const canonical = {
|
|
36852
|
+
identity: identity2,
|
|
36853
|
+
base: base.resolvedOid,
|
|
36854
|
+
head: index?.head ?? null,
|
|
36855
|
+
indexDigest: index?.digest ?? null,
|
|
36856
|
+
indexEntries: index?.entries ?? 0,
|
|
36857
|
+
entries: entries.map((entry) => [
|
|
36858
|
+
entry.layer,
|
|
36859
|
+
entry.path,
|
|
36860
|
+
entry.kind,
|
|
36861
|
+
entry.digest,
|
|
36862
|
+
entry.bytes
|
|
36863
|
+
]),
|
|
36864
|
+
untrackedIncluded,
|
|
36865
|
+
exclusions: exclusions.map((exclusion) => [exclusion.path, exclusion.reason]),
|
|
36866
|
+
withheld,
|
|
36867
|
+
limits,
|
|
36868
|
+
complete: !incomplete
|
|
36869
|
+
};
|
|
36870
|
+
return {
|
|
36871
|
+
id: hash3(JSON.stringify(canonical)),
|
|
36872
|
+
capturedAt: Date.now(),
|
|
36873
|
+
complete: !incomplete,
|
|
36874
|
+
identity: identity2,
|
|
36875
|
+
base,
|
|
36876
|
+
head: canonical.head,
|
|
36877
|
+
indexDigest: canonical.indexDigest,
|
|
36878
|
+
indexEntries: canonical.indexEntries,
|
|
36879
|
+
entries,
|
|
36880
|
+
untrackedIncluded,
|
|
36881
|
+
exclusions,
|
|
36882
|
+
withheld,
|
|
36883
|
+
limits,
|
|
36884
|
+
issues
|
|
36885
|
+
};
|
|
36886
|
+
}
|
|
36887
|
+
async function readIndexDigest(captured) {
|
|
36888
|
+
try {
|
|
36889
|
+
const output = await captured.runGit(["ls-files", "--stage", "-z"]);
|
|
36890
|
+
const lines = [];
|
|
36891
|
+
let entries = 0;
|
|
36892
|
+
for (const record3 of output.split("\0")) {
|
|
36893
|
+
if (record3.length === 0) continue;
|
|
36894
|
+
const tab = record3.indexOf(" ");
|
|
36895
|
+
if (tab < 0) continue;
|
|
36896
|
+
const meta2 = record3.slice(0, tab);
|
|
36897
|
+
const path = record3.slice(tab + 1);
|
|
36898
|
+
if (path.length === 0) continue;
|
|
36899
|
+
entries++;
|
|
36900
|
+
lines.push(`${meta2} ${path}`);
|
|
36901
|
+
}
|
|
36902
|
+
lines.sort();
|
|
36903
|
+
const head = (await captured.runGit(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"])).trim();
|
|
36904
|
+
return {
|
|
36905
|
+
digest: hash3(lines.join("\n")),
|
|
36906
|
+
entries,
|
|
36907
|
+
head: /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(head) ? head : null
|
|
36908
|
+
};
|
|
36909
|
+
} catch {
|
|
36910
|
+
return null;
|
|
36911
|
+
}
|
|
36912
|
+
}
|
|
36913
|
+
function readWorktreeFile(root, path, maxBytes) {
|
|
36914
|
+
let parts;
|
|
36915
|
+
try {
|
|
36916
|
+
parts = relativeParts(path);
|
|
36917
|
+
} catch {
|
|
36918
|
+
return { kind: "not_regular" };
|
|
36919
|
+
}
|
|
36920
|
+
if (parts.length === 0) return { kind: "not_regular" };
|
|
36921
|
+
let current = root;
|
|
36922
|
+
try {
|
|
36923
|
+
for (let i = 0; i < parts.length; i++) {
|
|
36924
|
+
const part = parts[i];
|
|
36925
|
+
const entry = current.entry(part);
|
|
36926
|
+
if (!entry) return { kind: "deleted" };
|
|
36927
|
+
if (entry.isSymbolicLink()) return { kind: "not_regular" };
|
|
36928
|
+
const last = i === parts.length - 1;
|
|
36929
|
+
if (last) {
|
|
36930
|
+
if (!entry.isFile()) return { kind: "not_regular" };
|
|
36931
|
+
if (entry.size > maxBytes) return { kind: "too_large" };
|
|
36932
|
+
const bytes = current.read(part, maxBytes);
|
|
36933
|
+
if (!bytes) return { kind: "unstable" };
|
|
36934
|
+
return { kind: "file", digest: hash3(bytes), bytes: bytes.length };
|
|
36935
|
+
}
|
|
36936
|
+
if (!entry.isDirectory()) return { kind: "not_regular" };
|
|
36937
|
+
current = current.directory(part);
|
|
36938
|
+
}
|
|
36939
|
+
} catch {
|
|
36940
|
+
return { kind: "unstable" };
|
|
36941
|
+
}
|
|
36942
|
+
return { kind: "not_regular" };
|
|
36943
|
+
}
|
|
36944
|
+
|
|
36945
|
+
// ../daemon/src/lib/git-review/service.ts
|
|
36946
|
+
var ReviewUnavailableError = class extends Error {
|
|
36947
|
+
code;
|
|
36948
|
+
constructor(code, message) {
|
|
36949
|
+
super(message);
|
|
36950
|
+
this.name = "ReviewUnavailableError";
|
|
36951
|
+
this.code = code;
|
|
36952
|
+
}
|
|
36953
|
+
};
|
|
36954
|
+
function requireTeam(db, paths, teamId) {
|
|
36955
|
+
const team = get3(db, teamId, paths);
|
|
36956
|
+
if (!team) throw new ReviewUnavailableError("team_not_found", "Team not found.");
|
|
36957
|
+
return { id: team.id, path: team.path };
|
|
36958
|
+
}
|
|
36959
|
+
async function withRepository(teamDir, fn) {
|
|
36960
|
+
let directory;
|
|
36961
|
+
try {
|
|
36962
|
+
directory = new ContextDirectory(teamDir);
|
|
36963
|
+
} catch (error2) {
|
|
36964
|
+
throw new ReviewUnavailableError(
|
|
36965
|
+
"unsupported_layout",
|
|
36966
|
+
error2 instanceof ContextReadError ? `This Team's workspace cannot be inspected safely (${error2.code}).` : "This Team workspace cannot be inspected."
|
|
36967
|
+
);
|
|
36968
|
+
}
|
|
36969
|
+
try {
|
|
36970
|
+
const root = findRepositoryRoot([directory]);
|
|
36971
|
+
if (!root) {
|
|
36972
|
+
throw new ReviewUnavailableError("not_repository", "This Team is not a Git repository.");
|
|
35769
36973
|
}
|
|
35770
|
-
|
|
35771
|
-
|
|
35772
|
-
|
|
35773
|
-
|
|
35774
|
-
|
|
35775
|
-
|
|
35776
|
-
|
|
35777
|
-
|
|
35778
|
-
|
|
35779
|
-
|
|
35780
|
-
|
|
35781
|
-
|
|
35782
|
-
|
|
35783
|
-
|
|
35784
|
-
|
|
35785
|
-
|
|
35786
|
-
|
|
35787
|
-
|
|
35788
|
-
|
|
35789
|
-
|
|
35790
|
-
|
|
36974
|
+
let captured;
|
|
36975
|
+
try {
|
|
36976
|
+
captured = await captureRepositoryGit(root);
|
|
36977
|
+
} catch (error2) {
|
|
36978
|
+
throw new ReviewUnavailableError(
|
|
36979
|
+
"unsupported_layout",
|
|
36980
|
+
error2 instanceof ContextReadError ? `This repository layout is not supported (${error2.code}).` : "This repository cannot be inspected."
|
|
36981
|
+
);
|
|
36982
|
+
}
|
|
36983
|
+
try {
|
|
36984
|
+
return await fn(captured);
|
|
36985
|
+
} finally {
|
|
36986
|
+
captured.cleanup();
|
|
36987
|
+
}
|
|
36988
|
+
} finally {
|
|
36989
|
+
directory.close();
|
|
36990
|
+
}
|
|
36991
|
+
}
|
|
36992
|
+
async function readTeamReview(db, paths, teamId, input = {}) {
|
|
36993
|
+
const team = requireTeam(db, paths, teamId);
|
|
36994
|
+
const changes = await withRepository(team.path, async (captured) => {
|
|
36995
|
+
const base = await resolveComparisonBase(captured, input.base ?? "HEAD");
|
|
36996
|
+
const identity2 = await readRepositoryIdentity(captured);
|
|
36997
|
+
const listed = await listChanges(captured, base, identity2, input.limits);
|
|
36998
|
+
return input.patches ? attachPatches(captured, listed, input.limits, input.patchPath) : listed;
|
|
36999
|
+
});
|
|
37000
|
+
return { teamId: team.id, changes };
|
|
37001
|
+
}
|
|
37002
|
+
async function captureTeamSnapshot(db, paths, teamId, input) {
|
|
37003
|
+
const team = requireTeam(db, paths, teamId);
|
|
37004
|
+
const snapshot = await withRepository(team.path, async (captured) => {
|
|
37005
|
+
const base = await resolveComparisonBase(captured, input.base ?? "HEAD");
|
|
37006
|
+
const identity2 = await readRepositoryIdentity(captured);
|
|
37007
|
+
return captureSourceSnapshot(captured, base, identity2, {
|
|
37008
|
+
...input.includeUntracked ? { includeUntracked: input.includeUntracked } : {},
|
|
37009
|
+
...input.limits ? { limits: input.limits } : {}
|
|
37010
|
+
});
|
|
37011
|
+
});
|
|
37012
|
+
saveSourceSnapshot(db, {
|
|
37013
|
+
snapshotId: snapshot.id,
|
|
37014
|
+
teamId: team.id,
|
|
37015
|
+
capturedBy: input.capturedBy,
|
|
37016
|
+
agentId: input.agentId ?? null,
|
|
37017
|
+
turnId: input.turnId ?? null,
|
|
37018
|
+
toolCallId: input.toolCallId ?? null,
|
|
37019
|
+
complete: snapshot.complete,
|
|
37020
|
+
head: snapshot.head,
|
|
37021
|
+
baseOid: snapshot.base.resolvedOid,
|
|
37022
|
+
entryCount: snapshot.entries.length,
|
|
37023
|
+
capturedContentBytes: snapshot.entries.reduce((total, entry) => total + (entry.bytes ?? 0), 0),
|
|
37024
|
+
manifestJson: JSON.stringify(snapshot)
|
|
37025
|
+
});
|
|
37026
|
+
return { snapshot, reference: snapshotReference(snapshot) };
|
|
37027
|
+
}
|
|
37028
|
+
async function readSnapshotApplicability(db, paths, teamId, snapshotId) {
|
|
37029
|
+
const team = requireTeam(db, paths, teamId);
|
|
37030
|
+
const record3 = getSourceSnapshot(db, team.id, snapshotId);
|
|
37031
|
+
if (!record3) return { comparison: "unknown", reason: "no_snapshot", currentSnapshot: null };
|
|
37032
|
+
let stored;
|
|
37033
|
+
try {
|
|
37034
|
+
stored = JSON.parse(record3.manifestJson);
|
|
37035
|
+
} catch {
|
|
37036
|
+
return { comparison: "unknown", reason: "no_snapshot", currentSnapshot: null };
|
|
37037
|
+
}
|
|
37038
|
+
if (!stored.complete) {
|
|
37039
|
+
return { comparison: "unknown", reason: "incomplete_snapshot", currentSnapshot: null };
|
|
37040
|
+
}
|
|
37041
|
+
let current;
|
|
37042
|
+
try {
|
|
37043
|
+
current = await withRepository(team.path, async (captured) => {
|
|
37044
|
+
const base = await resolveComparisonBase(captured, "HEAD");
|
|
37045
|
+
const identity2 = await readRepositoryIdentity(captured);
|
|
37046
|
+
return captureSourceSnapshot(captured, base, identity2, {
|
|
37047
|
+
includeUntracked: stored.untrackedIncluded
|
|
37048
|
+
});
|
|
37049
|
+
});
|
|
37050
|
+
} catch {
|
|
37051
|
+
return { comparison: "unknown", reason: "capture_unavailable", currentSnapshot: null };
|
|
37052
|
+
}
|
|
37053
|
+
const comparison = compareSnapshots(stored, current);
|
|
37054
|
+
return {
|
|
37055
|
+
comparison,
|
|
37056
|
+
reason: comparison === "identical" ? "source_unchanged" : "source_changed",
|
|
37057
|
+
currentSnapshot: snapshotReference(current)
|
|
37058
|
+
};
|
|
37059
|
+
}
|
|
37060
|
+
function listTeamSnapshots(db, paths, teamId, limit = 50) {
|
|
37061
|
+
const team = requireTeam(db, paths, teamId);
|
|
37062
|
+
return listSourceSnapshots(db, team.id, limit).map(toSummary);
|
|
37063
|
+
}
|
|
37064
|
+
function readTeamSnapshot(db, paths, teamId, snapshotId) {
|
|
37065
|
+
const team = requireTeam(db, paths, teamId);
|
|
37066
|
+
const record3 = getSourceSnapshot(db, team.id, snapshotId);
|
|
37067
|
+
if (!record3) return null;
|
|
37068
|
+
try {
|
|
37069
|
+
return JSON.parse(record3.manifestJson);
|
|
37070
|
+
} catch {
|
|
37071
|
+
return null;
|
|
37072
|
+
}
|
|
37073
|
+
}
|
|
37074
|
+
function toSummary(record3) {
|
|
37075
|
+
return {
|
|
37076
|
+
snapshotId: record3.snapshotId,
|
|
37077
|
+
teamId: record3.teamId,
|
|
37078
|
+
capturedBy: record3.capturedBy,
|
|
37079
|
+
agentId: record3.agentId,
|
|
37080
|
+
turnId: record3.turnId,
|
|
37081
|
+
toolCallId: record3.toolCallId,
|
|
37082
|
+
complete: record3.complete,
|
|
37083
|
+
head: record3.head,
|
|
37084
|
+
baseOid: record3.baseOid,
|
|
37085
|
+
entryCount: record3.entryCount,
|
|
37086
|
+
capturedContentBytes: record3.capturedContentBytes,
|
|
37087
|
+
createdAt: record3.createdAt,
|
|
37088
|
+
expiresAt: record3.expiresAt
|
|
37089
|
+
};
|
|
37090
|
+
}
|
|
37091
|
+
|
|
37092
|
+
// ../daemon/src/lib/repository-context/git.ts
|
|
37093
|
+
var emptyGit = () => ({
|
|
37094
|
+
state: "not_repository",
|
|
37095
|
+
root: null,
|
|
37096
|
+
branch: null,
|
|
37097
|
+
head: null,
|
|
37098
|
+
headState: null,
|
|
37099
|
+
staged: 0,
|
|
37100
|
+
unstaged: 0,
|
|
37101
|
+
untracked: 0,
|
|
37102
|
+
conflicted: 0,
|
|
37103
|
+
issues: []
|
|
37104
|
+
});
|
|
37105
|
+
async function inspectGit(ancestry) {
|
|
37106
|
+
const report = emptyGit();
|
|
37107
|
+
let capture2 = null;
|
|
37108
|
+
try {
|
|
37109
|
+
const root = findRepositoryRoot(ancestry);
|
|
37110
|
+
if (!root) return report;
|
|
37111
|
+
report.root = root.label;
|
|
37112
|
+
report.state = "unavailable";
|
|
37113
|
+
capture2 = await captureRepositoryGit(root);
|
|
37114
|
+
const output = await capture2.runGit([
|
|
35791
37115
|
"status",
|
|
35792
37116
|
"--porcelain=v2",
|
|
35793
37117
|
"--branch",
|
|
@@ -35795,7 +37119,7 @@ objectformat = ${value}`
|
|
|
35795
37119
|
"--ignore-submodules=all",
|
|
35796
37120
|
"-z"
|
|
35797
37121
|
]);
|
|
35798
|
-
|
|
37122
|
+
capture2.validate();
|
|
35799
37123
|
const records2 = output.split("\0");
|
|
35800
37124
|
for (let i = 0; i < records2.length; i++) {
|
|
35801
37125
|
const line = records2[i] ?? "";
|
|
@@ -35824,7 +37148,7 @@ objectformat = ${value}`
|
|
|
35824
37148
|
message: `Git inspection is unavailable (${code.replaceAll("_", " ")}); repository instructions are evaluated separately.`
|
|
35825
37149
|
});
|
|
35826
37150
|
} finally {
|
|
35827
|
-
|
|
37151
|
+
capture2?.cleanup();
|
|
35828
37152
|
}
|
|
35829
37153
|
return report;
|
|
35830
37154
|
}
|
|
@@ -36030,6 +37354,19 @@ function createCodingHost(input) {
|
|
|
36030
37354
|
const active = /* @__PURE__ */ new Set();
|
|
36031
37355
|
const calls = /* @__PURE__ */ new Set();
|
|
36032
37356
|
let starting = false;
|
|
37357
|
+
let sourceBefore = null;
|
|
37358
|
+
function authorizeEvidence(id, messageId) {
|
|
37359
|
+
const receipt = getCodingCommand(input.db, id);
|
|
37360
|
+
if (agents_exports.get(input.db, input.agentId)?.teamId !== input.teamId || !receipt || receipt.teamId !== input.teamId || agents_exports.get(input.db, receipt.agentId)?.teamId !== input.teamId)
|
|
37361
|
+
throw new Error("Coding receipt unavailable");
|
|
37362
|
+
if (receipt.agentId === input.agentId) return { receipt, producer: true };
|
|
37363
|
+
const message = deliverableInbox(input.db, input.agentId, false).find(
|
|
37364
|
+
(candidate) => candidate.id === messageId
|
|
37365
|
+
);
|
|
37366
|
+
if (!message || message.fromAgentId !== receipt.agentId || !message.payload.includes(`coding-receipt:${receipt.id}`) || receipt.state === "running")
|
|
37367
|
+
throw new Error("Coding receipt requires an authorized producer message");
|
|
37368
|
+
return { receipt, producer: false };
|
|
37369
|
+
}
|
|
36033
37370
|
async function snapshot(target) {
|
|
36034
37371
|
input.assertActive();
|
|
36035
37372
|
codingRelativePath(target);
|
|
@@ -36087,6 +37424,33 @@ function createCodingHost(input) {
|
|
|
36087
37424
|
input.assertActive();
|
|
36088
37425
|
if (!raw2 || typeof raw2 !== "object") throw new Error("Invalid coding request");
|
|
36089
37426
|
if (raw2.action === "environment") return snapshot(raw2.target);
|
|
37427
|
+
if (raw2.action === "snapshot") {
|
|
37428
|
+
if (typeof raw2.toolCallId !== "string" || !raw2.toolCallId || raw2.toolCallId.length > 256 || calls.has(raw2.toolCallId))
|
|
37429
|
+
throw new Error("Invalid or repeated coding tool call");
|
|
37430
|
+
if (calls.size >= 64) throw new Error("Turn operation limit reached");
|
|
37431
|
+
calls.add(raw2.toolCallId);
|
|
37432
|
+
const requested = Array.isArray(raw2.includeUntracked) ? raw2.includeUntracked.filter((entry) => typeof entry === "string") : [];
|
|
37433
|
+
if (requested.length > 1e3 || requested.some((entry) => entry.length > 4096))
|
|
37434
|
+
throw new Error("Invalid untracked selection");
|
|
37435
|
+
const captured = await captureTeamSnapshot(input.db, input.paths, input.teamId, {
|
|
37436
|
+
capturedBy: "agent",
|
|
37437
|
+
agentId: input.agentId,
|
|
37438
|
+
turnId: input.turnId,
|
|
37439
|
+
toolCallId: raw2.toolCallId,
|
|
37440
|
+
...requested.length > 0 ? { includeUntracked: requested } : {}
|
|
37441
|
+
});
|
|
37442
|
+
sourceBefore ??= captured.reference;
|
|
37443
|
+
const result = {
|
|
37444
|
+
reference: captured.reference,
|
|
37445
|
+
head: captured.snapshot.head,
|
|
37446
|
+
baseOid: captured.snapshot.base.resolvedOid,
|
|
37447
|
+
entries: captured.snapshot.entries.length,
|
|
37448
|
+
includedUntracked: captured.snapshot.untrackedIncluded,
|
|
37449
|
+
exclusions: captured.snapshot.exclusions,
|
|
37450
|
+
issues: captured.snapshot.issues
|
|
37451
|
+
};
|
|
37452
|
+
return result;
|
|
37453
|
+
}
|
|
36090
37454
|
if (raw2.action === "start") {
|
|
36091
37455
|
if (starting || active.size || calls.size >= 64)
|
|
36092
37456
|
throw new Error("Coding operation already active or turn operation limit reached");
|
|
@@ -36112,7 +37476,9 @@ function createCodingHost(input) {
|
|
|
36112
37476
|
exitCode: null,
|
|
36113
37477
|
diagnostic: "",
|
|
36114
37478
|
truncated: false,
|
|
36115
|
-
reason: null
|
|
37479
|
+
reason: null,
|
|
37480
|
+
sourceBefore,
|
|
37481
|
+
sourceAfter: null
|
|
36116
37482
|
};
|
|
36117
37483
|
saveCodingCommand(input.db, receipt);
|
|
36118
37484
|
active.add(receipt.id);
|
|
@@ -36131,35 +37497,53 @@ function createCodingHost(input) {
|
|
|
36131
37497
|
value.state
|
|
36132
37498
|
) || typeof value.diagnostic !== "string" || Buffer.byteLength(value.diagnostic) > 256 * 1024 || value.exitCode !== null && !Number.isSafeInteger(value.exitCode) || value.reason !== null && (typeof value.reason !== "string" || value.reason.length > 4096) || typeof value.truncated !== "boolean" || value.state === "succeeded" && value.exitCode !== 0 || value.state === "failed" && (value.exitCode === null || value.exitCode === 0))
|
|
36133
37499
|
throw new Error("Invalid coding terminal evidence");
|
|
36134
|
-
const diagnostic = new CodingDiagnostics(input.secrets);
|
|
37500
|
+
const diagnostic = new CodingDiagnostics(input.secrets, CODING_RETAINED_BYTES);
|
|
36135
37501
|
diagnostic.append(Buffer.from(value.diagnostic));
|
|
36136
|
-
const
|
|
37502
|
+
const retained = diagnostic.finish();
|
|
37503
|
+
const tail = diagnosticTail(retained.diagnostic, CODING_OUTPUT_BYTES);
|
|
36137
37504
|
const reason = new CodingDiagnostics(input.secrets);
|
|
36138
37505
|
reason.append(Buffer.from(value.reason ?? ""));
|
|
36139
37506
|
Object.assign(receipt, {
|
|
36140
37507
|
state: value.state,
|
|
36141
37508
|
exitCode: value.exitCode,
|
|
36142
|
-
diagnostic:
|
|
36143
|
-
truncated:
|
|
37509
|
+
diagnostic: tail.text,
|
|
37510
|
+
truncated: tail.truncated || value.truncated,
|
|
36144
37511
|
reason: reason.finish().diagnostic || null,
|
|
36145
37512
|
finishedAt: Date.now()
|
|
36146
37513
|
});
|
|
37514
|
+
try {
|
|
37515
|
+
const captured = await captureTeamSnapshot(input.db, input.paths, input.teamId, {
|
|
37516
|
+
capturedBy: "agent",
|
|
37517
|
+
agentId: input.agentId,
|
|
37518
|
+
turnId: input.turnId,
|
|
37519
|
+
toolCallId: receipt.toolCallId
|
|
37520
|
+
});
|
|
37521
|
+
receipt.sourceAfter = captured.reference;
|
|
37522
|
+
} catch {
|
|
37523
|
+
receipt.sourceAfter = null;
|
|
37524
|
+
}
|
|
36147
37525
|
saveCodingCommand(input.db, receipt);
|
|
37526
|
+
try {
|
|
37527
|
+
saveCodingCommandLog(input.db, {
|
|
37528
|
+
commandId: receipt.id,
|
|
37529
|
+
teamId: input.teamId,
|
|
37530
|
+
agentId: input.agentId,
|
|
37531
|
+
turnId: input.turnId,
|
|
37532
|
+
toolCallId: receipt.toolCallId,
|
|
37533
|
+
diagnostic: retained.diagnostic,
|
|
37534
|
+
observedBytes: retained.observedBytes,
|
|
37535
|
+
redacted: retained.redacted,
|
|
37536
|
+
truncated: retained.truncated || value.truncated
|
|
37537
|
+
});
|
|
37538
|
+
} catch {
|
|
37539
|
+
}
|
|
36148
37540
|
active.delete(raw2.id);
|
|
36149
37541
|
return receipt;
|
|
36150
37542
|
}
|
|
36151
37543
|
if (raw2.action === "read") {
|
|
36152
37544
|
pruneCodingCommands(input.db, input.teamId);
|
|
36153
|
-
|
|
36154
|
-
|
|
36155
|
-
throw new Error("Coding receipt unavailable");
|
|
36156
|
-
if (receipt.agentId !== input.agentId) {
|
|
36157
|
-
const message = deliverableInbox(input.db, input.agentId, false).find(
|
|
36158
|
-
(message2) => message2.id === raw2.messageId
|
|
36159
|
-
);
|
|
36160
|
-
if (!message || message.fromAgentId !== receipt.agentId || !message.payload.includes(`coding-receipt:${receipt.id}`) || receipt.state === "running")
|
|
36161
|
-
throw new Error("Coding receipt requires an authorized producer message");
|
|
36162
|
-
}
|
|
37545
|
+
pruneCodingCommandLogs(input.db);
|
|
37546
|
+
const { receipt } = authorizeEvidence(raw2.id, raw2.messageId);
|
|
36163
37547
|
let current;
|
|
36164
37548
|
try {
|
|
36165
37549
|
current = await snapshot(receipt.input.cwd);
|
|
@@ -36170,6 +37554,28 @@ function createCodingHost(input) {
|
|
|
36170
37554
|
const applicability = !current.inputFingerprint || !receipt.environment.inputFingerprint ? "unknown" : receipt.state === "interrupted" || Date.now() - receipt.startedAt > 15 * 6e4 || Date.now() < receipt.startedAt || current.rootIdentity !== receipt.environment.rootIdentity || current.posture !== receipt.environment.posture || current.imageId !== receipt.environment.imageId || current.inputFingerprint !== receipt.environment.inputFingerprint ? "stale" : "fresh";
|
|
36171
37555
|
return { receipt, applicability };
|
|
36172
37556
|
}
|
|
37557
|
+
if (raw2.action === "log") {
|
|
37558
|
+
pruneCodingCommandLogs(input.db);
|
|
37559
|
+
const { producer } = authorizeEvidence(raw2.id, raw2.messageId);
|
|
37560
|
+
if (!producer) releaseCodingCommandLog(input.db, raw2.id);
|
|
37561
|
+
const page = readCodingCommandLog(input.db, raw2.id, {
|
|
37562
|
+
audience: producer ? "producer" : "disclosure",
|
|
37563
|
+
...raw2.offset !== void 0 ? { offset: raw2.offset } : {},
|
|
37564
|
+
...raw2.limit !== void 0 ? { limit: raw2.limit } : {}
|
|
37565
|
+
});
|
|
37566
|
+
if (!page) throw new Error("Coding log unavailable");
|
|
37567
|
+
return page;
|
|
37568
|
+
}
|
|
37569
|
+
if (raw2.action === "log-search") {
|
|
37570
|
+
pruneCodingCommandLogs(input.db);
|
|
37571
|
+
const { producer } = authorizeEvidence(raw2.id, raw2.messageId);
|
|
37572
|
+
if (!producer) releaseCodingCommandLog(input.db, raw2.id);
|
|
37573
|
+
const result = searchCodingCommandLog(input.db, raw2.id, raw2.query, {
|
|
37574
|
+
audience: producer ? "producer" : "disclosure"
|
|
37575
|
+
});
|
|
37576
|
+
if (!result) throw new Error("Coding log unavailable");
|
|
37577
|
+
return result;
|
|
37578
|
+
}
|
|
36173
37579
|
throw new Error("Unknown coding operation");
|
|
36174
37580
|
}
|
|
36175
37581
|
};
|
|
@@ -39684,6 +41090,8 @@ async function mirrorAgentTurnFrame(agentIdOrAgent, frame, attemptBase = `telegr
|
|
|
39684
41090
|
if (!agent) return;
|
|
39685
41091
|
const topicId = agents_exports.getTelegramTopicId(deps.db, agent.id);
|
|
39686
41092
|
if (topicId === null) return;
|
|
41093
|
+
const codingReleaseId = frame.kind === "event" && frame.event.type === "tool_result" && frame.event.name === "coding_command" ? codingCommandIdFromResult(frame.event.result) : null;
|
|
41094
|
+
let codingReleased = false;
|
|
39687
41095
|
const imagePayload = frameToolResultImages(frame);
|
|
39688
41096
|
if (imagePayload) {
|
|
39689
41097
|
await mirrorImages(deps, topicId, agent.id, imagePayload, `${attemptBase}:images`);
|
|
@@ -39715,6 +41123,10 @@ async function mirrorAgentTurnFrame(agentIdOrAgent, frame, attemptBase = `telegr
|
|
|
39715
41123
|
...isReply ? { parse_mode: "HTML" } : {}
|
|
39716
41124
|
})
|
|
39717
41125
|
);
|
|
41126
|
+
if (codingReleaseId && !codingReleased) {
|
|
41127
|
+
releaseCodingCommandLog(deps.db, codingReleaseId);
|
|
41128
|
+
codingReleased = true;
|
|
41129
|
+
}
|
|
39718
41130
|
} catch (e) {
|
|
39719
41131
|
if (isThreadGoneError(e)) {
|
|
39720
41132
|
console.warn(
|
|
@@ -39890,6 +41302,8 @@ function renderFrame(frame, mode) {
|
|
|
39890
41302
|
return null;
|
|
39891
41303
|
case "command_approval":
|
|
39892
41304
|
return null;
|
|
41305
|
+
case "coding_progress":
|
|
41306
|
+
return null;
|
|
39893
41307
|
case "user_message":
|
|
39894
41308
|
case "assistant_delta":
|
|
39895
41309
|
return null;
|
|
@@ -40048,6 +41462,7 @@ async function* runAgentTurn(turn) {
|
|
|
40048
41462
|
const selectedDocker = turn.protectedExecution?.docker ?? turn.configuredDocker?.docker;
|
|
40049
41463
|
const codingHost = createCodingHost({
|
|
40050
41464
|
db,
|
|
41465
|
+
paths,
|
|
40051
41466
|
agentId: agent.agent.id,
|
|
40052
41467
|
teamId: agent.team.id,
|
|
40053
41468
|
turnId,
|
|
@@ -41218,28 +42633,6 @@ async function authMiddleware(c, next) {
|
|
|
41218
42633
|
import { randomUUID as randomUUID34 } from "crypto";
|
|
41219
42634
|
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
|
|
41220
42635
|
import { join as join28 } from "path";
|
|
41221
|
-
|
|
41222
|
-
// ../../packages/api-types/src/entities.ts
|
|
41223
|
-
var REASONING_LEVELS2 = [
|
|
41224
|
-
"off",
|
|
41225
|
-
"minimal",
|
|
41226
|
-
"low",
|
|
41227
|
-
"medium",
|
|
41228
|
-
"high",
|
|
41229
|
-
"xhigh"
|
|
41230
|
-
];
|
|
41231
|
-
|
|
41232
|
-
// ../../packages/api-types/src/index.ts
|
|
41233
|
-
var PROFILE_FILES = [
|
|
41234
|
-
"profile.json",
|
|
41235
|
-
"SOUL.md",
|
|
41236
|
-
"IDENTITY.md",
|
|
41237
|
-
"BOOTSTRAP.md",
|
|
41238
|
-
"AGENTS.md",
|
|
41239
|
-
"TOOLS.md"
|
|
41240
|
-
];
|
|
41241
|
-
|
|
41242
|
-
// ../daemon/src/routes/agents.ts
|
|
41243
42636
|
import { Hono } from "hono";
|
|
41244
42637
|
|
|
41245
42638
|
// ../daemon/src/lib/agent-id.ts
|
|
@@ -45126,7 +46519,7 @@ function isChatFrame(value) {
|
|
|
45126
46519
|
return isNonEmptyString3(event2.name) && isNonEmptyString3(event2.mimeType) && isBase64(event2.data);
|
|
45127
46520
|
}
|
|
45128
46521
|
if (event2.type === "tool_result") {
|
|
45129
|
-
return isNonEmptyString3(event2.id) && isNonEmptyString3(event2.name) && typeof event2.result === "string" &&
|
|
46522
|
+
return isNonEmptyString3(event2.id) && isNonEmptyString3(event2.name) && typeof event2.result === "string" && (event2.images === void 0 || Array.isArray(event2.images) && event2.images.every(isToolResultImage));
|
|
45130
46523
|
}
|
|
45131
46524
|
return false;
|
|
45132
46525
|
}
|
|
@@ -45565,6 +46958,10 @@ async function deliver(plan) {
|
|
|
45565
46958
|
capturedResultFile(getCtx().db, plan.payload.agentId, frame.event);
|
|
45566
46959
|
releaseResultFile(getCtx().db, plan.payload.agentId, frame.event.result);
|
|
45567
46960
|
}
|
|
46961
|
+
if (frame.kind === "event" && frame.event.type === "tool_result" && frame.event.name === "coding_command") {
|
|
46962
|
+
const id = codingCommandIdFromResult(frame.event.result);
|
|
46963
|
+
if (id) releaseCodingCommandLog(getCtx().db, id);
|
|
46964
|
+
}
|
|
45568
46965
|
return;
|
|
45569
46966
|
}
|
|
45570
46967
|
if (plan.kind === "inbox_message") {
|
|
@@ -48807,6 +50204,128 @@ async function codingEnvironmentStatus(db, team, env = process.env) {
|
|
|
48807
50204
|
};
|
|
48808
50205
|
}
|
|
48809
50206
|
|
|
50207
|
+
// ../daemon/src/lib/git-review/feedback.ts
|
|
50208
|
+
var FEEDBACK_LIMITS = {
|
|
50209
|
+
excerptChars: 4e3,
|
|
50210
|
+
noteChars: 2e3
|
|
50211
|
+
};
|
|
50212
|
+
var feedbackReferencePrefix = "review-feedback:";
|
|
50213
|
+
function feedbackReference(document) {
|
|
50214
|
+
return `${feedbackReferencePrefix}${document.snapshotId ?? "none"}:${document.path}`;
|
|
50215
|
+
}
|
|
50216
|
+
function composeFeedbackMessage(document, note) {
|
|
50217
|
+
const lines = [
|
|
50218
|
+
`${feedbackReferencePrefix}${document.snapshotId ?? "none"}`,
|
|
50219
|
+
`Repository source snapshot: ${document.snapshotId ?? "(not captured)"}`,
|
|
50220
|
+
`File: ${document.previousPath ? `${document.previousPath} -> ${document.path}` : document.path}`,
|
|
50221
|
+
`Change: ${document.status}`
|
|
50222
|
+
];
|
|
50223
|
+
if (document.lineRange)
|
|
50224
|
+
lines.push(`Selected lines: ${document.lineRange.start}-${document.lineRange.end} (context)`);
|
|
50225
|
+
lines.push(
|
|
50226
|
+
`Source identity: ${document.applicability === "current" ? "unchanged since this snapshot" : document.applicability === "stale" ? "CHANGED since this snapshot \u2014 the selected lines may have moved" : `unknown (${document.applicabilityReason})`}`
|
|
50227
|
+
);
|
|
50228
|
+
if (document.excerpt !== null) {
|
|
50229
|
+
lines.push(
|
|
50230
|
+
"",
|
|
50231
|
+
`Excerpt (${document.excerptSource === "current_read" ? "read now, not stored in the snapshot" : document.excerptSource}):`,
|
|
50232
|
+
"```",
|
|
50233
|
+
document.excerpt,
|
|
50234
|
+
"```"
|
|
50235
|
+
);
|
|
50236
|
+
} else {
|
|
50237
|
+
lines.push("", `Excerpt: unavailable (${document.excerptSource})`);
|
|
50238
|
+
}
|
|
50239
|
+
const trimmed = note?.trim();
|
|
50240
|
+
if (trimmed) lines.push("", `Operator note: ${trimmed.slice(0, FEEDBACK_LIMITS.noteChars)}`);
|
|
50241
|
+
return lines.join("\n");
|
|
50242
|
+
}
|
|
50243
|
+
async function buildReviewFeedback(db, paths, teamId, request2) {
|
|
50244
|
+
const team = requireTeam(db, paths, teamId);
|
|
50245
|
+
const record3 = request2.snapshotId ? getSourceSnapshot(db, team.id, request2.snapshotId) : null;
|
|
50246
|
+
const stored = parseStoredSnapshot(record3);
|
|
50247
|
+
return withRepository(team.path, async (captured) => {
|
|
50248
|
+
const base = await resolveComparisonBase(captured, request2.base ?? "HEAD");
|
|
50249
|
+
const identity2 = await readRepositoryIdentity(captured);
|
|
50250
|
+
const listed = await attachPatches(
|
|
50251
|
+
captured,
|
|
50252
|
+
await listChanges(captured, base, identity2),
|
|
50253
|
+
void 0,
|
|
50254
|
+
request2.path
|
|
50255
|
+
);
|
|
50256
|
+
const change = listed.changes.find((entry) => entry.path === request2.path);
|
|
50257
|
+
if (!change)
|
|
50258
|
+
throw new FeedbackTargetError("unknown_path", "That path is not changed since this baseline.");
|
|
50259
|
+
const current = await captureSourceSnapshot(captured, base, identity2);
|
|
50260
|
+
return {
|
|
50261
|
+
teamId: team.id,
|
|
50262
|
+
path: change.path,
|
|
50263
|
+
previousPath: change.previousPath,
|
|
50264
|
+
status: change.status,
|
|
50265
|
+
snapshotId: request2.snapshotId ?? null,
|
|
50266
|
+
lineRange: lineRange(request2, captured),
|
|
50267
|
+
excerpt: excerptOf(change),
|
|
50268
|
+
excerptSource: excerptSource(change),
|
|
50269
|
+
...applicabilityOf(stored, current, change.path)
|
|
50270
|
+
};
|
|
50271
|
+
});
|
|
50272
|
+
}
|
|
50273
|
+
var FeedbackTargetError = class extends Error {
|
|
50274
|
+
code;
|
|
50275
|
+
constructor(code, message) {
|
|
50276
|
+
super(message);
|
|
50277
|
+
this.name = "FeedbackTargetError";
|
|
50278
|
+
this.code = code;
|
|
50279
|
+
}
|
|
50280
|
+
};
|
|
50281
|
+
function applicabilityOf(stored, current, path) {
|
|
50282
|
+
if (!stored) return { applicability: "unknown", applicabilityReason: "no_snapshot" };
|
|
50283
|
+
if (!stored.complete)
|
|
50284
|
+
return { applicability: "unknown", applicabilityReason: "incomplete_snapshot" };
|
|
50285
|
+
if (!current.complete)
|
|
50286
|
+
return { applicability: "unknown", applicabilityReason: "capture_incomplete" };
|
|
50287
|
+
const before = stored.entries.find((entry) => entry.path === path) ?? null;
|
|
50288
|
+
const after = current.entries.find((entry) => entry.path === path) ?? null;
|
|
50289
|
+
if (before === null && after === null) {
|
|
50290
|
+
return { applicability: "current", applicabilityReason: "path_unchanged" };
|
|
50291
|
+
}
|
|
50292
|
+
if (before?.digest && before.digest === after?.digest && before.kind === after?.kind) {
|
|
50293
|
+
return { applicability: "current", applicabilityReason: "path_unchanged" };
|
|
50294
|
+
}
|
|
50295
|
+
return { applicability: "stale", applicabilityReason: "path_changed" };
|
|
50296
|
+
}
|
|
50297
|
+
function parseStoredSnapshot(record3) {
|
|
50298
|
+
if (!record3) return null;
|
|
50299
|
+
try {
|
|
50300
|
+
return JSON.parse(record3.manifestJson);
|
|
50301
|
+
} catch {
|
|
50302
|
+
return null;
|
|
50303
|
+
}
|
|
50304
|
+
}
|
|
50305
|
+
function excerptSource(change) {
|
|
50306
|
+
if (change.excludedReason) return "excluded";
|
|
50307
|
+
if (change.binary) return "binary";
|
|
50308
|
+
if (change.patch === null) return "omitted";
|
|
50309
|
+
return "current_read";
|
|
50310
|
+
}
|
|
50311
|
+
function excerptOf(change) {
|
|
50312
|
+
if (change.excludedReason || change.binary || change.patch === null) return null;
|
|
50313
|
+
return change.patch.slice(0, FEEDBACK_LIMITS.excerptChars);
|
|
50314
|
+
}
|
|
50315
|
+
function lineRange(request2, _captured) {
|
|
50316
|
+
const { startLine, endLine } = request2;
|
|
50317
|
+
if (startLine === void 0 && endLine === void 0) return null;
|
|
50318
|
+
const start = startLine ?? endLine;
|
|
50319
|
+
const end = endLine ?? startLine;
|
|
50320
|
+
if (start === void 0 || end === void 0 || !Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 1 || end < start || end > 1e6) {
|
|
50321
|
+
throw new FeedbackTargetError(
|
|
50322
|
+
"invalid_range",
|
|
50323
|
+
"Selected lines must be an ordered positive range."
|
|
50324
|
+
);
|
|
50325
|
+
}
|
|
50326
|
+
return { start, end };
|
|
50327
|
+
}
|
|
50328
|
+
|
|
48810
50329
|
// ../daemon/src/routes/teams.ts
|
|
48811
50330
|
var USER_MD_MAX_BYTES2 = 12e3;
|
|
48812
50331
|
var teamsRouter = new Hono18();
|
|
@@ -48846,6 +50365,195 @@ teamsRouter.get("/:id/repository-context", async (c) => {
|
|
|
48846
50365
|
})
|
|
48847
50366
|
);
|
|
48848
50367
|
});
|
|
50368
|
+
function codingLogCommand(c) {
|
|
50369
|
+
const { db, paths } = getCtx();
|
|
50370
|
+
const team = teams_exports.get(db, c.req.param("id") ?? "", paths);
|
|
50371
|
+
if (!team) return { error: "Team not found", status: 404 };
|
|
50372
|
+
const commandId = c.req.param("commandId") ?? "";
|
|
50373
|
+
const receipt = getCodingCommand(db, commandId);
|
|
50374
|
+
if (!receipt || receipt.teamId !== team.id)
|
|
50375
|
+
return { error: "Coding command not found", status: 404 };
|
|
50376
|
+
const view2 = getCodingCommandLog(db, commandId);
|
|
50377
|
+
if (view2.releasedAt === null) return { error: "Coding log has not been shared", status: 403 };
|
|
50378
|
+
return { db, commandId, view: view2 };
|
|
50379
|
+
}
|
|
50380
|
+
teamsRouter.get("/:id/coding-commands/:commandId/log", (c) => {
|
|
50381
|
+
const resolved = codingLogCommand(c);
|
|
50382
|
+
if ("error" in resolved) return c.json({ error: resolved.error }, resolved.status);
|
|
50383
|
+
const offset = Number(c.req.query("offset") ?? "0");
|
|
50384
|
+
const limit = Number(c.req.query("limit") ?? "65536");
|
|
50385
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit))
|
|
50386
|
+
return c.json({ error: "Invalid page request" }, 400);
|
|
50387
|
+
const page = readCodingCommandLog(resolved.db, resolved.commandId, {
|
|
50388
|
+
offset,
|
|
50389
|
+
limit,
|
|
50390
|
+
audience: "disclosure"
|
|
50391
|
+
});
|
|
50392
|
+
c.header("Cache-Control", "no-store");
|
|
50393
|
+
return c.json({ view: resolved.view, page });
|
|
50394
|
+
});
|
|
50395
|
+
teamsRouter.get("/:id/coding-commands/:commandId/log/search", (c) => {
|
|
50396
|
+
const resolved = codingLogCommand(c);
|
|
50397
|
+
if ("error" in resolved) return c.json({ error: resolved.error }, resolved.status);
|
|
50398
|
+
const query = c.req.query("q") ?? "";
|
|
50399
|
+
if (!query || query.length > 256) return c.json({ error: "Invalid search query" }, 400);
|
|
50400
|
+
const result = searchCodingCommandLog(resolved.db, resolved.commandId, query, {
|
|
50401
|
+
audience: "disclosure"
|
|
50402
|
+
});
|
|
50403
|
+
c.header("Cache-Control", "no-store");
|
|
50404
|
+
return c.json({ view: resolved.view, result });
|
|
50405
|
+
});
|
|
50406
|
+
function reviewFailure(c, error2) {
|
|
50407
|
+
if (error2 instanceof ReviewBaseError)
|
|
50408
|
+
return c.json({ error: error2.message, code: error2.code }, 400);
|
|
50409
|
+
if (error2 instanceof FeedbackTargetError)
|
|
50410
|
+
return c.json({ error: error2.message, code: error2.code }, 400);
|
|
50411
|
+
if (error2 instanceof ReviewUnavailableError) {
|
|
50412
|
+
return c.json(
|
|
50413
|
+
{ error: error2.message, code: error2.code },
|
|
50414
|
+
error2.code === "team_not_found" ? 404 : 409
|
|
50415
|
+
);
|
|
50416
|
+
}
|
|
50417
|
+
throw error2;
|
|
50418
|
+
}
|
|
50419
|
+
teamsRouter.get("/:id/review", async (c) => {
|
|
50420
|
+
const { db, paths } = getCtx();
|
|
50421
|
+
c.header("Cache-Control", "no-store");
|
|
50422
|
+
const base = c.req.query("base");
|
|
50423
|
+
const patches = c.req.query("patches") === "1";
|
|
50424
|
+
const patchPath = c.req.query("path");
|
|
50425
|
+
if (patchPath !== void 0 && (patchPath.length === 0 || patchPath.length > 4096)) {
|
|
50426
|
+
return c.json({ error: "Invalid path", code: "invalid_path" }, 400);
|
|
50427
|
+
}
|
|
50428
|
+
try {
|
|
50429
|
+
const review = await readTeamReview(db, paths, c.req.param("id"), {
|
|
50430
|
+
...base === void 0 ? {} : { base },
|
|
50431
|
+
patches,
|
|
50432
|
+
...patchPath === void 0 ? {} : { patchPath }
|
|
50433
|
+
});
|
|
50434
|
+
return c.json(review);
|
|
50435
|
+
} catch (error2) {
|
|
50436
|
+
return reviewFailure(c, error2);
|
|
50437
|
+
}
|
|
50438
|
+
});
|
|
50439
|
+
teamsRouter.get("/:id/review/snapshots", (c) => {
|
|
50440
|
+
const { db, paths } = getCtx();
|
|
50441
|
+
c.header("Cache-Control", "no-store");
|
|
50442
|
+
try {
|
|
50443
|
+
return c.json({ snapshots: listTeamSnapshots(db, paths, c.req.param("id")) });
|
|
50444
|
+
} catch (error2) {
|
|
50445
|
+
return reviewFailure(c, error2);
|
|
50446
|
+
}
|
|
50447
|
+
});
|
|
50448
|
+
teamsRouter.get("/:id/review/snapshots/:snapshotId", (c) => {
|
|
50449
|
+
const { db, paths } = getCtx();
|
|
50450
|
+
c.header("Cache-Control", "no-store");
|
|
50451
|
+
try {
|
|
50452
|
+
const snapshot = readTeamSnapshot(db, paths, c.req.param("id"), c.req.param("snapshotId"));
|
|
50453
|
+
if (!snapshot) return c.json({ error: "Snapshot not found" }, 404);
|
|
50454
|
+
return c.json({ snapshot });
|
|
50455
|
+
} catch (error2) {
|
|
50456
|
+
return reviewFailure(c, error2);
|
|
50457
|
+
}
|
|
50458
|
+
});
|
|
50459
|
+
teamsRouter.post("/:id/review/feedback", async (c) => {
|
|
50460
|
+
const { db, paths } = getCtx();
|
|
50461
|
+
c.header("Cache-Control", "no-store");
|
|
50462
|
+
const body = await c.req.json().catch(() => null);
|
|
50463
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
50464
|
+
return c.json({ error: "Invalid feedback request" }, 400);
|
|
50465
|
+
}
|
|
50466
|
+
const input = body;
|
|
50467
|
+
if (Object.keys(input).some(
|
|
50468
|
+
(key3) => !["path", "base", "snapshotId", "startLine", "endLine", "note"].includes(key3)
|
|
50469
|
+
)) {
|
|
50470
|
+
return c.json({ error: "Invalid feedback request" }, 400);
|
|
50471
|
+
}
|
|
50472
|
+
if (typeof input.path !== "string" || input.path.length === 0 || input.path.length > 4096) {
|
|
50473
|
+
return c.json({ error: "A changed path is required" }, 400);
|
|
50474
|
+
}
|
|
50475
|
+
if (input.base !== void 0 && (typeof input.base !== "string" || input.base.length > 4096)) {
|
|
50476
|
+
return c.json({ error: "Invalid comparison base" }, 400);
|
|
50477
|
+
}
|
|
50478
|
+
if (input.snapshotId !== void 0 && (typeof input.snapshotId !== "string" || input.snapshotId.length > 128)) {
|
|
50479
|
+
return c.json({ error: "Invalid snapshot reference" }, 400);
|
|
50480
|
+
}
|
|
50481
|
+
if (input.note !== void 0 && (typeof input.note !== "string" || input.note.length > FEEDBACK_LIMITS.noteChars)) {
|
|
50482
|
+
return c.json({ error: "Invalid feedback note" }, 400);
|
|
50483
|
+
}
|
|
50484
|
+
for (const key3 of ["startLine", "endLine"]) {
|
|
50485
|
+
const value = input[key3];
|
|
50486
|
+
if (value !== void 0 && (typeof value !== "number" || !Number.isSafeInteger(value))) {
|
|
50487
|
+
return c.json({ error: "Selected lines must be whole numbers" }, 400);
|
|
50488
|
+
}
|
|
50489
|
+
}
|
|
50490
|
+
try {
|
|
50491
|
+
const feedback = await buildReviewFeedback(db, paths, c.req.param("id"), {
|
|
50492
|
+
path: input.path,
|
|
50493
|
+
...input.base === void 0 ? {} : { base: input.base },
|
|
50494
|
+
...input.snapshotId === void 0 ? {} : { snapshotId: input.snapshotId },
|
|
50495
|
+
...input.startLine === void 0 ? {} : { startLine: input.startLine },
|
|
50496
|
+
...input.endLine === void 0 ? {} : { endLine: input.endLine },
|
|
50497
|
+
...input.note === void 0 ? {} : { note: input.note }
|
|
50498
|
+
});
|
|
50499
|
+
return c.json({
|
|
50500
|
+
feedback,
|
|
50501
|
+
reference: feedbackReference(feedback),
|
|
50502
|
+
message: composeFeedbackMessage(feedback, input.note)
|
|
50503
|
+
});
|
|
50504
|
+
} catch (error2) {
|
|
50505
|
+
return reviewFailure(c, error2);
|
|
50506
|
+
}
|
|
50507
|
+
});
|
|
50508
|
+
teamsRouter.get("/:id/review/snapshots/:snapshotId/applicability", async (c) => {
|
|
50509
|
+
const { db, paths } = getCtx();
|
|
50510
|
+
c.header("Cache-Control", "no-store");
|
|
50511
|
+
try {
|
|
50512
|
+
return c.json(
|
|
50513
|
+
await readSnapshotApplicability(db, paths, c.req.param("id"), c.req.param("snapshotId"))
|
|
50514
|
+
);
|
|
50515
|
+
} catch (error2) {
|
|
50516
|
+
return reviewFailure(c, error2);
|
|
50517
|
+
}
|
|
50518
|
+
});
|
|
50519
|
+
teamsRouter.post("/:id/review/snapshots", async (c) => {
|
|
50520
|
+
const { db, paths } = getCtx();
|
|
50521
|
+
c.header("Cache-Control", "no-store");
|
|
50522
|
+
const body = await c.req.json().catch(() => null);
|
|
50523
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
50524
|
+
return c.json({ error: "Invalid snapshot request" }, 400);
|
|
50525
|
+
}
|
|
50526
|
+
const input = body;
|
|
50527
|
+
if (Object.keys(input).some((key3) => !["base", "includeUntracked"].includes(key3))) {
|
|
50528
|
+
return c.json({ error: "Invalid snapshot request" }, 400);
|
|
50529
|
+
}
|
|
50530
|
+
if (input.base !== void 0 && (typeof input.base !== "string" || input.base.length > 4096)) {
|
|
50531
|
+
return c.json({ error: "Invalid comparison base" }, 400);
|
|
50532
|
+
}
|
|
50533
|
+
let includeUntracked;
|
|
50534
|
+
if (input.includeUntracked !== void 0) {
|
|
50535
|
+
if (!Array.isArray(input.includeUntracked) || input.includeUntracked.length > REVIEW_LIMITS.files) {
|
|
50536
|
+
return c.json({ error: "Invalid untracked selection" }, 400);
|
|
50537
|
+
}
|
|
50538
|
+
includeUntracked = [];
|
|
50539
|
+
for (const entry of input.includeUntracked) {
|
|
50540
|
+
if (typeof entry !== "string" || entry.length === 0 || entry.length > 4096) {
|
|
50541
|
+
return c.json({ error: "Invalid untracked selection" }, 400);
|
|
50542
|
+
}
|
|
50543
|
+
includeUntracked.push(entry);
|
|
50544
|
+
}
|
|
50545
|
+
}
|
|
50546
|
+
try {
|
|
50547
|
+
const captured = await captureTeamSnapshot(db, paths, c.req.param("id"), {
|
|
50548
|
+
capturedBy: "operator",
|
|
50549
|
+
...input.base === void 0 ? {} : { base: input.base },
|
|
50550
|
+
...includeUntracked ? { includeUntracked } : {}
|
|
50551
|
+
});
|
|
50552
|
+
return c.json(captured);
|
|
50553
|
+
} catch (error2) {
|
|
50554
|
+
return reviewFailure(c, error2);
|
|
50555
|
+
}
|
|
50556
|
+
});
|
|
48849
50557
|
teamsRouter.get("/:id/coding-environment", async (c) => {
|
|
48850
50558
|
const { db, paths } = getCtx();
|
|
48851
50559
|
const team = teams_exports.get(db, c.req.param("id"), paths);
|