sentinelayer-cli 0.8.0 → 0.8.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/README.md +23 -2
- package/package.json +4 -4
- package/src/agents/ai-governance/index.js +12 -0
- package/src/agents/ai-governance/tools/base.js +171 -0
- package/src/agents/ai-governance/tools/eval-regression.js +47 -0
- package/src/agents/ai-governance/tools/hitl-audit.js +81 -0
- package/src/agents/ai-governance/tools/index.js +52 -0
- package/src/agents/ai-governance/tools/prompt-drift.js +42 -0
- package/src/agents/ai-governance/tools/provenance-check.js +69 -0
- package/src/agents/backend/index.js +12 -0
- package/src/agents/backend/tools/base.js +189 -0
- package/src/agents/backend/tools/circuit-breaker-check.js +123 -0
- package/src/agents/backend/tools/idempotency-audit.js +105 -0
- package/src/agents/backend/tools/index.js +87 -0
- package/src/agents/backend/tools/retry-audit.js +132 -0
- package/src/agents/backend/tools/timeout-audit.js +144 -0
- package/src/agents/code-quality/index.js +12 -0
- package/src/agents/code-quality/tools/base.js +159 -0
- package/src/agents/code-quality/tools/complexity-measure.js +197 -0
- package/src/agents/code-quality/tools/coupling-analysis.js +81 -0
- package/src/agents/code-quality/tools/cycle-detect.js +49 -0
- package/src/agents/code-quality/tools/dep-graph.js +196 -0
- package/src/agents/code-quality/tools/index.js +89 -0
- package/src/agents/data-layer/index.js +12 -0
- package/src/agents/data-layer/tools/base.js +181 -0
- package/src/agents/data-layer/tools/index-audit.js +165 -0
- package/src/agents/data-layer/tools/index.js +83 -0
- package/src/agents/data-layer/tools/migration-scan.js +135 -0
- package/src/agents/data-layer/tools/query-explain.js +120 -0
- package/src/agents/data-layer/tools/tenancy-scan.js +166 -0
- package/src/agents/documentation/index.js +12 -0
- package/src/agents/documentation/tools/api-diff.js +91 -0
- package/src/agents/documentation/tools/base.js +151 -0
- package/src/agents/documentation/tools/dead-link-check.js +58 -0
- package/src/agents/documentation/tools/docstring-coverage.js +78 -0
- package/src/agents/documentation/tools/index.js +52 -0
- package/src/agents/documentation/tools/readme-freshness.js +61 -0
- package/src/agents/envelope/fix-cycle.js +45 -0
- package/src/agents/envelope/index.js +31 -0
- package/src/agents/envelope/loop.js +150 -0
- package/src/agents/envelope/pulse.js +18 -0
- package/src/agents/envelope/stream.js +40 -0
- package/src/agents/infrastructure/index.js +12 -0
- package/src/agents/infrastructure/tools/base.js +171 -0
- package/src/agents/infrastructure/tools/checkov-run.js +32 -0
- package/src/agents/infrastructure/tools/drift-detect.js +59 -0
- package/src/agents/infrastructure/tools/iam-least-priv-check.js +78 -0
- package/src/agents/infrastructure/tools/index.js +52 -0
- package/src/agents/infrastructure/tools/tflint-run.js +31 -0
- package/src/agents/jules/loop.js +7 -4
- package/src/agents/jules/swarm/sub-agent.js +5 -1
- package/src/agents/jules/tools/auth-audit.js +10 -1
- package/src/agents/mode.js +113 -0
- package/src/agents/observability/index.js +12 -0
- package/src/agents/observability/tools/alert-audit.js +39 -0
- package/src/agents/observability/tools/base.js +181 -0
- package/src/agents/observability/tools/dashboard-gap.js +42 -0
- package/src/agents/observability/tools/index.js +54 -0
- package/src/agents/observability/tools/log-schema-check.js +74 -0
- package/src/agents/observability/tools/span-coverage.js +74 -0
- package/src/agents/persona-visuals.js +38 -0
- package/src/agents/release/index.js +12 -0
- package/src/agents/release/tools/base.js +181 -0
- package/src/agents/release/tools/changelog-diff.js +86 -0
- package/src/agents/release/tools/feature-flag-audit.js +126 -0
- package/src/agents/release/tools/index.js +61 -0
- package/src/agents/release/tools/rollback-verify.js +129 -0
- package/src/agents/release/tools/semver-check.js +109 -0
- package/src/agents/reliability/index.js +12 -0
- package/src/agents/reliability/tools/backpressure-check.js +129 -0
- package/src/agents/reliability/tools/base.js +181 -0
- package/src/agents/reliability/tools/chaos-probe.js +109 -0
- package/src/agents/reliability/tools/graceful-degradation-check.js +114 -0
- package/src/agents/reliability/tools/health-check-audit.js +111 -0
- package/src/agents/reliability/tools/index.js +87 -0
- package/src/agents/run-persona.js +109 -0
- package/src/agents/security/index.js +12 -0
- package/src/agents/security/tools/authz-audit.js +134 -0
- package/src/agents/security/tools/base.js +190 -0
- package/src/agents/security/tools/crypto-review.js +175 -0
- package/src/agents/security/tools/index.js +97 -0
- package/src/agents/security/tools/sast-scan.js +175 -0
- package/src/agents/security/tools/secrets-scan.js +216 -0
- package/src/agents/supply-chain/index.js +12 -0
- package/src/agents/supply-chain/tools/attestation-check.js +42 -0
- package/src/agents/supply-chain/tools/base.js +151 -0
- package/src/agents/supply-chain/tools/index.js +52 -0
- package/src/agents/supply-chain/tools/lockfile-integrity.js +73 -0
- package/src/agents/supply-chain/tools/package-verify.js +56 -0
- package/src/agents/supply-chain/tools/sbom-diff.js +34 -0
- package/src/agents/testing/index.js +12 -0
- package/src/agents/testing/tools/base.js +202 -0
- package/src/agents/testing/tools/coverage-gap.js +144 -0
- package/src/agents/testing/tools/flake-detect.js +125 -0
- package/src/agents/testing/tools/index.js +85 -0
- package/src/agents/testing/tools/mutation-test.js +143 -0
- package/src/agents/testing/tools/snapshot-diff.js +103 -0
- package/src/auth/gate.js +65 -37
- package/src/cli.js +1 -1
- package/src/commands/chat.js +3 -10
- package/src/commands/legacy-args.js +10 -0
- package/src/commands/omargate.js +36 -2
- package/src/commands/persona.js +46 -1
- package/src/commands/scan.js +3 -10
- package/src/commands/session.js +654 -6
- package/src/commands/spec.js +3 -10
- package/src/coord/events-log.js +141 -0
- package/src/coord/handshake.js +719 -0
- package/src/coord/index.js +35 -0
- package/src/coord/paths.js +84 -0
- package/src/coord/priority.js +62 -0
- package/src/coord/tarjan.js +157 -0
- package/src/cost/tokenizer.js +160 -0
- package/src/cost/tracker.js +61 -0
- package/src/daemon/artifact-lineage.js +362 -0
- package/src/daemon/assignment-ledger.js +117 -0
- package/src/daemon/ast-drift.js +496 -0
- package/src/daemon/ingest-refresh.js +69 -2
- package/src/ingest/engine.js +15 -0
- package/src/ingest/ownership.js +380 -0
- package/src/legacy-cli.js +68 -1
- package/src/orchestrator/kai-chen.js +126 -0
- package/src/review/ai-review.js +3 -10
- package/src/review/compliance-pack.js +389 -0
- package/src/review/investor-dd-config.js +54 -0
- package/src/review/investor-dd-file-loop.js +303 -0
- package/src/review/investor-dd-file-router.js +406 -0
- package/src/review/investor-dd-html-report.js +233 -0
- package/src/review/investor-dd-notification.js +120 -0
- package/src/review/investor-dd-orchestrator.js +405 -0
- package/src/review/investor-dd-persona-runner.js +275 -0
- package/src/review/live-validator.js +253 -0
- package/src/review/omargate-orchestrator.js +90 -2
- package/src/review/persona-prompts.js +244 -56
- package/src/review/reconciliation-rules.js +329 -0
- package/src/review/reproducibility-chain.js +136 -0
- package/src/review/scan-modes.js +102 -3
- package/src/session/agent-registry.js +7 -0
- package/src/session/analytics.js +479 -0
- package/src/session/daemon.js +609 -14
- package/src/session/file-locks.js +666 -0
- package/src/session/paths.js +4 -0
- package/src/session/recap.js +567 -0
- package/src/session/redact.js +82 -0
- package/src/session/runtime-bridge.js +24 -1
- package/src/session/scoring.js +406 -0
- package/src/session/setup-guides.js +304 -0
- package/src/session/store.js +318 -2
- package/src/session/stream.js +9 -1
- package/src/session/sync.js +753 -0
- package/src/session/tasks.js +1054 -0
- package/src/session/templates.js +188 -0
- package/src/swarm/runtime.js +1 -8
package/src/commands/session.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import process from "node:process";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
3
4
|
|
|
4
5
|
import pc from "picocolors";
|
|
5
6
|
|
|
7
|
+
import { SentinelayerApiError, requestJsonMutation } from "../auth/http.js";
|
|
8
|
+
import {
|
|
9
|
+
buildProvisionEmailPayload,
|
|
10
|
+
normalizeAidenIdApiUrl,
|
|
11
|
+
provisionEmailIdentity,
|
|
12
|
+
resolveAidenIdCredentials,
|
|
13
|
+
} from "../ai/aidenid.js";
|
|
14
|
+
import { recordProvisionedIdentity } from "../ai/identity-store.js";
|
|
15
|
+
import { readStoredSession } from "../auth/session-store.js";
|
|
16
|
+
import { fetchAidenIdCredentials } from "../auth/service.js";
|
|
17
|
+
import { resolveActiveAuthSession } from "../auth/service.js";
|
|
18
|
+
import { resolveOutputRoot } from "../config/service.js";
|
|
6
19
|
import {
|
|
7
20
|
listAssignments,
|
|
8
21
|
releaseLease,
|
|
@@ -17,13 +30,32 @@ import {
|
|
|
17
30
|
} from "../session/agent-registry.js";
|
|
18
31
|
import { stopSenti } from "../session/daemon.js";
|
|
19
32
|
import { listRuntimeRuns } from "../session/runtime-bridge.js";
|
|
33
|
+
import {
|
|
34
|
+
listFileLocks,
|
|
35
|
+
releaseFileLocksForAgent,
|
|
36
|
+
} from "../session/file-locks.js";
|
|
37
|
+
import {
|
|
38
|
+
injectSessionGuides,
|
|
39
|
+
setupSessionGuides,
|
|
40
|
+
} from "../session/setup-guides.js";
|
|
41
|
+
import { listSessionTasks } from "../session/tasks.js";
|
|
20
42
|
import {
|
|
21
43
|
createSession,
|
|
22
44
|
DEFAULT_TTL_SECONDS,
|
|
23
45
|
getSession,
|
|
24
46
|
listActiveSessions,
|
|
47
|
+
recordSessionProvisionedIdentities,
|
|
25
48
|
} from "../session/store.js";
|
|
26
49
|
import { appendToStream, readStream, tailStream } from "../session/stream.js";
|
|
50
|
+
import { syncSessionMetadataToApi } from "../session/sync.js";
|
|
51
|
+
import {
|
|
52
|
+
buildDashboardUrl,
|
|
53
|
+
buildTemplateLaunchPlan,
|
|
54
|
+
getTemplateRegistry,
|
|
55
|
+
resolveSessionTemplate,
|
|
56
|
+
} from "../session/templates.js";
|
|
57
|
+
import { authLoginHint } from "../ui/command-hints.js";
|
|
58
|
+
import { parseCsvTokens } from "./ai/shared.js";
|
|
27
59
|
|
|
28
60
|
function shouldEmitJson(options, command) {
|
|
29
61
|
const local = Boolean(options && options.json);
|
|
@@ -55,6 +87,29 @@ function normalizeAgentId(value, fallbackValue = "cli-user") {
|
|
|
55
87
|
return normalized || fallbackValue;
|
|
56
88
|
}
|
|
57
89
|
|
|
90
|
+
async function runWithConcurrency(items = [], concurrency = 1, worker = async () => null) {
|
|
91
|
+
const normalizedItems = Array.isArray(items) ? items : [];
|
|
92
|
+
const normalizedConcurrency = Math.max(
|
|
93
|
+
1,
|
|
94
|
+
Math.min(
|
|
95
|
+
normalizedItems.length || 1,
|
|
96
|
+
Number.isFinite(Number(concurrency)) ? Math.floor(Number(concurrency)) : 1
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
const results = new Array(normalizedItems.length);
|
|
100
|
+
let cursor = 0;
|
|
101
|
+
|
|
102
|
+
const runners = Array.from({ length: normalizedConcurrency }, async () => {
|
|
103
|
+
while (cursor < normalizedItems.length) {
|
|
104
|
+
const index = cursor;
|
|
105
|
+
cursor += 1;
|
|
106
|
+
results[index] = await worker(normalizedItems[index], index);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
await Promise.all(runners);
|
|
110
|
+
return results;
|
|
111
|
+
}
|
|
112
|
+
|
|
58
113
|
function resolveSessionIdOption(options = {}) {
|
|
59
114
|
const sessionId = normalizeString(options.session || options.id);
|
|
60
115
|
if (!sessionId) {
|
|
@@ -75,6 +130,78 @@ function formatEventLine(event = {}) {
|
|
|
75
130
|
return `${ts} ${agentId} ${type}`;
|
|
76
131
|
}
|
|
77
132
|
|
|
133
|
+
function formatTemplateLaunchLine(slot = {}) {
|
|
134
|
+
const terminal = Number(slot.terminal || 0);
|
|
135
|
+
const role = normalizeString(slot.role) || "agent";
|
|
136
|
+
const command = normalizeString(slot.command);
|
|
137
|
+
return `Terminal ${terminal} (${role}): ${command}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function formatApiError(error) {
|
|
141
|
+
if (!(error instanceof SentinelayerApiError)) {
|
|
142
|
+
return error instanceof Error ? error.message : String(error || "Unknown API error");
|
|
143
|
+
}
|
|
144
|
+
const requestId = error.requestId ? ` request_id=${error.requestId}` : "";
|
|
145
|
+
return `${error.message} [${error.code}] status=${error.status}${requestId}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function resolveAdminApiSession({ targetPath, explicitApiUrl }) {
|
|
149
|
+
const session = await resolveActiveAuthSession({
|
|
150
|
+
cwd: targetPath,
|
|
151
|
+
env: process.env,
|
|
152
|
+
explicitApiUrl,
|
|
153
|
+
autoRotate: true,
|
|
154
|
+
});
|
|
155
|
+
if (!session || !session.token) {
|
|
156
|
+
throw new Error(`No active auth token found. Run \`${authLoginHint()}\` first.`);
|
|
157
|
+
}
|
|
158
|
+
return session;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function postAdminSessionMutation({
|
|
162
|
+
session,
|
|
163
|
+
pathSuffix,
|
|
164
|
+
operationName,
|
|
165
|
+
body = {},
|
|
166
|
+
headers = {},
|
|
167
|
+
} = {}) {
|
|
168
|
+
const apiUrl = normalizeString(session?.apiUrl).replace(/\/+$/, "");
|
|
169
|
+
if (!apiUrl) {
|
|
170
|
+
throw new Error("Missing apiUrl for admin session mutation.");
|
|
171
|
+
}
|
|
172
|
+
return requestJsonMutation(`${apiUrl}${pathSuffix}`, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
operationName,
|
|
175
|
+
headers: {
|
|
176
|
+
Authorization: `Bearer ${normalizeString(session.token)}`,
|
|
177
|
+
...headers,
|
|
178
|
+
},
|
|
179
|
+
body,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function emitLocalAdminKillEvent(
|
|
184
|
+
sessionId,
|
|
185
|
+
{ targetPath, reason, scope, apiResult, actorId = "admin" } = {}
|
|
186
|
+
) {
|
|
187
|
+
const session = await getSession(sessionId, { targetPath });
|
|
188
|
+
if (!session) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const event = createAgentEvent({
|
|
192
|
+
event: "session_admin_kill",
|
|
193
|
+
agentId: actorId,
|
|
194
|
+
agentModel: "api-admin",
|
|
195
|
+
sessionId,
|
|
196
|
+
payload: {
|
|
197
|
+
scope: normalizeString(scope) || "session",
|
|
198
|
+
reason: normalizeString(reason) || "admin_kill",
|
|
199
|
+
result: apiResult && typeof apiResult === "object" ? apiResult : null,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
return appendToStream(sessionId, event, { targetPath });
|
|
203
|
+
}
|
|
204
|
+
|
|
78
205
|
async function revokeAgentLeases(sessionId, agentId, { targetPath, reason } = {}) {
|
|
79
206
|
const active = await listAssignments({
|
|
80
207
|
targetPath,
|
|
@@ -127,21 +254,36 @@ export function registerSessionCommand(program) {
|
|
|
127
254
|
.command("start")
|
|
128
255
|
.description("Create a new persistent session with metadata + NDJSON stream")
|
|
129
256
|
.option("--path <path>", "Workspace path for the session", ".")
|
|
257
|
+
.option(
|
|
258
|
+
"--template <name>",
|
|
259
|
+
"Optional quick-start template (code-review, security-audit, e2e-test, incident-response, standup)"
|
|
260
|
+
)
|
|
130
261
|
.option(
|
|
131
262
|
"--ttl-seconds <seconds>",
|
|
132
|
-
`Session time-to-live in seconds (default ${DEFAULT_TTL_SECONDS})
|
|
133
|
-
String(DEFAULT_TTL_SECONDS)
|
|
263
|
+
`Session time-to-live in seconds (default ${DEFAULT_TTL_SECONDS}; template defaults override when omitted)`
|
|
134
264
|
)
|
|
135
265
|
.option("--json", "Emit machine-readable output")
|
|
136
266
|
.action(async (options, command) => {
|
|
137
267
|
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
138
|
-
const
|
|
268
|
+
const template = resolveSessionTemplate(options.template);
|
|
269
|
+
const templateDefaultTtlSeconds =
|
|
270
|
+
template && Number.isFinite(Number(template.ttlHours))
|
|
271
|
+
? Math.max(1, Math.floor(Number(template.ttlHours))) * 60 * 60
|
|
272
|
+
: DEFAULT_TTL_SECONDS;
|
|
273
|
+
const ttlSeconds = parsePositiveInteger(
|
|
274
|
+
options.ttlSeconds,
|
|
275
|
+
"ttl-seconds",
|
|
276
|
+
templateDefaultTtlSeconds
|
|
277
|
+
);
|
|
139
278
|
const startedAt = Date.now();
|
|
140
279
|
const created = await createSession({
|
|
141
280
|
targetPath,
|
|
142
281
|
ttlSeconds,
|
|
282
|
+
template,
|
|
143
283
|
});
|
|
144
284
|
const durationMs = Date.now() - startedAt;
|
|
285
|
+
const launchPlan = template ? buildTemplateLaunchPlan(created.sessionId, template) : [];
|
|
286
|
+
const dashboardUrl = buildDashboardUrl(created.sessionId);
|
|
145
287
|
|
|
146
288
|
const payload = {
|
|
147
289
|
command: "session start",
|
|
@@ -153,16 +295,46 @@ export function registerSessionCommand(program) {
|
|
|
153
295
|
streamPath: created.streamPath,
|
|
154
296
|
createdAt: created.createdAt,
|
|
155
297
|
expiresAt: created.expiresAt,
|
|
298
|
+
ttlSeconds,
|
|
156
299
|
elapsedTimer: created.elapsedTimer,
|
|
157
300
|
renewalCount: created.renewalCount,
|
|
158
301
|
status: created.status,
|
|
302
|
+
template: created.template,
|
|
303
|
+
launchPlan,
|
|
304
|
+
dashboardUrl,
|
|
159
305
|
};
|
|
160
306
|
|
|
307
|
+
// Best-effort admin visibility sync. Session creation remains local-first.
|
|
308
|
+
void syncSessionMetadataToApi(created.sessionId, {
|
|
309
|
+
targetPath,
|
|
310
|
+
sessionId: created.sessionId,
|
|
311
|
+
status: created.status,
|
|
312
|
+
createdAt: created.createdAt,
|
|
313
|
+
expiresAt: created.expiresAt,
|
|
314
|
+
ttlSeconds,
|
|
315
|
+
template: created.template,
|
|
316
|
+
codebaseContext: created.codebaseContext,
|
|
317
|
+
}).catch(() => {});
|
|
318
|
+
|
|
161
319
|
if (shouldEmitJson(options, command)) {
|
|
162
320
|
console.log(JSON.stringify(payload, null, 2));
|
|
163
321
|
return;
|
|
164
322
|
}
|
|
165
323
|
|
|
324
|
+
if (template) {
|
|
325
|
+
console.log(`Session ${created.sessionId} created (template: ${template.id})`);
|
|
326
|
+
if (launchPlan.length > 0) {
|
|
327
|
+
console.log("");
|
|
328
|
+
console.log("Launch your agents:");
|
|
329
|
+
for (const slot of launchPlan) {
|
|
330
|
+
console.log(formatTemplateLaunchLine(slot));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
console.log("");
|
|
334
|
+
console.log(`Dashboard: ${dashboardUrl}`);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
166
338
|
console.log(pc.bold("Session created"));
|
|
167
339
|
console.log(pc.gray(`Session: ${created.sessionId}`));
|
|
168
340
|
console.log(pc.gray(`Stream: ${created.streamPath}`));
|
|
@@ -172,6 +344,26 @@ export function registerSessionCommand(program) {
|
|
|
172
344
|
);
|
|
173
345
|
});
|
|
174
346
|
|
|
347
|
+
session
|
|
348
|
+
.command("templates")
|
|
349
|
+
.description("List available session quick-start templates")
|
|
350
|
+
.option("--json", "Emit machine-readable output")
|
|
351
|
+
.action(async (options, command) => {
|
|
352
|
+
const registry = getTemplateRegistry();
|
|
353
|
+
const payload = {
|
|
354
|
+
command: "session templates",
|
|
355
|
+
...registry,
|
|
356
|
+
};
|
|
357
|
+
if (shouldEmitJson(options, command)) {
|
|
358
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
console.log(`Session templates (registry ${registry.registryVersion}):`);
|
|
362
|
+
for (const template of registry.templates) {
|
|
363
|
+
console.log(`- ${template.id}: ${template.description}`);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
175
367
|
session
|
|
176
368
|
.command("join <sessionId>")
|
|
177
369
|
.description("Join an active session")
|
|
@@ -325,7 +517,7 @@ export function registerSessionCommand(program) {
|
|
|
325
517
|
throw new Error(`Session '${normalizedSessionId}' was not found.`);
|
|
326
518
|
}
|
|
327
519
|
|
|
328
|
-
const [agents, runtimeRuns, leases, recentEvents] = await Promise.all([
|
|
520
|
+
const [agents, runtimeRuns, leases, fileLocks, activeTasks, recentEvents] = await Promise.all([
|
|
329
521
|
listAgents(normalizedSessionId, {
|
|
330
522
|
targetPath,
|
|
331
523
|
includeInactive: false,
|
|
@@ -344,6 +536,15 @@ export function registerSessionCommand(program) {
|
|
|
344
536
|
includeExpired: true,
|
|
345
537
|
limit: 100,
|
|
346
538
|
}),
|
|
539
|
+
listFileLocks(normalizedSessionId, {
|
|
540
|
+
targetPath,
|
|
541
|
+
emitExpiredEvents: false,
|
|
542
|
+
}),
|
|
543
|
+
listSessionTasks(normalizedSessionId, {
|
|
544
|
+
targetPath,
|
|
545
|
+
statuses: ["PENDING", "ACCEPTED"],
|
|
546
|
+
limit: 100,
|
|
547
|
+
}),
|
|
347
548
|
readStream(normalizedSessionId, {
|
|
348
549
|
targetPath,
|
|
349
550
|
tail: 10,
|
|
@@ -360,6 +561,8 @@ export function registerSessionCommand(program) {
|
|
|
360
561
|
staleAgents,
|
|
361
562
|
runtimeRuns,
|
|
362
563
|
activeLeases: leases.assignments,
|
|
564
|
+
activeFileLocks: fileLocks,
|
|
565
|
+
activeTasks: activeTasks.tasks,
|
|
363
566
|
recentEvents,
|
|
364
567
|
};
|
|
365
568
|
if (shouldEmitJson(options, command)) {
|
|
@@ -370,7 +573,7 @@ export function registerSessionCommand(program) {
|
|
|
370
573
|
console.log(pc.bold(`Session ${normalizedSessionId}`));
|
|
371
574
|
console.log(
|
|
372
575
|
pc.gray(
|
|
373
|
-
`status=${sessionPayload.status} agents=${agents.length} stale=${staleAgents.length} runs=${runtimeRuns.length} leases=${leases.assignments.length}`
|
|
576
|
+
`status=${sessionPayload.status} agents=${agents.length} stale=${staleAgents.length} runs=${runtimeRuns.length} leases=${leases.assignments.length} locks=${fileLocks.length} tasks=${activeTasks.tasks.length}`
|
|
374
577
|
)
|
|
375
578
|
);
|
|
376
579
|
for (const event of recentEvents) {
|
|
@@ -443,6 +646,441 @@ export function registerSessionCommand(program) {
|
|
|
443
646
|
}
|
|
444
647
|
});
|
|
445
648
|
|
|
649
|
+
session
|
|
650
|
+
.command("setup-guides <sessionId>")
|
|
651
|
+
.description("Generate or update AGENTS.md and CLAUDE.md with session coordination rules")
|
|
652
|
+
.option("--path <path>", "Workspace path for the session", ".")
|
|
653
|
+
.option("--json", "Emit machine-readable output")
|
|
654
|
+
.action(async (sessionId, options, command) => {
|
|
655
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
656
|
+
if (!normalizedSessionId) {
|
|
657
|
+
throw new Error("session id is required.");
|
|
658
|
+
}
|
|
659
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
660
|
+
const result = await setupSessionGuides(normalizedSessionId, {
|
|
661
|
+
targetPath,
|
|
662
|
+
});
|
|
663
|
+
const payload = {
|
|
664
|
+
command: "session setup-guides",
|
|
665
|
+
targetPath,
|
|
666
|
+
sessionId: normalizedSessionId,
|
|
667
|
+
sectionHeading: result.sectionHeading,
|
|
668
|
+
agents: result.agents,
|
|
669
|
+
claude: result.claude,
|
|
670
|
+
sessionGuide: result.sessionGuide,
|
|
671
|
+
};
|
|
672
|
+
if (shouldEmitJson(options, command)) {
|
|
673
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
console.log(pc.bold(`Session guide sync complete for ${normalizedSessionId}`));
|
|
678
|
+
console.log(pc.gray(`AGENTS.md: changed=${result.agents.changed} path=${result.agents.path}`));
|
|
679
|
+
console.log(pc.gray(`CLAUDE.md: changed=${result.claude.changed} path=${result.claude.path}`));
|
|
680
|
+
console.log(
|
|
681
|
+
pc.gray(
|
|
682
|
+
`.sentinelayer/AGENTS_SESSION_GUIDE.md: changed=${result.sessionGuide.changed} path=${result.sessionGuide.path}`
|
|
683
|
+
)
|
|
684
|
+
);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
session
|
|
688
|
+
.command("inject-guide <sessionId>")
|
|
689
|
+
.description("Append coordination section to existing AGENTS.md and CLAUDE.md files")
|
|
690
|
+
.option("--path <path>", "Workspace path for the session", ".")
|
|
691
|
+
.option("--json", "Emit machine-readable output")
|
|
692
|
+
.action(async (sessionId, options, command) => {
|
|
693
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
694
|
+
if (!normalizedSessionId) {
|
|
695
|
+
throw new Error("session id is required.");
|
|
696
|
+
}
|
|
697
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
698
|
+
const result = await injectSessionGuides(normalizedSessionId, {
|
|
699
|
+
targetPath,
|
|
700
|
+
});
|
|
701
|
+
const payload = {
|
|
702
|
+
command: "session inject-guide",
|
|
703
|
+
targetPath,
|
|
704
|
+
sessionId: normalizedSessionId,
|
|
705
|
+
sectionHeading: result.sectionHeading,
|
|
706
|
+
agents: result.agents,
|
|
707
|
+
claude: result.claude,
|
|
708
|
+
};
|
|
709
|
+
if (shouldEmitJson(options, command)) {
|
|
710
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
console.log(pc.bold(`Session guide section injected for ${normalizedSessionId}`));
|
|
715
|
+
console.log(pc.gray(`AGENTS.md: existed=${result.agents.existed} changed=${result.agents.changed}`));
|
|
716
|
+
console.log(pc.gray(`CLAUDE.md: existed=${result.claude.existed} changed=${result.claude.changed}`));
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
session
|
|
720
|
+
.command("provision-emails <sessionId>")
|
|
721
|
+
.description("Provision ephemeral AIdenID emails for swarm testing")
|
|
722
|
+
.option("--count <n>", "Number of emails to provision", "5")
|
|
723
|
+
.option("--tags <csv>", "Tags for provisioned identities", "session,swarm")
|
|
724
|
+
.option("--ttl-hours <hours>", "Identity TTL in hours", "24")
|
|
725
|
+
.option("--alias-template <value>", "Optional alias template override")
|
|
726
|
+
.option("--concurrency <n>", "Parallel provision requests (max 10)", "10")
|
|
727
|
+
.option("--path <path>", "Workspace path for the session", ".")
|
|
728
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
729
|
+
.option("--api-url <url>", "AIdenID API base URL", "https://api.aidenid.com")
|
|
730
|
+
.option("--api-key <key>", "AIdenID API key (or use AIDENID_API_KEY env)")
|
|
731
|
+
.option("--org-id <id>", "AIdenID org id (or use AIDENID_ORG_ID env)")
|
|
732
|
+
.option("--project-id <id>", "AIdenID project id (or use AIDENID_PROJECT_ID env)")
|
|
733
|
+
.option("--dry-run", "Plan provisioning without executing remote API calls")
|
|
734
|
+
.option("--json", "Emit machine-readable output")
|
|
735
|
+
.action(async (sessionId, options, command) => {
|
|
736
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
737
|
+
if (!normalizedSessionId) {
|
|
738
|
+
throw new Error("session id is required.");
|
|
739
|
+
}
|
|
740
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
741
|
+
const sessionPayload = await getSession(normalizedSessionId, { targetPath });
|
|
742
|
+
if (!sessionPayload) {
|
|
743
|
+
throw new Error(`Session '${normalizedSessionId}' was not found.`);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const count = parsePositiveInteger(options.count, "count", 5);
|
|
747
|
+
if (count > 50) {
|
|
748
|
+
throw new Error("count must be <= 50 for a single provisioning batch.");
|
|
749
|
+
}
|
|
750
|
+
const ttlHours = parsePositiveInteger(options.ttlHours, "ttl-hours", 24);
|
|
751
|
+
if (ttlHours > 24 * 30) {
|
|
752
|
+
throw new Error("ttl-hours must be between 1 and 720.");
|
|
753
|
+
}
|
|
754
|
+
const requestedConcurrency = parsePositiveInteger(options.concurrency, "concurrency", 10);
|
|
755
|
+
const concurrency = Math.max(1, Math.min(10, requestedConcurrency, count));
|
|
756
|
+
const tags = parseCsvTokens(options.tags, ["session", "swarm"]);
|
|
757
|
+
const apiUrl = normalizeAidenIdApiUrl(options.apiUrl);
|
|
758
|
+
const outputRoot = await resolveOutputRoot({
|
|
759
|
+
cwd: targetPath,
|
|
760
|
+
outputDirOverride: options.outputDir,
|
|
761
|
+
env: process.env,
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
const aliasBase =
|
|
765
|
+
normalizeString(options.aliasTemplate) ||
|
|
766
|
+
`session-${normalizedSessionId.slice(0, 8)}-identity`;
|
|
767
|
+
|
|
768
|
+
if (Boolean(options.dryRun)) {
|
|
769
|
+
const planned = Array.from({ length: count }, (_, index) => ({
|
|
770
|
+
index: index + 1,
|
|
771
|
+
aliasTemplate: `${aliasBase}-${index + 1}`,
|
|
772
|
+
tags,
|
|
773
|
+
ttlHours,
|
|
774
|
+
}));
|
|
775
|
+
const payload = {
|
|
776
|
+
command: "session provision-emails",
|
|
777
|
+
execute: false,
|
|
778
|
+
sessionId: normalizedSessionId,
|
|
779
|
+
targetPath,
|
|
780
|
+
apiUrl,
|
|
781
|
+
requestedCount: count,
|
|
782
|
+
concurrency,
|
|
783
|
+
tags,
|
|
784
|
+
planned,
|
|
785
|
+
};
|
|
786
|
+
if (shouldEmitJson(options, command)) {
|
|
787
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
console.log(pc.bold(`Provision plan ready for session ${normalizedSessionId}`));
|
|
791
|
+
console.log(pc.gray(`count=${count} concurrency=${concurrency} api=${apiUrl}`));
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
let storedSession = null;
|
|
796
|
+
try {
|
|
797
|
+
storedSession = await readStoredSession();
|
|
798
|
+
} catch {
|
|
799
|
+
storedSession = null;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const fetchCredentials =
|
|
803
|
+
storedSession && storedSession.token
|
|
804
|
+
? () =>
|
|
805
|
+
fetchAidenIdCredentials({
|
|
806
|
+
apiUrl: storedSession.apiUrl,
|
|
807
|
+
token: storedSession.token,
|
|
808
|
+
})
|
|
809
|
+
: null;
|
|
810
|
+
const credentials = await resolveAidenIdCredentials({
|
|
811
|
+
apiKey: options.apiKey,
|
|
812
|
+
orgId: options.orgId,
|
|
813
|
+
projectId: options.projectId,
|
|
814
|
+
env: process.env,
|
|
815
|
+
requireAll: true,
|
|
816
|
+
session: storedSession,
|
|
817
|
+
fetchCredentials,
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
const startedAt = Date.now();
|
|
821
|
+
const indices = Array.from({ length: count }, (_, index) => index);
|
|
822
|
+
const provisioned = await runWithConcurrency(indices, concurrency, async (index) => {
|
|
823
|
+
const idempotencyKey = `session-${normalizedSessionId}-${index + 1}-${randomUUID()}`;
|
|
824
|
+
const payload = buildProvisionEmailPayload({
|
|
825
|
+
aliasTemplate: `${aliasBase}-${index + 1}`,
|
|
826
|
+
ttlHours,
|
|
827
|
+
tags,
|
|
828
|
+
});
|
|
829
|
+
const execution = await provisionEmailIdentity({
|
|
830
|
+
apiUrl,
|
|
831
|
+
apiKey: credentials.apiKey,
|
|
832
|
+
orgId: credentials.orgId,
|
|
833
|
+
projectId: credentials.projectId,
|
|
834
|
+
idempotencyKey,
|
|
835
|
+
payload,
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
const responseIdentity = execution.response || {};
|
|
839
|
+
return {
|
|
840
|
+
index: index + 1,
|
|
841
|
+
idempotencyKey,
|
|
842
|
+
identityId: normalizeString(responseIdentity.id) || null,
|
|
843
|
+
emailAddress: normalizeString(responseIdentity.emailAddress) || null,
|
|
844
|
+
status: normalizeString(responseIdentity.status) || null,
|
|
845
|
+
expiresAt: responseIdentity.expiresAt || null,
|
|
846
|
+
response: responseIdentity,
|
|
847
|
+
};
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
for (const identity of provisioned) {
|
|
851
|
+
await recordProvisionedIdentity({
|
|
852
|
+
outputRoot,
|
|
853
|
+
response: identity.response || {},
|
|
854
|
+
context: {
|
|
855
|
+
source: "session-provision-emails",
|
|
856
|
+
apiUrl,
|
|
857
|
+
orgId: credentials.orgId,
|
|
858
|
+
projectId: credentials.projectId,
|
|
859
|
+
idempotencyKey: identity.idempotencyKey,
|
|
860
|
+
tags,
|
|
861
|
+
},
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const identityIds = provisioned
|
|
866
|
+
.map((identity) => normalizeString(identity.identityId))
|
|
867
|
+
.filter(Boolean);
|
|
868
|
+
const updatedSession = await recordSessionProvisionedIdentities(normalizedSessionId, {
|
|
869
|
+
targetPath,
|
|
870
|
+
identityIds,
|
|
871
|
+
tags,
|
|
872
|
+
});
|
|
873
|
+
const streamEvent = await appendToStream(
|
|
874
|
+
normalizedSessionId,
|
|
875
|
+
createAgentEvent({
|
|
876
|
+
event: "session_provision_emails",
|
|
877
|
+
agentId: "senti",
|
|
878
|
+
agentModel: "gpt-5.4-mini",
|
|
879
|
+
sessionId: normalizedSessionId,
|
|
880
|
+
payload: {
|
|
881
|
+
requestedCount: count,
|
|
882
|
+
provisionedCount: provisioned.length,
|
|
883
|
+
identityIds,
|
|
884
|
+
tags,
|
|
885
|
+
ttlHours,
|
|
886
|
+
concurrency,
|
|
887
|
+
},
|
|
888
|
+
}),
|
|
889
|
+
{ targetPath }
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
const durationMs = Date.now() - startedAt;
|
|
893
|
+
const payload = {
|
|
894
|
+
command: "session provision-emails",
|
|
895
|
+
execute: true,
|
|
896
|
+
targetPath,
|
|
897
|
+
outputRoot,
|
|
898
|
+
durationMs,
|
|
899
|
+
sessionId: normalizedSessionId,
|
|
900
|
+
apiUrl,
|
|
901
|
+
requestedCount: count,
|
|
902
|
+
provisionedCount: provisioned.length,
|
|
903
|
+
concurrency,
|
|
904
|
+
tags,
|
|
905
|
+
ttlHours,
|
|
906
|
+
identities: provisioned,
|
|
907
|
+
sharedResources: updatedSession.sharedResources,
|
|
908
|
+
event: streamEvent,
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
if (shouldEmitJson(options, command)) {
|
|
912
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
console.log(pc.bold(`Provisioned ${provisioned.length} identities for session ${normalizedSessionId}`));
|
|
916
|
+
console.log(pc.gray(`concurrency=${concurrency} duration_ms=${durationMs}`));
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
session
|
|
920
|
+
.command("admin-kill <sessionId>")
|
|
921
|
+
.description("Admin: kill a remote session through sentinelayer-api")
|
|
922
|
+
.option("--reason <reason>", "Kill reason", "admin_kill")
|
|
923
|
+
.option("--api-url <url>", "Override Sentinelayer API base URL")
|
|
924
|
+
.option("--path <path>", "Workspace path for local stream sync", ".")
|
|
925
|
+
.option("--json", "Emit machine-readable output")
|
|
926
|
+
.action(async (sessionId, options, command) => {
|
|
927
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
928
|
+
if (!normalizedSessionId) {
|
|
929
|
+
throw new Error("session id is required.");
|
|
930
|
+
}
|
|
931
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
932
|
+
const reason = normalizeString(options.reason) || "admin_kill";
|
|
933
|
+
|
|
934
|
+
let apiSession;
|
|
935
|
+
try {
|
|
936
|
+
apiSession = await resolveAdminApiSession({
|
|
937
|
+
targetPath,
|
|
938
|
+
explicitApiUrl: options.apiUrl,
|
|
939
|
+
});
|
|
940
|
+
} catch (error) {
|
|
941
|
+
throw new Error(formatApiError(error));
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
let result;
|
|
945
|
+
try {
|
|
946
|
+
result = await postAdminSessionMutation({
|
|
947
|
+
session: apiSession,
|
|
948
|
+
pathSuffix: `/api/v1/admin/sessions/${encodeURIComponent(normalizedSessionId)}/kill`,
|
|
949
|
+
operationName: "session-admin-kill",
|
|
950
|
+
body: { reason },
|
|
951
|
+
});
|
|
952
|
+
} catch (error) {
|
|
953
|
+
throw new Error(formatApiError(error));
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
let localEvent = null;
|
|
957
|
+
try {
|
|
958
|
+
localEvent = await emitLocalAdminKillEvent(normalizedSessionId, {
|
|
959
|
+
targetPath,
|
|
960
|
+
reason,
|
|
961
|
+
scope: "session",
|
|
962
|
+
apiResult: result,
|
|
963
|
+
});
|
|
964
|
+
} catch {
|
|
965
|
+
localEvent = null;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const payload = {
|
|
969
|
+
command: "session admin-kill",
|
|
970
|
+
targetPath,
|
|
971
|
+
sessionId: normalizedSessionId,
|
|
972
|
+
reason,
|
|
973
|
+
apiUrl: apiSession.apiUrl,
|
|
974
|
+
tokenSource: apiSession.source,
|
|
975
|
+
result,
|
|
976
|
+
localEventEmitted: Boolean(localEvent),
|
|
977
|
+
};
|
|
978
|
+
if (shouldEmitJson(options, command)) {
|
|
979
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
console.log(pc.bold(`Admin kill completed for session ${normalizedSessionId}`));
|
|
983
|
+
console.log(pc.gray(`api=${apiSession.apiUrl} source=${apiSession.source} reason=${reason}`));
|
|
984
|
+
if (payload.localEventEmitted) {
|
|
985
|
+
console.log(pc.gray("Local stream event emitted."));
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
session
|
|
990
|
+
.command("admin-kill-all")
|
|
991
|
+
.description("Admin: kill all active remote sessions (requires --confirm)")
|
|
992
|
+
.option("--confirm", "Required confirmation flag")
|
|
993
|
+
.option("--reason <reason>", "Kill reason", "admin_global_kill")
|
|
994
|
+
.option("--api-url <url>", "Override Sentinelayer API base URL")
|
|
995
|
+
.option("--path <path>", "Workspace path for local stream sync", ".")
|
|
996
|
+
.option("--json", "Emit machine-readable output")
|
|
997
|
+
.action(async (options, command) => {
|
|
998
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
999
|
+
const reason = normalizeString(options.reason) || "admin_global_kill";
|
|
1000
|
+
const emitJson = shouldEmitJson(options, command);
|
|
1001
|
+
|
|
1002
|
+
if (!options.confirm) {
|
|
1003
|
+
const confirmationMessage = "This will kill ALL active sessions. Pass --confirm to proceed.";
|
|
1004
|
+
const blockedPayload = {
|
|
1005
|
+
command: "session admin-kill-all",
|
|
1006
|
+
targetPath,
|
|
1007
|
+
blocked: true,
|
|
1008
|
+
reason,
|
|
1009
|
+
error: confirmationMessage,
|
|
1010
|
+
};
|
|
1011
|
+
if (emitJson) {
|
|
1012
|
+
console.log(JSON.stringify(blockedPayload, null, 2));
|
|
1013
|
+
} else {
|
|
1014
|
+
console.error(pc.red(confirmationMessage));
|
|
1015
|
+
}
|
|
1016
|
+
process.exitCode = 1;
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
let apiSession;
|
|
1021
|
+
try {
|
|
1022
|
+
apiSession = await resolveAdminApiSession({
|
|
1023
|
+
targetPath,
|
|
1024
|
+
explicitApiUrl: options.apiUrl,
|
|
1025
|
+
});
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
throw new Error(formatApiError(error));
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
let result;
|
|
1031
|
+
try {
|
|
1032
|
+
result = await postAdminSessionMutation({
|
|
1033
|
+
session: apiSession,
|
|
1034
|
+
pathSuffix: "/api/v1/admin/sessions/kill-all",
|
|
1035
|
+
operationName: "session-admin-kill-all",
|
|
1036
|
+
headers: {
|
|
1037
|
+
"X-Confirm-Kill-All": "true",
|
|
1038
|
+
},
|
|
1039
|
+
body: { reason },
|
|
1040
|
+
});
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
throw new Error(formatApiError(error));
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const localSessions = await listActiveSessions({ targetPath });
|
|
1046
|
+
const localSessionIds = [];
|
|
1047
|
+
for (const item of localSessions) {
|
|
1048
|
+
try {
|
|
1049
|
+
const event = await emitLocalAdminKillEvent(item.sessionId, {
|
|
1050
|
+
targetPath,
|
|
1051
|
+
reason,
|
|
1052
|
+
scope: "global",
|
|
1053
|
+
apiResult: result,
|
|
1054
|
+
});
|
|
1055
|
+
if (event) {
|
|
1056
|
+
localSessionIds.push(item.sessionId);
|
|
1057
|
+
}
|
|
1058
|
+
} catch {
|
|
1059
|
+
// Best effort local mirror only.
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
const payload = {
|
|
1064
|
+
command: "session admin-kill-all",
|
|
1065
|
+
targetPath,
|
|
1066
|
+
reason,
|
|
1067
|
+
apiUrl: apiSession.apiUrl,
|
|
1068
|
+
tokenSource: apiSession.source,
|
|
1069
|
+
result,
|
|
1070
|
+
localEventsEmitted: localSessionIds.length,
|
|
1071
|
+
localSessionIds,
|
|
1072
|
+
};
|
|
1073
|
+
if (emitJson) {
|
|
1074
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
console.log(pc.bold("Admin kill-all completed"));
|
|
1078
|
+
console.log(pc.gray(`api=${apiSession.apiUrl} source=${apiSession.source} reason=${reason}`));
|
|
1079
|
+
if (localSessionIds.length > 0) {
|
|
1080
|
+
console.log(pc.gray(`local_events_emitted=${localSessionIds.length}`));
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
|
|
446
1084
|
session
|
|
447
1085
|
.command("kill")
|
|
448
1086
|
.description("Kill a single agent or all agents in a session")
|
|
@@ -486,6 +1124,7 @@ export function registerSessionCommand(program) {
|
|
|
486
1124
|
let runtimeStops = 0;
|
|
487
1125
|
let scopeStops = 0;
|
|
488
1126
|
let leaseRevocations = 0;
|
|
1127
|
+
let lockRevocations = 0;
|
|
489
1128
|
let anyStopped = false;
|
|
490
1129
|
|
|
491
1130
|
for (const agentId of agentsToKill) {
|
|
@@ -542,6 +1181,13 @@ export function registerSessionCommand(program) {
|
|
|
542
1181
|
reason: `agent_killed:${reason}`,
|
|
543
1182
|
});
|
|
544
1183
|
leaseRevocations += releasedCount;
|
|
1184
|
+
|
|
1185
|
+
const releasedLocks = await releaseFileLocksForAgent(sessionId, agentId, {
|
|
1186
|
+
targetPath,
|
|
1187
|
+
reason: `agent_killed:${reason}`,
|
|
1188
|
+
actorAgentId: "senti",
|
|
1189
|
+
});
|
|
1190
|
+
lockRevocations += Number(releasedLocks.releasedCount || 0);
|
|
545
1191
|
anyStopped = anyStopped || stopped;
|
|
546
1192
|
|
|
547
1193
|
results.push({
|
|
@@ -550,6 +1196,7 @@ export function registerSessionCommand(program) {
|
|
|
550
1196
|
runtimeStops: stopDetails.runtimeStops,
|
|
551
1197
|
scopeStops: stopDetails.scopeStops,
|
|
552
1198
|
leaseRevocations: releasedCount,
|
|
1199
|
+
lockRevocations: Number(releasedLocks.releasedCount || 0),
|
|
553
1200
|
});
|
|
554
1201
|
}
|
|
555
1202
|
|
|
@@ -567,6 +1214,7 @@ export function registerSessionCommand(program) {
|
|
|
567
1214
|
runtimeStops,
|
|
568
1215
|
scopeStops,
|
|
569
1216
|
leaseRevocations,
|
|
1217
|
+
lockRevocations,
|
|
570
1218
|
results,
|
|
571
1219
|
};
|
|
572
1220
|
|
|
@@ -582,7 +1230,7 @@ export function registerSessionCommand(program) {
|
|
|
582
1230
|
}
|
|
583
1231
|
console.log(
|
|
584
1232
|
pc.gray(
|
|
585
|
-
`session=${sessionId} runtime_stops=${runtimeStops} scope_stops=${scopeStops} lease_revocations=${leaseRevocations}`
|
|
1233
|
+
`session=${sessionId} runtime_stops=${runtimeStops} scope_stops=${scopeStops} lease_revocations=${leaseRevocations} lock_revocations=${lockRevocations}`
|
|
586
1234
|
)
|
|
587
1235
|
);
|
|
588
1236
|
console.log(`stopped=${payload.stopped} reason=${reason} duration_ms=${durationMs}`);
|