sentinelayer-cli 0.6.2 → 0.8.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/README.md +996 -996
- package/bin/create-sentinelayer.js +5 -5
- package/bin/sentinelayer-cli.js +4 -4
- package/bin/sl.js +5 -5
- package/package.json +64 -63
- package/src/agents/jules/config/definition.js +160 -160
- package/src/agents/jules/config/system-prompt.js +182 -182
- package/src/agents/jules/error-intake.js +51 -51
- package/src/agents/jules/fix-cycle.js +17 -17
- package/src/agents/jules/loop.js +457 -450
- package/src/agents/jules/pulse.js +10 -10
- package/src/agents/jules/stream.js +187 -186
- package/src/agents/jules/swarm/file-scanner.js +74 -74
- package/src/agents/jules/swarm/index.js +11 -11
- package/src/agents/jules/swarm/orchestrator.js +362 -362
- package/src/agents/jules/swarm/pattern-hunter.js +123 -123
- package/src/agents/jules/swarm/sub-agent.js +311 -309
- package/src/agents/jules/tools/aidenid-email.js +189 -189
- package/src/agents/jules/tools/auth-audit.js +1699 -1691
- package/src/agents/jules/tools/dispatch.js +340 -335
- package/src/agents/jules/tools/file-edit.js +2 -2
- package/src/agents/jules/tools/file-read.js +2 -2
- package/src/agents/jules/tools/frontend-analyze.js +570 -570
- package/src/agents/jules/tools/glob.js +2 -2
- package/src/agents/jules/tools/grep.js +2 -2
- package/src/agents/jules/tools/index.js +29 -29
- package/src/agents/jules/tools/path-guards.js +2 -2
- package/src/agents/jules/tools/runtime-audit.js +507 -507
- package/src/agents/jules/tools/shell.js +2 -2
- package/src/agents/jules/tools/url-policy.js +100 -100
- package/src/agents/persona-visuals.js +64 -61
- package/src/agents/shared-tools/dispatch-core.js +320 -315
- package/src/agents/shared-tools/file-edit.js +180 -180
- package/src/agents/shared-tools/file-read.js +100 -100
- package/src/agents/shared-tools/glob.js +168 -168
- package/src/agents/shared-tools/grep.js +228 -228
- package/src/agents/shared-tools/index.js +46 -46
- package/src/agents/shared-tools/path-guards.js +161 -161
- package/src/agents/shared-tools/shell.js +383 -383
- package/src/ai/aidenid.js +1021 -1009
- package/src/ai/client.js +553 -553
- package/src/ai/domain-target-store.js +268 -268
- package/src/ai/identity-store.js +270 -270
- package/src/ai/proxy.js +137 -137
- package/src/ai/site-store.js +145 -145
- package/src/audit/agents/architecture.js +180 -180
- package/src/audit/agents/compliance.js +179 -179
- package/src/audit/agents/documentation.js +165 -165
- package/src/audit/agents/performance.js +145 -145
- package/src/audit/agents/security.js +215 -215
- package/src/audit/agents/testing.js +172 -172
- package/src/audit/orchestrator.js +557 -557
- package/src/audit/package.js +204 -204
- package/src/audit/registry.js +284 -284
- package/src/audit/replay.js +103 -103
- package/src/auth/gate.js +400 -371
- package/src/auth/http.js +681 -611
- package/src/auth/service.js +1106 -1106
- package/src/auth/session-store.js +813 -813
- package/src/cli.js +257 -252
- package/src/commands/ai/identity-lifecycle.js +1338 -1338
- package/src/commands/ai/provision-governance.js +1272 -1272
- package/src/commands/ai/shared.js +147 -147
- package/src/commands/ai.js +11 -11
- package/src/commands/apply.js +12 -12
- package/src/commands/audit.js +1171 -1166
- package/src/commands/auth.js +419 -419
- package/src/commands/chat.js +191 -191
- package/src/commands/config.js +184 -184
- package/src/commands/cost.js +311 -311
- package/src/commands/daemon/core.js +850 -850
- package/src/commands/daemon/extended.js +1048 -1048
- package/src/commands/daemon/shared.js +213 -213
- package/src/commands/daemon.js +11 -11
- package/src/commands/guide.js +174 -174
- package/src/commands/ingest.js +58 -58
- package/src/commands/init.js +55 -55
- package/src/commands/legacy-args.js +10 -10
- package/src/commands/mcp.js +461 -461
- package/src/commands/omargate.js +29 -29
- package/src/commands/persona.js +20 -20
- package/src/commands/plugin.js +260 -260
- package/src/commands/policy.js +132 -132
- package/src/commands/prompt.js +238 -238
- package/src/commands/review.js +704 -704
- package/src/commands/scan.js +872 -872
- package/src/commands/session.js +590 -0
- package/src/commands/spec.js +778 -716
- package/src/commands/swarm.js +651 -651
- package/src/commands/telemetry.js +202 -202
- package/src/commands/watch.js +511 -511
- package/src/config/agent-dictionary.js +182 -182
- package/src/config/io.js +56 -56
- package/src/config/paths.js +18 -18
- package/src/config/schema.js +55 -55
- package/src/config/service.js +184 -184
- package/src/cost/budget.js +235 -235
- package/src/cost/history.js +188 -188
- package/src/cost/tracker.js +171 -171
- package/src/daemon/artifact-lineage.js +534 -534
- package/src/daemon/assignment-ledger.js +966 -770
- package/src/daemon/ast-parser-layer.js +258 -258
- package/src/daemon/budget-governor.js +633 -633
- package/src/daemon/callgraph-overlay.js +646 -646
- package/src/daemon/error-worker.js +1209 -626
- package/src/daemon/fix-cycle.js +384 -377
- package/src/daemon/hybrid-mapper.js +929 -929
- package/src/daemon/ingest-refresh.js +10 -9
- package/src/daemon/jira-lifecycle.js +767 -632
- package/src/daemon/operator-control.js +657 -657
- package/src/daemon/pulse.js +327 -327
- package/src/daemon/reliability-lane.js +471 -471
- package/src/daemon/scope-engine.js +1068 -0
- package/src/daemon/watchdog.js +971 -971
- package/src/events/schema.js +190 -0
- package/src/guide/generator.js +316 -316
- package/src/ingest/engine.js +918 -918
- package/src/interactive/index.js +97 -97
- package/src/legacy-cli.js +3161 -2994
- package/src/mcp/registry.js +695 -695
- package/src/memory/blackboard.js +301 -301
- package/src/memory/retrieval.js +581 -581
- package/src/plugin/manifest.js +553 -553
- package/src/policy/packs.js +144 -144
- package/src/prompt/generator.js +136 -118
- package/src/review/ai-review.js +679 -679
- package/src/review/local-review.js +1351 -1305
- package/src/review/omargate-interactive.js +68 -68
- package/src/review/omargate-orchestrator.js +404 -300
- package/src/review/persona-prompts.js +296 -296
- package/src/review/replay.js +235 -235
- package/src/review/report.js +664 -664
- package/src/review/scan-modes.js +48 -42
- package/src/review/spec-binding.js +487 -487
- package/src/scaffold/generator.js +67 -67
- package/src/scaffold/templates.js +150 -150
- package/src/scan/generator.js +418 -418
- package/src/scan/gh-secrets.js +107 -107
- package/src/session/agent-registry.js +352 -0
- package/src/session/daemon.js +801 -0
- package/src/session/paths.js +33 -0
- package/src/session/runtime-bridge.js +739 -0
- package/src/session/store.js +388 -0
- package/src/session/stream.js +325 -0
- package/src/spec/generator.js +619 -519
- package/src/spec/regenerate.js +237 -237
- package/src/spec/templates.js +91 -91
- package/src/swarm/dashboard.js +247 -247
- package/src/swarm/factory.js +363 -363
- package/src/swarm/pentest.js +934 -934
- package/src/swarm/registry.js +419 -419
- package/src/swarm/report.js +158 -158
- package/src/swarm/runtime.js +576 -576
- package/src/swarm/scenario-dsl.js +272 -272
- package/src/telemetry/ledger.js +302 -302
- package/src/telemetry/session-tracker.js +234 -234
- package/src/telemetry/sync.js +203 -203
- package/src/ui/command-hints.js +13 -13
- package/src/ui/markdown.js +220 -220
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
listAssignments,
|
|
8
|
+
releaseLease,
|
|
9
|
+
} from "../daemon/assignment-ledger.js";
|
|
10
|
+
import { stopScopeEngine } from "../daemon/scope-engine.js";
|
|
11
|
+
import { createAgentEvent } from "../events/schema.js";
|
|
12
|
+
import {
|
|
13
|
+
detectStaleAgents,
|
|
14
|
+
listAgents,
|
|
15
|
+
registerAgent,
|
|
16
|
+
unregisterAgent,
|
|
17
|
+
} from "../session/agent-registry.js";
|
|
18
|
+
import { stopSenti } from "../session/daemon.js";
|
|
19
|
+
import { listRuntimeRuns } from "../session/runtime-bridge.js";
|
|
20
|
+
import {
|
|
21
|
+
createSession,
|
|
22
|
+
DEFAULT_TTL_SECONDS,
|
|
23
|
+
getSession,
|
|
24
|
+
listActiveSessions,
|
|
25
|
+
} from "../session/store.js";
|
|
26
|
+
import { appendToStream, readStream, tailStream } from "../session/stream.js";
|
|
27
|
+
|
|
28
|
+
function shouldEmitJson(options, command) {
|
|
29
|
+
const local = Boolean(options && options.json);
|
|
30
|
+
const globalFromCommand =
|
|
31
|
+
command && command.optsWithGlobals ? Boolean(command.optsWithGlobals().json) : false;
|
|
32
|
+
return local || globalFromCommand;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeString(value) {
|
|
36
|
+
return String(value || "").trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parsePositiveInteger(rawValue, field, fallbackValue) {
|
|
40
|
+
if (rawValue === undefined || rawValue === null || String(rawValue).trim() === "") {
|
|
41
|
+
return fallbackValue;
|
|
42
|
+
}
|
|
43
|
+
const normalized = Number(rawValue);
|
|
44
|
+
if (!Number.isFinite(normalized) || normalized <= 0) {
|
|
45
|
+
throw new Error(`${field} must be a positive integer.`);
|
|
46
|
+
}
|
|
47
|
+
return Math.floor(normalized);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeAgentId(value, fallbackValue = "cli-user") {
|
|
51
|
+
const normalized = normalizeString(value)
|
|
52
|
+
.toLowerCase()
|
|
53
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
54
|
+
.replace(/^-+|-+$/g, "");
|
|
55
|
+
return normalized || fallbackValue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveSessionIdOption(options = {}) {
|
|
59
|
+
const sessionId = normalizeString(options.session || options.id);
|
|
60
|
+
if (!sessionId) {
|
|
61
|
+
throw new Error("session id is required (use --session <id>).");
|
|
62
|
+
}
|
|
63
|
+
return sessionId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function formatEventLine(event = {}) {
|
|
67
|
+
const ts = normalizeString(event.ts || event.timestamp);
|
|
68
|
+
const type = normalizeString(event.event || event.type) || "event";
|
|
69
|
+
const agentId = normalizeString(event.agent?.id || event.agentId || "unknown");
|
|
70
|
+
const payload = event.payload && typeof event.payload === "object" ? event.payload : {};
|
|
71
|
+
const message = normalizeString(payload.message || payload.response || payload.alert || payload.reason || "");
|
|
72
|
+
if (message) {
|
|
73
|
+
return `${ts} ${agentId} ${type}: ${message}`;
|
|
74
|
+
}
|
|
75
|
+
return `${ts} ${agentId} ${type}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function revokeAgentLeases(sessionId, agentId, { targetPath, reason } = {}) {
|
|
79
|
+
const active = await listAssignments({
|
|
80
|
+
targetPath,
|
|
81
|
+
sessionId,
|
|
82
|
+
agentIdentity: agentId,
|
|
83
|
+
statuses: ["CLAIMED", "IN_PROGRESS"],
|
|
84
|
+
includeExpired: true,
|
|
85
|
+
limit: 500,
|
|
86
|
+
});
|
|
87
|
+
let releasedCount = 0;
|
|
88
|
+
for (const assignment of active.assignments) {
|
|
89
|
+
await releaseLease({
|
|
90
|
+
targetPath,
|
|
91
|
+
sessionId,
|
|
92
|
+
workItemId: assignment.workItemId,
|
|
93
|
+
agentIdentity: agentId,
|
|
94
|
+
status: "QUEUED",
|
|
95
|
+
reason,
|
|
96
|
+
});
|
|
97
|
+
releasedCount += 1;
|
|
98
|
+
}
|
|
99
|
+
return releasedCount;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function emitAgentKilledEvent(sessionId, agentId, {
|
|
103
|
+
targetPath,
|
|
104
|
+
reason,
|
|
105
|
+
leaseRevocations = 0,
|
|
106
|
+
} = {}) {
|
|
107
|
+
const event = createAgentEvent({
|
|
108
|
+
event: "agent_killed",
|
|
109
|
+
agentId,
|
|
110
|
+
sessionId,
|
|
111
|
+
payload: {
|
|
112
|
+
target: agentId,
|
|
113
|
+
reason: normalizeString(reason) || "manual_stop",
|
|
114
|
+
leaseRevocations: Number(leaseRevocations || 0),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
await appendToStream(sessionId, event, { targetPath });
|
|
118
|
+
return event;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function registerSessionCommand(program) {
|
|
122
|
+
const session = program
|
|
123
|
+
.command("session")
|
|
124
|
+
.description("Multi-agent ephemeral coordination sessions");
|
|
125
|
+
|
|
126
|
+
session
|
|
127
|
+
.command("start")
|
|
128
|
+
.description("Create a new persistent session with metadata + NDJSON stream")
|
|
129
|
+
.option("--path <path>", "Workspace path for the session", ".")
|
|
130
|
+
.option(
|
|
131
|
+
"--ttl-seconds <seconds>",
|
|
132
|
+
`Session time-to-live in seconds (default ${DEFAULT_TTL_SECONDS})`,
|
|
133
|
+
String(DEFAULT_TTL_SECONDS)
|
|
134
|
+
)
|
|
135
|
+
.option("--json", "Emit machine-readable output")
|
|
136
|
+
.action(async (options, command) => {
|
|
137
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
138
|
+
const ttlSeconds = parsePositiveInteger(options.ttlSeconds, "ttl-seconds", DEFAULT_TTL_SECONDS);
|
|
139
|
+
const startedAt = Date.now();
|
|
140
|
+
const created = await createSession({
|
|
141
|
+
targetPath,
|
|
142
|
+
ttlSeconds,
|
|
143
|
+
});
|
|
144
|
+
const durationMs = Date.now() - startedAt;
|
|
145
|
+
|
|
146
|
+
const payload = {
|
|
147
|
+
command: "session start",
|
|
148
|
+
targetPath,
|
|
149
|
+
durationMs,
|
|
150
|
+
sessionId: created.sessionId,
|
|
151
|
+
sessionDir: created.sessionDir,
|
|
152
|
+
metadataPath: created.metadataPath,
|
|
153
|
+
streamPath: created.streamPath,
|
|
154
|
+
createdAt: created.createdAt,
|
|
155
|
+
expiresAt: created.expiresAt,
|
|
156
|
+
elapsedTimer: created.elapsedTimer,
|
|
157
|
+
renewalCount: created.renewalCount,
|
|
158
|
+
status: created.status,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (shouldEmitJson(options, command)) {
|
|
162
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(pc.bold("Session created"));
|
|
167
|
+
console.log(pc.gray(`Session: ${created.sessionId}`));
|
|
168
|
+
console.log(pc.gray(`Stream: ${created.streamPath}`));
|
|
169
|
+
console.log(pc.gray(`Created in ${durationMs}ms`));
|
|
170
|
+
console.log(
|
|
171
|
+
`status=${created.status} created_at=${created.createdAt} expires_at=${created.expiresAt} ttl_seconds=${ttlSeconds}`
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
session
|
|
176
|
+
.command("join <sessionId>")
|
|
177
|
+
.description("Join an active session")
|
|
178
|
+
.option("--name <name>", "Agent display name")
|
|
179
|
+
.option("--role <role>", "Agent role: coder, reviewer, tester, observer", "coder")
|
|
180
|
+
.option("--model <model>", "Agent model hint", "cli")
|
|
181
|
+
.option("--path <path>", "Workspace path for the session", ".")
|
|
182
|
+
.option("--json", "Emit machine-readable output")
|
|
183
|
+
.action(async (sessionId, options, command) => {
|
|
184
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
185
|
+
if (!normalizedSessionId) {
|
|
186
|
+
throw new Error("session id is required.");
|
|
187
|
+
}
|
|
188
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
189
|
+
const joined = await registerAgent(normalizedSessionId, {
|
|
190
|
+
targetPath,
|
|
191
|
+
agentId: normalizeAgentId(options.name, "cli-user"),
|
|
192
|
+
model: normalizeString(options.model) || "cli",
|
|
193
|
+
role: options.role || "coder",
|
|
194
|
+
});
|
|
195
|
+
const payload = {
|
|
196
|
+
command: "session join",
|
|
197
|
+
targetPath,
|
|
198
|
+
sessionId: normalizedSessionId,
|
|
199
|
+
agentId: joined.agentId,
|
|
200
|
+
role: joined.role,
|
|
201
|
+
model: joined.model,
|
|
202
|
+
status: joined.status,
|
|
203
|
+
joinedAt: joined.joinedAt,
|
|
204
|
+
};
|
|
205
|
+
if (shouldEmitJson(options, command)) {
|
|
206
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
console.log(pc.bold(`Joined session ${normalizedSessionId}`));
|
|
210
|
+
console.log(pc.gray(`agent=${joined.agentId} role=${joined.role} model=${joined.model}`));
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
session
|
|
214
|
+
.command("say <sessionId> <message>")
|
|
215
|
+
.description("Send a message to the session")
|
|
216
|
+
.option("--agent <id>", "Agent id to emit from", "cli-user")
|
|
217
|
+
.option("--path <path>", "Workspace path for the session", ".")
|
|
218
|
+
.option("--json", "Emit machine-readable output")
|
|
219
|
+
.action(async (sessionId, message, options, command) => {
|
|
220
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
221
|
+
if (!normalizedSessionId) {
|
|
222
|
+
throw new Error("session id is required.");
|
|
223
|
+
}
|
|
224
|
+
const normalizedMessage = normalizeString(message);
|
|
225
|
+
if (!normalizedMessage) {
|
|
226
|
+
throw new Error("message is required.");
|
|
227
|
+
}
|
|
228
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
229
|
+
const agentId = normalizeAgentId(options.agent, "cli-user");
|
|
230
|
+
const event = createAgentEvent({
|
|
231
|
+
event: "session_message",
|
|
232
|
+
agentId,
|
|
233
|
+
sessionId: normalizedSessionId,
|
|
234
|
+
payload: {
|
|
235
|
+
message: normalizedMessage,
|
|
236
|
+
channel: "session",
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
const persisted = await appendToStream(normalizedSessionId, event, {
|
|
240
|
+
targetPath,
|
|
241
|
+
});
|
|
242
|
+
const payload = {
|
|
243
|
+
command: "session say",
|
|
244
|
+
targetPath,
|
|
245
|
+
sessionId: normalizedSessionId,
|
|
246
|
+
agentId,
|
|
247
|
+
event: persisted,
|
|
248
|
+
};
|
|
249
|
+
if (shouldEmitJson(options, command)) {
|
|
250
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
console.log(formatEventLine(persisted));
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
session
|
|
257
|
+
.command("read <sessionId>")
|
|
258
|
+
.description("Read recent session messages")
|
|
259
|
+
.option("--tail <n>", "Number of recent events", "20")
|
|
260
|
+
.option("--follow", "Continuously follow new events")
|
|
261
|
+
.option("--path <path>", "Workspace path for the session", ".")
|
|
262
|
+
.option("--json", "Emit machine-readable output")
|
|
263
|
+
.action(async (sessionId, options, command) => {
|
|
264
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
265
|
+
if (!normalizedSessionId) {
|
|
266
|
+
throw new Error("session id is required.");
|
|
267
|
+
}
|
|
268
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
269
|
+
const tail = parsePositiveInteger(options.tail, "tail", 20);
|
|
270
|
+
const emitJson = shouldEmitJson(options, command);
|
|
271
|
+
|
|
272
|
+
if (!options.follow) {
|
|
273
|
+
const events = await readStream(normalizedSessionId, {
|
|
274
|
+
targetPath,
|
|
275
|
+
tail,
|
|
276
|
+
});
|
|
277
|
+
const payload = {
|
|
278
|
+
command: "session read",
|
|
279
|
+
targetPath,
|
|
280
|
+
sessionId: normalizedSessionId,
|
|
281
|
+
tail,
|
|
282
|
+
count: events.length,
|
|
283
|
+
events,
|
|
284
|
+
};
|
|
285
|
+
if (emitJson) {
|
|
286
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
for (const event of events) {
|
|
290
|
+
console.log(formatEventLine(event));
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (!emitJson) {
|
|
296
|
+
console.log(pc.gray(`Following session ${normalizedSessionId}... Press Ctrl+C to stop.`));
|
|
297
|
+
}
|
|
298
|
+
for await (const event of tailStream(normalizedSessionId, {
|
|
299
|
+
targetPath,
|
|
300
|
+
replayTail: tail,
|
|
301
|
+
})) {
|
|
302
|
+
if (emitJson) {
|
|
303
|
+
console.log(JSON.stringify(event));
|
|
304
|
+
} else {
|
|
305
|
+
console.log(formatEventLine(event));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
session
|
|
311
|
+
.command("status <sessionId>")
|
|
312
|
+
.description("Show session status, agents, and health")
|
|
313
|
+
.option("--path <path>", "Workspace path for the session", ".")
|
|
314
|
+
.option("--json", "Emit machine-readable output")
|
|
315
|
+
.action(async (sessionId, options, command) => {
|
|
316
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
317
|
+
if (!normalizedSessionId) {
|
|
318
|
+
throw new Error("session id is required.");
|
|
319
|
+
}
|
|
320
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
321
|
+
const sessionPayload = await getSession(normalizedSessionId, {
|
|
322
|
+
targetPath,
|
|
323
|
+
});
|
|
324
|
+
if (!sessionPayload) {
|
|
325
|
+
throw new Error(`Session '${normalizedSessionId}' was not found.`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const [agents, runtimeRuns, leases, recentEvents] = await Promise.all([
|
|
329
|
+
listAgents(normalizedSessionId, {
|
|
330
|
+
targetPath,
|
|
331
|
+
includeInactive: false,
|
|
332
|
+
}),
|
|
333
|
+
Promise.resolve(
|
|
334
|
+
listRuntimeRuns({
|
|
335
|
+
sessionId: normalizedSessionId,
|
|
336
|
+
targetPath,
|
|
337
|
+
includeStopped: false,
|
|
338
|
+
})
|
|
339
|
+
),
|
|
340
|
+
listAssignments({
|
|
341
|
+
targetPath,
|
|
342
|
+
sessionId: normalizedSessionId,
|
|
343
|
+
statuses: ["CLAIMED", "IN_PROGRESS"],
|
|
344
|
+
includeExpired: true,
|
|
345
|
+
limit: 100,
|
|
346
|
+
}),
|
|
347
|
+
readStream(normalizedSessionId, {
|
|
348
|
+
targetPath,
|
|
349
|
+
tail: 10,
|
|
350
|
+
}),
|
|
351
|
+
]);
|
|
352
|
+
|
|
353
|
+
const staleAgents = detectStaleAgents(agents, {});
|
|
354
|
+
const payload = {
|
|
355
|
+
command: "session status",
|
|
356
|
+
targetPath,
|
|
357
|
+
sessionId: normalizedSessionId,
|
|
358
|
+
session: sessionPayload,
|
|
359
|
+
activeAgents: agents,
|
|
360
|
+
staleAgents,
|
|
361
|
+
runtimeRuns,
|
|
362
|
+
activeLeases: leases.assignments,
|
|
363
|
+
recentEvents,
|
|
364
|
+
};
|
|
365
|
+
if (shouldEmitJson(options, command)) {
|
|
366
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
console.log(pc.bold(`Session ${normalizedSessionId}`));
|
|
371
|
+
console.log(
|
|
372
|
+
pc.gray(
|
|
373
|
+
`status=${sessionPayload.status} agents=${agents.length} stale=${staleAgents.length} runs=${runtimeRuns.length} leases=${leases.assignments.length}`
|
|
374
|
+
)
|
|
375
|
+
);
|
|
376
|
+
for (const event of recentEvents) {
|
|
377
|
+
console.log(formatEventLine(event));
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
session
|
|
382
|
+
.command("leave <sessionId>")
|
|
383
|
+
.description("Leave a session")
|
|
384
|
+
.option("--agent <id>", "Agent id to unregister", "cli-user")
|
|
385
|
+
.option("--reason <reason>", "Leave reason", "manual")
|
|
386
|
+
.option("--path <path>", "Workspace path for the session", ".")
|
|
387
|
+
.option("--json", "Emit machine-readable output")
|
|
388
|
+
.action(async (sessionId, options, command) => {
|
|
389
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
390
|
+
if (!normalizedSessionId) {
|
|
391
|
+
throw new Error("session id is required.");
|
|
392
|
+
}
|
|
393
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
394
|
+
const agentId = normalizeAgentId(options.agent, "cli-user");
|
|
395
|
+
const left = await unregisterAgent(normalizedSessionId, agentId, {
|
|
396
|
+
reason: options.reason || "manual",
|
|
397
|
+
targetPath,
|
|
398
|
+
});
|
|
399
|
+
const payload = {
|
|
400
|
+
command: "session leave",
|
|
401
|
+
targetPath,
|
|
402
|
+
sessionId: normalizedSessionId,
|
|
403
|
+
agentId: left.agentId,
|
|
404
|
+
reason: left.leaveReason,
|
|
405
|
+
leftAt: left.leftAt,
|
|
406
|
+
};
|
|
407
|
+
if (shouldEmitJson(options, command)) {
|
|
408
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
console.log(pc.bold(`Left session ${normalizedSessionId}`));
|
|
412
|
+
console.log(pc.gray(`agent=${left.agentId} reason=${left.leaveReason}`));
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
session
|
|
416
|
+
.command("list")
|
|
417
|
+
.description("List active sessions")
|
|
418
|
+
.option("--path <path>", "Workspace path for sessions", ".")
|
|
419
|
+
.option("--json", "Emit machine-readable output")
|
|
420
|
+
.action(async (options, command) => {
|
|
421
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
422
|
+
const sessions = await listActiveSessions({
|
|
423
|
+
targetPath,
|
|
424
|
+
});
|
|
425
|
+
const payload = {
|
|
426
|
+
command: "session list",
|
|
427
|
+
targetPath,
|
|
428
|
+
count: sessions.length,
|
|
429
|
+
sessions,
|
|
430
|
+
};
|
|
431
|
+
if (shouldEmitJson(options, command)) {
|
|
432
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (sessions.length === 0) {
|
|
436
|
+
console.log(pc.yellow("No active sessions."));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
for (const item of sessions) {
|
|
440
|
+
console.log(
|
|
441
|
+
`${item.sessionId} status=${item.status} created_at=${item.createdAt} expires_at=${item.expiresAt}`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
session
|
|
447
|
+
.command("kill")
|
|
448
|
+
.description("Kill a single agent or all agents in a session")
|
|
449
|
+
.option("--agent <id>", "Specific agent id to stop")
|
|
450
|
+
.option("--all", "Kill every known agent in the session")
|
|
451
|
+
.option("--session <id>", "Session id")
|
|
452
|
+
.option("--id <sessionId>", "Deprecated alias for --session")
|
|
453
|
+
.option("--path <path>", "Workspace path for the session", ".")
|
|
454
|
+
.option("--reason <reason>", "Kill reason code", "manual_stop")
|
|
455
|
+
.option("--json", "Emit machine-readable output")
|
|
456
|
+
.action(async (options, command) => {
|
|
457
|
+
const sessionId = resolveSessionIdOption(options);
|
|
458
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
459
|
+
const reason = normalizeString(options.reason) || "manual_stop";
|
|
460
|
+
const requestedAgent = normalizeString(options.agent).toLowerCase();
|
|
461
|
+
|
|
462
|
+
if (!options.all && !requestedAgent) {
|
|
463
|
+
throw new Error("session kill requires --agent <id> or --all.");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const startedAt = Date.now();
|
|
467
|
+
const discoveredAgents = await listAgents(sessionId, {
|
|
468
|
+
targetPath,
|
|
469
|
+
includeInactive: false,
|
|
470
|
+
});
|
|
471
|
+
const agentsToKill = new Set();
|
|
472
|
+
if (options.all) {
|
|
473
|
+
agentsToKill.add("senti");
|
|
474
|
+
agentsToKill.add("scope-engine");
|
|
475
|
+
for (const agent of discoveredAgents) {
|
|
476
|
+
const agentId = normalizeString(agent.agentId).toLowerCase();
|
|
477
|
+
if (agentId) {
|
|
478
|
+
agentsToKill.add(agentId);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
} else {
|
|
482
|
+
agentsToKill.add(requestedAgent);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const results = [];
|
|
486
|
+
let runtimeStops = 0;
|
|
487
|
+
let scopeStops = 0;
|
|
488
|
+
let leaseRevocations = 0;
|
|
489
|
+
let anyStopped = false;
|
|
490
|
+
|
|
491
|
+
for (const agentId of agentsToKill) {
|
|
492
|
+
let stopped = false;
|
|
493
|
+
let stopDetails = {};
|
|
494
|
+
if (agentId === "senti") {
|
|
495
|
+
const stopResult = await stopSenti(sessionId, {
|
|
496
|
+
targetPath,
|
|
497
|
+
reason,
|
|
498
|
+
});
|
|
499
|
+
runtimeStops += Number(stopResult?.runtimeStopSummary?.stoppedCount || 0);
|
|
500
|
+
stopped = Boolean(stopResult?.stopped);
|
|
501
|
+
stopDetails = {
|
|
502
|
+
runtimeStops: Number(stopResult?.runtimeStopSummary?.stoppedCount || 0),
|
|
503
|
+
scopeStops: 0,
|
|
504
|
+
};
|
|
505
|
+
} else if (agentId === "scope-engine") {
|
|
506
|
+
const stopResult = await stopScopeEngine({
|
|
507
|
+
targetPath,
|
|
508
|
+
sessionId,
|
|
509
|
+
reason,
|
|
510
|
+
});
|
|
511
|
+
scopeStops += Number(stopResult?.count || 0);
|
|
512
|
+
stopped = Boolean(stopResult?.stopped);
|
|
513
|
+
stopDetails = {
|
|
514
|
+
runtimeStops: 0,
|
|
515
|
+
scopeStops: Number(stopResult?.count || 0),
|
|
516
|
+
};
|
|
517
|
+
} else {
|
|
518
|
+
try {
|
|
519
|
+
await unregisterAgent(sessionId, agentId, {
|
|
520
|
+
reason: "killed",
|
|
521
|
+
targetPath,
|
|
522
|
+
});
|
|
523
|
+
stopped = true;
|
|
524
|
+
} catch {
|
|
525
|
+
stopped = false;
|
|
526
|
+
}
|
|
527
|
+
if (stopped) {
|
|
528
|
+
await emitAgentKilledEvent(sessionId, agentId, {
|
|
529
|
+
targetPath,
|
|
530
|
+
reason,
|
|
531
|
+
leaseRevocations: 0,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
stopDetails = {
|
|
535
|
+
runtimeStops: 0,
|
|
536
|
+
scopeStops: 0,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const releasedCount = await revokeAgentLeases(sessionId, agentId, {
|
|
541
|
+
targetPath,
|
|
542
|
+
reason: `agent_killed:${reason}`,
|
|
543
|
+
});
|
|
544
|
+
leaseRevocations += releasedCount;
|
|
545
|
+
anyStopped = anyStopped || stopped;
|
|
546
|
+
|
|
547
|
+
results.push({
|
|
548
|
+
agentId,
|
|
549
|
+
stopped,
|
|
550
|
+
runtimeStops: stopDetails.runtimeStops,
|
|
551
|
+
scopeStops: stopDetails.scopeStops,
|
|
552
|
+
leaseRevocations: releasedCount,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const durationMs = Date.now() - startedAt;
|
|
557
|
+
const primaryAgentId = !options.all ? requestedAgent : null;
|
|
558
|
+
const payload = {
|
|
559
|
+
command: "session kill",
|
|
560
|
+
targetPath,
|
|
561
|
+
durationMs,
|
|
562
|
+
sessionId,
|
|
563
|
+
agentId: primaryAgentId,
|
|
564
|
+
all: Boolean(options.all),
|
|
565
|
+
reason,
|
|
566
|
+
stopped: anyStopped,
|
|
567
|
+
runtimeStops,
|
|
568
|
+
scopeStops,
|
|
569
|
+
leaseRevocations,
|
|
570
|
+
results,
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
if (shouldEmitJson(options, command)) {
|
|
574
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (payload.stopped) {
|
|
579
|
+
console.log(pc.bold("Kill complete"));
|
|
580
|
+
} else {
|
|
581
|
+
console.log(pc.yellow(`No active target found in session ${sessionId}.`));
|
|
582
|
+
}
|
|
583
|
+
console.log(
|
|
584
|
+
pc.gray(
|
|
585
|
+
`session=${sessionId} runtime_stops=${runtimeStops} scope_stops=${scopeStops} lease_revocations=${leaseRevocations}`
|
|
586
|
+
)
|
|
587
|
+
);
|
|
588
|
+
console.log(`stopped=${payload.stopped} reason=${reason} duration_ms=${durationMs}`);
|
|
589
|
+
});
|
|
590
|
+
}
|