sentinelayer-cli 0.30.0 → 0.30.2
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/package.json
CHANGED
package/src/commands/session.js
CHANGED
|
@@ -42,7 +42,9 @@ import {
|
|
|
42
42
|
import { listRuntimeRuns } from "../session/runtime-bridge.js";
|
|
43
43
|
import {
|
|
44
44
|
listFileLocks,
|
|
45
|
+
lockFile,
|
|
45
46
|
releaseFileLocksForAgent,
|
|
47
|
+
unlockFile,
|
|
46
48
|
} from "../session/file-locks.js";
|
|
47
49
|
import {
|
|
48
50
|
injectSessionGuides,
|
|
@@ -2014,30 +2016,6 @@ export function buildSessionLockDirective(verb, file, intent = "") {
|
|
|
2014
2016
|
return normalizedIntent ? `lock: ${normalizedFile} - ${normalizedIntent}` : `lock: ${normalizedFile}`;
|
|
2015
2017
|
}
|
|
2016
2018
|
|
|
2017
|
-
// Posts a coordination directive (e.g. "lock: <file> - <intent>") as a session
|
|
2018
|
-
// message so the session daemon processes it into the authoritative file-lock
|
|
2019
|
-
// registry. Used by `session lock`/`unlock` as ergonomic sugar over the
|
|
2020
|
-
// say-convention; locks are advisory + daemon-enforced with TTL auto-release,
|
|
2021
|
-
// so this is a best-effort post.
|
|
2022
|
-
async function postSessionDirectiveMessage(sessionId, message, {
|
|
2023
|
-
agentId,
|
|
2024
|
-
targetPath = process.cwd(),
|
|
2025
|
-
} = {}) {
|
|
2026
|
-
const clientMessageId = `cli-${randomUUID()}`;
|
|
2027
|
-
const agent = await resolveSessionAgentEnvelope(sessionId, agentId, { targetPath });
|
|
2028
|
-
await ensureSessionSayAgentRegistered(sessionId, agent, { targetPath });
|
|
2029
|
-
const event = createAgentEvent({
|
|
2030
|
-
event: "session_message",
|
|
2031
|
-
agent,
|
|
2032
|
-
sessionId,
|
|
2033
|
-
payload: { message, channel: "session", clientMessageId },
|
|
2034
|
-
});
|
|
2035
|
-
event.eventId = clientMessageId;
|
|
2036
|
-
event.idempotencyToken = clientMessageId;
|
|
2037
|
-
const result = await syncSessionEventToApi(sessionId, event, { targetPath });
|
|
2038
|
-
return { event, result };
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
2019
|
async function runWithConcurrency(items = [], concurrency = 1, worker = async () => null) {
|
|
2042
2020
|
const normalizedItems = Array.isArray(items) ? items : [];
|
|
2043
2021
|
const normalizedConcurrency = Math.max(
|
|
@@ -3672,7 +3650,7 @@ export function registerSessionCommand(program) {
|
|
|
3672
3650
|
return payload;
|
|
3673
3651
|
});
|
|
3674
3652
|
|
|
3675
|
-
const
|
|
3653
|
+
const runFileLockCommand = async (verb, sessionId, files, options, command) => {
|
|
3676
3654
|
const normalizedSessionId = normalizeString(sessionId);
|
|
3677
3655
|
if (!normalizedSessionId) {
|
|
3678
3656
|
throw new Error("session id is required.");
|
|
@@ -3698,20 +3676,68 @@ export function registerSessionCommand(program) {
|
|
|
3698
3676
|
);
|
|
3699
3677
|
}
|
|
3700
3678
|
const intent = normalizeString(options.intent);
|
|
3679
|
+
const results = [];
|
|
3701
3680
|
const processed = [];
|
|
3681
|
+
const skipped = [];
|
|
3682
|
+
const failed = [];
|
|
3702
3683
|
for (const file of fileList) {
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3684
|
+
if (verb === "lock") {
|
|
3685
|
+
const result = await lockFile(normalizedSessionId, identity.agentId, file, {
|
|
3686
|
+
intent,
|
|
3687
|
+
targetPath,
|
|
3688
|
+
awaitRemoteSync: true,
|
|
3689
|
+
});
|
|
3690
|
+
const record = {
|
|
3691
|
+
file: result.file || file,
|
|
3692
|
+
locked: Boolean(result.locked),
|
|
3693
|
+
reason: result.reason || (result.locked ? "locked" : "held_by_other_agent"),
|
|
3694
|
+
heldBy: result.heldBy || null,
|
|
3695
|
+
since: result.since || null,
|
|
3696
|
+
};
|
|
3697
|
+
results.push(record);
|
|
3698
|
+
if (result.locked) {
|
|
3699
|
+
processed.push(record.file);
|
|
3700
|
+
} else {
|
|
3701
|
+
failed.push(record);
|
|
3702
|
+
}
|
|
3703
|
+
continue;
|
|
3704
|
+
}
|
|
3705
|
+
|
|
3706
|
+
const result = await unlockFile(normalizedSessionId, identity.agentId, file, {
|
|
3707
|
+
reason: intent || "manual_release",
|
|
3706
3708
|
targetPath,
|
|
3709
|
+
awaitRemoteSync: true,
|
|
3707
3710
|
});
|
|
3708
|
-
|
|
3711
|
+
const record = {
|
|
3712
|
+
file: result.file || file,
|
|
3713
|
+
unlocked: Boolean(result.unlocked),
|
|
3714
|
+
reason: result.reason || (result.unlocked ? "unlocked" : "not_locked"),
|
|
3715
|
+
heldBy: result.heldBy || null,
|
|
3716
|
+
since: result.since || null,
|
|
3717
|
+
};
|
|
3718
|
+
results.push(record);
|
|
3719
|
+
if (result.unlocked) {
|
|
3720
|
+
processed.push(record.file);
|
|
3721
|
+
} else if (record.reason === "not_locked") {
|
|
3722
|
+
skipped.push(record);
|
|
3723
|
+
} else {
|
|
3724
|
+
failed.push(record);
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
if (failed.length > 0) {
|
|
3729
|
+
const summary = failed
|
|
3730
|
+
.map((item) => `${item.file}${item.heldBy ? ` held by ${item.heldBy}` : ""}`)
|
|
3731
|
+
.join(", ");
|
|
3732
|
+
throw new Error(`session ${verb} failed for ${summary}`);
|
|
3709
3733
|
}
|
|
3710
3734
|
const payload = {
|
|
3711
3735
|
command: `session ${verb}`,
|
|
3712
3736
|
sessionId: normalizedSessionId,
|
|
3713
3737
|
agentId: identity.agentId,
|
|
3714
3738
|
files: processed,
|
|
3739
|
+
results,
|
|
3740
|
+
skipped,
|
|
3715
3741
|
};
|
|
3716
3742
|
if (shouldEmitJson(options, command)) {
|
|
3717
3743
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -3735,7 +3761,7 @@ export function registerSessionCommand(program) {
|
|
|
3735
3761
|
.option("--path <path>", "Workspace path for the session", ".")
|
|
3736
3762
|
.option("--json", "Emit machine-readable output")
|
|
3737
3763
|
.action(async (sessionId, files, options, command) => {
|
|
3738
|
-
await
|
|
3764
|
+
await runFileLockCommand("lock", sessionId, files, options, command);
|
|
3739
3765
|
});
|
|
3740
3766
|
|
|
3741
3767
|
session
|
|
@@ -3746,7 +3772,7 @@ export function registerSessionCommand(program) {
|
|
|
3746
3772
|
.option("--path <path>", "Workspace path for the session", ".")
|
|
3747
3773
|
.option("--json", "Emit machine-readable output")
|
|
3748
3774
|
.action(async (sessionId, files, options, command) => {
|
|
3749
|
-
await
|
|
3775
|
+
await runFileLockCommand("unlock", sessionId, files, options, command);
|
|
3750
3776
|
});
|
|
3751
3777
|
|
|
3752
3778
|
session
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import fsp from "node:fs/promises";
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import crypto from "node:crypto";
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
13
14
|
|
|
14
15
|
import { recordProvisionedIdentity } from "../ai/identity-store.js";
|
|
15
16
|
import { runDevTestBotSession } from "../agents/devtestbot/tool.js";
|
|
@@ -67,6 +68,26 @@ function parsePlannerJson(text) {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
// Derive a stable repo key for the managed-proxy DD entitlement gate (#94651):
|
|
72
|
+
// owner/repo from the git remote when present, else a non-identifying local key
|
|
73
|
+
// (hash of the absolute repo root — no raw path leak). Fail-safe to the local key.
|
|
74
|
+
function deriveRepoKey(rootPath) {
|
|
75
|
+
try {
|
|
76
|
+
const remote = spawnSync("git", ["-C", rootPath || ".", "remote", "get-url", "origin"], {
|
|
77
|
+
encoding: "utf-8",
|
|
78
|
+
});
|
|
79
|
+
if (remote.status === 0) {
|
|
80
|
+
const url = String(remote.stdout || "").trim();
|
|
81
|
+
const match = url.match(/[:/]([^/:]+\/[^/]+?)(?:\.git)?$/);
|
|
82
|
+
if (match && match[1]) return match[1].toLowerCase();
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// fall through to the local key
|
|
86
|
+
}
|
|
87
|
+
const abs = path.resolve(rootPath || ".");
|
|
88
|
+
return `local/${crypto.createHash("sha256").update(abs).digest("hex").slice(0, 12)}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
70
91
|
function buildPlannerPrompt({ rootPath, files = [], findings = [], budget = {} }) {
|
|
71
92
|
const severityCounts = {};
|
|
72
93
|
for (const finding of findings || []) {
|
|
@@ -86,9 +107,22 @@ function buildPlannerPrompt({ rootPath, files = [], findings = [], budget = {} }
|
|
|
86
107
|
].join("\n");
|
|
87
108
|
}
|
|
88
109
|
|
|
89
|
-
async function callPlannerClient({ plannerClient, rootPath, files, findings, budget, sessionUsage }) {
|
|
110
|
+
export async function callPlannerClient({ plannerClient, rootPath, files, findings, budget, sessionUsage }) {
|
|
90
111
|
if (!plannerClient) return { planned: {}, usageLedger: null };
|
|
91
112
|
const prompt = buildPlannerPrompt({ rootPath, files, findings, budget });
|
|
113
|
+
// DD entitlement gate metadata (#94651): only label proxy calls when there
|
|
114
|
+
// is a real session usage anchor. API-side DD actions are fail-closed and
|
|
115
|
+
// reject incomplete session context before provider spend.
|
|
116
|
+
const sessionId = String(sessionUsage?.sessionId || "").trim();
|
|
117
|
+
const ddProxyFields = sessionId
|
|
118
|
+
? {
|
|
119
|
+
action: INVESTOR_DD_USAGE_ACTIONS.devTestBotPlanner,
|
|
120
|
+
sessionId,
|
|
121
|
+
agentId: "investor-dd-devtestbot-planner",
|
|
122
|
+
usageIdempotencyKey: `investor-dd-devtestbot-${crypto.randomUUID()}`,
|
|
123
|
+
metadata: { repoKey: deriveRepoKey(rootPath), phase: "devtestbot" },
|
|
124
|
+
}
|
|
125
|
+
: {};
|
|
92
126
|
if (typeof plannerClient.decideDevTestBotPhase === "function") {
|
|
93
127
|
return {
|
|
94
128
|
planned: await plannerClient.decideDevTestBotPhase({ rootPath, files, findings, budget, prompt }),
|
|
@@ -105,7 +139,7 @@ async function callPlannerClient({ plannerClient, rootPath, files, findings, bud
|
|
|
105
139
|
}
|
|
106
140
|
const startedAtIso = new Date().toISOString();
|
|
107
141
|
if (typeof plannerClient.invoke === "function") {
|
|
108
|
-
const response = await plannerClient.invoke({ prompt, stream: false });
|
|
142
|
+
const response = await plannerClient.invoke({ prompt, stream: false, ...ddProxyFields });
|
|
109
143
|
const usageLedger = sessionUsage
|
|
110
144
|
? await recordInvestorDdLlmUsage({
|
|
111
145
|
usageContext: sessionUsage,
|
|
@@ -128,6 +162,7 @@ async function callPlannerClient({ plannerClient, rootPath, files, findings, bud
|
|
|
128
162
|
if (typeof plannerClient.generatePlan === "function") {
|
|
129
163
|
const response = await plannerClient.generatePlan([{ role: "user", content: prompt }], {
|
|
130
164
|
phase: "devtestbot",
|
|
165
|
+
...ddProxyFields,
|
|
131
166
|
});
|
|
132
167
|
const usageLedger = sessionUsage
|
|
133
168
|
? await recordInvestorDdLlmUsage({
|
|
@@ -260,6 +260,8 @@ async function appendLockEvent(
|
|
|
260
260
|
{
|
|
261
261
|
targetPath = process.cwd(),
|
|
262
262
|
nowIso = new Date().toISOString(),
|
|
263
|
+
syncRemote = true,
|
|
264
|
+
awaitRemoteSync = false,
|
|
263
265
|
} = {}
|
|
264
266
|
) {
|
|
265
267
|
return appendToStream(
|
|
@@ -273,6 +275,8 @@ async function appendLockEvent(
|
|
|
273
275
|
}),
|
|
274
276
|
{
|
|
275
277
|
targetPath,
|
|
278
|
+
syncRemote,
|
|
279
|
+
awaitRemoteSync,
|
|
276
280
|
}
|
|
277
281
|
);
|
|
278
282
|
}
|
|
@@ -371,6 +375,8 @@ export async function lockFile(
|
|
|
371
375
|
ttlSeconds = DEFAULT_FILE_LOCK_TTL_SECONDS,
|
|
372
376
|
targetPath = process.cwd(),
|
|
373
377
|
nowIso = new Date().toISOString(),
|
|
378
|
+
syncRemote = true,
|
|
379
|
+
awaitRemoteSync = false,
|
|
374
380
|
} = {}
|
|
375
381
|
) {
|
|
376
382
|
const normalizedAgentId = normalizeAgentId(agentId);
|
|
@@ -437,6 +443,8 @@ export async function lockFile(
|
|
|
437
443
|
{
|
|
438
444
|
targetPath,
|
|
439
445
|
nowIso: normalizedNow,
|
|
446
|
+
syncRemote,
|
|
447
|
+
awaitRemoteSync,
|
|
440
448
|
}
|
|
441
449
|
);
|
|
442
450
|
return {
|
|
@@ -462,6 +470,8 @@ export async function unlockFile(
|
|
|
462
470
|
targetPath = process.cwd(),
|
|
463
471
|
nowIso = new Date().toISOString(),
|
|
464
472
|
actorAgentId = null,
|
|
473
|
+
syncRemote = true,
|
|
474
|
+
awaitRemoteSync = false,
|
|
465
475
|
} = {}
|
|
466
476
|
) {
|
|
467
477
|
const normalizedAgentId = normalizeAgentId(agentId);
|
|
@@ -527,6 +537,8 @@ export async function unlockFile(
|
|
|
527
537
|
{
|
|
528
538
|
targetPath,
|
|
529
539
|
nowIso: normalizedNow,
|
|
540
|
+
syncRemote,
|
|
541
|
+
awaitRemoteSync,
|
|
530
542
|
}
|
|
531
543
|
);
|
|
532
544
|
|