sentinelayer-cli 0.22.0 → 0.24.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 +22 -0
- package/package.json +1 -1
- package/src/audit/orchestrator.js +17 -0
- package/src/commands/audit.js +58 -10
- package/src/commands/guide.js +52 -0
- package/src/commands/session.js +310 -17
- package/src/guide/enrich.js +173 -0
- package/src/guide/generator.js +167 -10
- package/src/legacy-cli.js +88 -1
- package/src/session/audit-reporter.js +164 -0
- package/src/session/daemon-spawn.js +192 -0
- package/src/session/first-message.js +99 -0
- package/src/session/listeners.js +126 -0
- package/src/session/project-bootstrap.js +115 -0
- package/src/session/wake/listen-wake.js +144 -0
package/README.md
CHANGED
|
@@ -101,6 +101,28 @@ Inputs for non-interactive mode:
|
|
|
101
101
|
|
|
102
102
|
## Multi-Agent Session Workflow
|
|
103
103
|
|
|
104
|
+
Create a managed session (the golden path — one command):
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
sl session start --title "my room" --force-new
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`session start` resumes this workspace's most recent active session when it was
|
|
111
|
+
active within the last hour (`--force-new` always mints a fresh one) and spawns
|
|
112
|
+
the **detached Senti daemon** that manages the room — agent greetings, mention
|
|
113
|
+
routing, recaps, durable checkpoints — surviving your terminal. `--no-daemon`
|
|
114
|
+
opts out; `sl session daemon <id>` runs the manager in the foreground. One
|
|
115
|
+
daemon per session is enforced via `senti-daemon.json` in the session directory
|
|
116
|
+
(logs in `senti-daemon.log` next to it), and the daemon exits on its own when
|
|
117
|
+
the session expires.
|
|
118
|
+
|
|
119
|
+
Then point your agents at it:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
sl session join <session-id> --agent <agent-name>
|
|
123
|
+
sl session say <session-id> "status: starting on auth middleware" --agent <agent-name>
|
|
124
|
+
```
|
|
125
|
+
|
|
104
126
|
Sentinelayer includes a deterministic session coordination surface for multi-agent coding loops:
|
|
105
127
|
|
|
106
128
|
- session event stream and replay (`start`, `join`, `say`, `read`, `status`, `leave`, `list`, `kill`)
|
package/package.json
CHANGED
|
@@ -753,6 +753,23 @@ export async function runAuditOrchestrator({
|
|
|
753
753
|
});
|
|
754
754
|
const agentPath = path.join(agentsDirectory, `${agent.id}.json`);
|
|
755
755
|
await fsp.writeFile(agentPath, `${JSON.stringify(result, null, 2)}\n`, "utf-8");
|
|
756
|
+
emitAuditLifecycleEvent(
|
|
757
|
+
onEvent,
|
|
758
|
+
runId,
|
|
759
|
+
"agent_complete",
|
|
760
|
+
{
|
|
761
|
+
phase: "dispatch",
|
|
762
|
+
agentId: agent.id,
|
|
763
|
+
persona: agent.persona,
|
|
764
|
+
domain: agent.domain,
|
|
765
|
+
status: agentStatus,
|
|
766
|
+
findingCount: findings.length,
|
|
767
|
+
summary,
|
|
768
|
+
confidence,
|
|
769
|
+
durationMs: result.durationMs,
|
|
770
|
+
},
|
|
771
|
+
`${agent.id} persona complete: ${findings.length} finding(s).`
|
|
772
|
+
);
|
|
756
773
|
return {
|
|
757
774
|
...result,
|
|
758
775
|
artifactPath: agentPath,
|
package/src/commands/audit.js
CHANGED
|
@@ -9,6 +9,7 @@ import { writeAuditComparisonArtifact } from "../audit/replay.js";
|
|
|
9
9
|
import { loadAuditRegistry, selectAuditAgents } from "../audit/registry.js";
|
|
10
10
|
import { resolveOutputRoot } from "../config/service.js";
|
|
11
11
|
import { createAgentEvent } from "../events/schema.js";
|
|
12
|
+
import { createAuditSessionReporter, resolveAuditSessionId } from "../session/audit-reporter.js";
|
|
12
13
|
import { buildLegacyArgs } from "./legacy-args.js";
|
|
13
14
|
|
|
14
15
|
function shouldEmitJson(options, command) {
|
|
@@ -89,6 +90,11 @@ export function registerAuditCommand(program, invokeLegacy) {
|
|
|
89
90
|
.option("--no-seed-from-deterministic", "Run personas without deterministic baseline or specialist seed findings")
|
|
90
91
|
.option("--reuse-omargate <runId>", "Reuse deterministic findings from an OmarGate run id or latest")
|
|
91
92
|
.option("--stream", "Emit NDJSON agent events to stdout")
|
|
93
|
+
.option(
|
|
94
|
+
"--session <id>",
|
|
95
|
+
"Senti session id to relay audit progress into (defaults to the workspace's most recent active session)"
|
|
96
|
+
)
|
|
97
|
+
.option("--no-session", "Disable senti session progress relay")
|
|
92
98
|
.option("--json", "Emit machine-readable output")
|
|
93
99
|
.action(async (targetPathArg, options, command) => {
|
|
94
100
|
const emitJson = shouldEmitJson(options, command);
|
|
@@ -105,18 +111,49 @@ export function registerAuditCommand(program, invokeLegacy) {
|
|
|
105
111
|
throw new Error("No agents selected for audit run.");
|
|
106
112
|
}
|
|
107
113
|
|
|
108
|
-
const
|
|
114
|
+
const auditSessionId = await resolveAuditSessionId({
|
|
115
|
+
targetPath,
|
|
116
|
+
explicitSessionId: typeof options.session === "string" ? options.session : "",
|
|
117
|
+
disabled: options.session === false,
|
|
118
|
+
});
|
|
119
|
+
const sessionReporter = createAuditSessionReporter({
|
|
120
|
+
sessionId: auditSessionId,
|
|
109
121
|
targetPath,
|
|
110
|
-
agents: selected.selected,
|
|
111
|
-
maxParallel: parseMaxParallel(options.maxParallel),
|
|
112
|
-
outputDir: options.outputDir,
|
|
113
|
-
dryRun: Boolean(options.dryRun),
|
|
114
|
-
refreshIngest: Boolean(options.refresh),
|
|
115
|
-
isolation: parseIsolationMode(options.isolation),
|
|
116
|
-
seedFromDeterministic: options.seedFromDeterministic !== false,
|
|
117
|
-
reuseOmarGate: options.reuseOmargate,
|
|
118
|
-
onEvent: buildAuditOrchestratorEventHandler(emitStream),
|
|
119
122
|
});
|
|
123
|
+
const streamHandler = buildAuditOrchestratorEventHandler(emitStream);
|
|
124
|
+
const onEvent =
|
|
125
|
+
streamHandler || sessionReporter
|
|
126
|
+
? (evt) => {
|
|
127
|
+
if (streamHandler) {
|
|
128
|
+
streamHandler(evt);
|
|
129
|
+
}
|
|
130
|
+
if (sessionReporter) {
|
|
131
|
+
sessionReporter.handleEvent(evt);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
: null;
|
|
135
|
+
|
|
136
|
+
let result;
|
|
137
|
+
try {
|
|
138
|
+
result = await runAuditOrchestrator({
|
|
139
|
+
targetPath,
|
|
140
|
+
agents: selected.selected,
|
|
141
|
+
maxParallel: parseMaxParallel(options.maxParallel),
|
|
142
|
+
outputDir: options.outputDir,
|
|
143
|
+
dryRun: Boolean(options.dryRun),
|
|
144
|
+
refreshIngest: Boolean(options.refresh),
|
|
145
|
+
isolation: parseIsolationMode(options.isolation),
|
|
146
|
+
seedFromDeterministic: options.seedFromDeterministic !== false,
|
|
147
|
+
reuseOmarGate: options.reuseOmargate,
|
|
148
|
+
onEvent,
|
|
149
|
+
});
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (sessionReporter) {
|
|
152
|
+
await sessionReporter.failed(error);
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
const sessionRelay = sessionReporter ? await sessionReporter.completed(result) : null;
|
|
120
157
|
|
|
121
158
|
const payload = {
|
|
122
159
|
command: "audit",
|
|
@@ -144,12 +181,23 @@ export function registerAuditCommand(program, invokeLegacy) {
|
|
|
144
181
|
ddPackageFindingsPath: result.ddPackage?.findingsIndexPath || "",
|
|
145
182
|
ddPackageSummaryPath: result.ddPackage?.executiveSummaryPath || "",
|
|
146
183
|
ingestRefresh: result.ingest?.refresh || null,
|
|
184
|
+
sessionId: auditSessionId || "",
|
|
185
|
+
sessionRelay: sessionRelay || null,
|
|
147
186
|
};
|
|
148
187
|
|
|
149
188
|
if (emitJson) {
|
|
150
189
|
console.log(JSON.stringify(payload, null, 2));
|
|
151
190
|
} else if (!emitStream) {
|
|
152
191
|
printAuditSummary(result);
|
|
192
|
+
if (auditSessionId) {
|
|
193
|
+
console.log(
|
|
194
|
+
pc.gray(
|
|
195
|
+
`Senti session: ${auditSessionId} (posted ${sessionRelay?.posted ?? 0} update(s)${
|
|
196
|
+
sessionRelay?.failed ? `, ${sessionRelay.failed} failed` : ""
|
|
197
|
+
})`
|
|
198
|
+
)
|
|
199
|
+
);
|
|
200
|
+
}
|
|
153
201
|
}
|
|
154
202
|
|
|
155
203
|
if (result.summary.blocking) {
|
package/src/commands/guide.js
CHANGED
|
@@ -4,6 +4,13 @@ import path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
import pc from "picocolors";
|
|
6
6
|
|
|
7
|
+
import {
|
|
8
|
+
createMultiProviderApiClient,
|
|
9
|
+
resolveApiKey,
|
|
10
|
+
resolveModel,
|
|
11
|
+
resolveProvider,
|
|
12
|
+
} from "../ai/client.js";
|
|
13
|
+
import { enrichGuideTickets } from "../guide/enrich.js";
|
|
7
14
|
import {
|
|
8
15
|
defaultGuideExportFileName,
|
|
9
16
|
generateBuildGuide,
|
|
@@ -12,6 +19,40 @@ import {
|
|
|
12
19
|
} from "../guide/generator.js";
|
|
13
20
|
import { renderTerminalMarkdown } from "../ui/markdown.js";
|
|
14
21
|
|
|
22
|
+
// Optionally split each phase into per-PR tickets with an LLM. Best-effort:
|
|
23
|
+
// any failure leaves the deterministic tickets untouched. Returns the number
|
|
24
|
+
// of phases enriched (0 when disabled or unavailable).
|
|
25
|
+
async function maybeEnrichGuide(guideDoc, options) {
|
|
26
|
+
if (!options || !options.enrich) return 0;
|
|
27
|
+
try {
|
|
28
|
+
const provider = resolveProvider({ provider: options.provider });
|
|
29
|
+
const model = resolveModel({ provider, model: options.model });
|
|
30
|
+
const apiKey = resolveApiKey({ provider, explicitApiKey: options.apiKey });
|
|
31
|
+
if (!apiKey) {
|
|
32
|
+
console.error(
|
|
33
|
+
pc.yellow(`! --enrich skipped: no API key for provider '${provider}'. Set the provider key or pass --api-key.`)
|
|
34
|
+
);
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
const limits = {};
|
|
38
|
+
if (options.maxPhases) limits.maxPhases = Number(options.maxPhases);
|
|
39
|
+
if (options.maxPrsPerPhase) limits.maxTicketsPerPhase = Number(options.maxPrsPerPhase);
|
|
40
|
+
const { tickets, enrichedPhases } = await enrichGuideTickets({
|
|
41
|
+
guide: guideDoc,
|
|
42
|
+
client: createMultiProviderApiClient(),
|
|
43
|
+
provider,
|
|
44
|
+
model,
|
|
45
|
+
apiKey,
|
|
46
|
+
limits,
|
|
47
|
+
});
|
|
48
|
+
if (enrichedPhases > 0) guideDoc.tickets = tickets;
|
|
49
|
+
return enrichedPhases;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(pc.yellow(`! --enrich failed, using deterministic tickets: ${error?.message || error}`));
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
15
56
|
function shouldEmitJson(options, command) {
|
|
16
57
|
const local = Boolean(options && options.json);
|
|
17
58
|
const globalFromCommand =
|
|
@@ -101,6 +142,12 @@ export function registerGuideCommand(program) {
|
|
|
101
142
|
.option("--path <path>", "Target workspace path", ".")
|
|
102
143
|
.option("--spec-file <path>", "Spec file path relative to --path")
|
|
103
144
|
.option("--output-file <path>", "Output export file path relative to --path")
|
|
145
|
+
.option("--enrich", "Split each phase into per-PR tickets with an LLM (opt-in, capped)")
|
|
146
|
+
.option("--provider <provider>", "LLM provider for --enrich (openai|anthropic|google)")
|
|
147
|
+
.option("--model <model>", "LLM model for --enrich")
|
|
148
|
+
.option("--api-key <key>", "Explicit API key for --enrich (else from env)")
|
|
149
|
+
.option("--max-phases <n>", "Cap how many phases --enrich expands")
|
|
150
|
+
.option("--max-prs-per-phase <n>", "Cap PRs per phase for --enrich")
|
|
104
151
|
.option("--json", "Emit machine-readable output")
|
|
105
152
|
.action(async (options, command) => {
|
|
106
153
|
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
@@ -116,6 +163,7 @@ export function registerGuideCommand(program) {
|
|
|
116
163
|
projectPath: targetPath,
|
|
117
164
|
specPath,
|
|
118
165
|
});
|
|
166
|
+
const enrichedPhases = await maybeEnrichGuide(guideDoc, options);
|
|
119
167
|
const exportBody = renderGuideExport({ format, guide: guideDoc });
|
|
120
168
|
|
|
121
169
|
await fsp.mkdir(path.dirname(outputPath), { recursive: true });
|
|
@@ -128,6 +176,7 @@ export function registerGuideCommand(program) {
|
|
|
128
176
|
specPath,
|
|
129
177
|
outputPath,
|
|
130
178
|
issueCount: guideDoc.tickets.length,
|
|
179
|
+
enrichedPhases,
|
|
131
180
|
};
|
|
132
181
|
|
|
133
182
|
if (shouldEmitJson(options, command)) {
|
|
@@ -139,6 +188,9 @@ export function registerGuideCommand(program) {
|
|
|
139
188
|
console.log(pc.gray(`Format: ${format}`));
|
|
140
189
|
console.log(pc.gray(`Output: ${outputPath}`));
|
|
141
190
|
console.log(pc.gray(`Issues: ${guideDoc.tickets.length}`));
|
|
191
|
+
if (enrichedPhases > 0) {
|
|
192
|
+
console.log(pc.gray(`LLM-enriched phases: ${enrichedPhases}`));
|
|
193
|
+
}
|
|
142
194
|
});
|
|
143
195
|
|
|
144
196
|
guide
|