sentinelayer-cli 0.30.13 → 0.30.14
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 +5 -0
- package/package.json +1 -1
- package/src/commands/mcp.js +34 -0
- package/src/commands/session.js +89 -19
- package/src/mcp/cli-command-tools.js +506 -0
- package/src/mcp/cli-registry.js +231 -0
- package/src/mcp/registry.js +260 -0
- package/src/mcp/session-stdio-server.js +436 -6
- package/src/review/persona-prompts.js +8 -0
- package/src/session/daemon-spawn.js +35 -15
- package/src/session/rotating-log.js +181 -0
- package/src/session/sync.js +7 -1
package/README.md
CHANGED
|
@@ -658,14 +658,19 @@ The CLI now includes deterministic MCP registry commands:
|
|
|
658
658
|
- `sl mcp schema write`
|
|
659
659
|
- `sl mcp registry init-aidenid`
|
|
660
660
|
- `sl mcp registry init-aidenid-adapter`
|
|
661
|
+
- `sl mcp registry init-session`
|
|
662
|
+
- `sl mcp registry init-cli`
|
|
661
663
|
- `sl mcp registry validate --file <path>`
|
|
662
664
|
- `sl mcp registry validate-aidenid-adapter --file <path> [--registry-file <path>]`
|
|
663
665
|
- `sl mcp server init --id <server-id> --registry-file <path>`
|
|
664
666
|
- `sl mcp server validate --file <path>`
|
|
667
|
+
- `sl mcp server run --path .`
|
|
665
668
|
- `sl mcp bridge init-vscode --server-id <server-id> --server-config <path>`
|
|
666
669
|
|
|
667
670
|
Use `init-aidenid` to scaffold an Anthropic-compatible tool schema wrapper for AIdenID provisioning APIs, then customize transport/auth before runtime wiring.
|
|
668
671
|
Use `init-aidenid-adapter` to scaffold a deterministic AIdenID provisioning API contract (tool binding -> HTTP path/method -> response field mapping) and cross-check it against the registry with `validate-aidenid-adapter`.
|
|
672
|
+
Use `init-session` plus `server run` for the local stdio Senti MCP server. It exposes session inbox, durable posts, actions/replies/reactions, file locks, and attention requests to local MCP clients. Hosted Claude-web/ChatGPT connectors need a separate HTTPS/OAuth service and are not shipped by the local stdio server. See [docs/mcp.md](docs/mcp.md).
|
|
673
|
+
Use `init-cli` to generate a bridge registry for every `sl` leaf command from Commander metadata. This exposes the full CLI surface as `sl.<command.path>` tool schemas, but execution still requires a bridge-capable MCP host with OAuth/session-seat policy and human approval enforcement. The hosted connector target contract is documented in [docs/mcp-hosted-connector.md](docs/mcp-hosted-connector.md).
|
|
669
674
|
|
|
670
675
|
## Plugin governance foundation (Phase 5.2 slice)
|
|
671
676
|
|
package/package.json
CHANGED
package/src/commands/mcp.js
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
validateMcpToolRegistry,
|
|
25
25
|
writeJsonFile,
|
|
26
26
|
} from "../mcp/registry.js";
|
|
27
|
+
import { buildSentinelayerCliRegistryTemplate } from "../mcp/cli-registry.js";
|
|
27
28
|
import { runMcpStdioServer } from "../mcp/session-stdio-server.js";
|
|
28
29
|
import { resolveOutputRoot } from "../config/service.js";
|
|
29
30
|
|
|
@@ -254,6 +255,39 @@ export function registerMcpCommand(program) {
|
|
|
254
255
|
console.log(pc.gray("Next: run 'sl mcp server init --id sentinelayer-session --registry-file <path>'."));
|
|
255
256
|
});
|
|
256
257
|
|
|
258
|
+
registry
|
|
259
|
+
.command("init-cli")
|
|
260
|
+
.description("Write a generated MCP registry for every SentinelLayer CLI leaf command")
|
|
261
|
+
.option("--path <path>", "Destination file path override")
|
|
262
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
263
|
+
.option("--force", "Overwrite destination file if it already exists")
|
|
264
|
+
.option("--json", "Emit machine-readable output")
|
|
265
|
+
.action(async (options, command) => {
|
|
266
|
+
const defaultPath = await resolveDefaultMcpOutputPath({
|
|
267
|
+
cwd: process.cwd(),
|
|
268
|
+
outputDir: options.outputDir,
|
|
269
|
+
env: process.env,
|
|
270
|
+
});
|
|
271
|
+
const outputPath = normalizeOutputPath(options.path, defaultPath.replace(".schema", ".cli-tools"));
|
|
272
|
+
const template = await buildSentinelayerCliRegistryTemplate();
|
|
273
|
+
validateMcpToolRegistry(template);
|
|
274
|
+
const writtenPath = await writeJsonFile(outputPath, template, { force: Boolean(options.force) });
|
|
275
|
+
const payload = {
|
|
276
|
+
command: "mcp registry init-cli",
|
|
277
|
+
outputPath: writtenPath,
|
|
278
|
+
toolCount: template.tools.length,
|
|
279
|
+
tools: template.tools.map((tool) => tool.name),
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
if (shouldEmitJson(options, command)) {
|
|
283
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
console.log(pc.green(`Wrote SentinelLayer CLI MCP registry template: ${writtenPath}`));
|
|
287
|
+
console.log(pc.gray(`${template.tools.length} CLI leaf commands exposed as bridge tools.`));
|
|
288
|
+
console.log(pc.gray("Execution requires a bridge-capable MCP host; local stdio run still exposes session tools."));
|
|
289
|
+
});
|
|
290
|
+
|
|
257
291
|
registry
|
|
258
292
|
.command("validate")
|
|
259
293
|
.description("Validate an MCP tool registry JSON payload against Sentinelayer contract")
|
package/src/commands/session.js
CHANGED
|
@@ -101,6 +101,11 @@ import { hydrateSessionFromRemote } from "../session/remote-hydrate.js";
|
|
|
101
101
|
import { mergeLiveSources } from "../session/live-source.js";
|
|
102
102
|
import { listenSessionEvents } from "../session/listener.js";
|
|
103
103
|
import { SESSION_LIVE_SUCCESS_TIPS } from "../session/coordination-guidance.js";
|
|
104
|
+
import {
|
|
105
|
+
DEFAULT_ROTATING_LOG_MAX_BYTES,
|
|
106
|
+
DEFAULT_ROTATING_LOG_MAX_FILES,
|
|
107
|
+
installRotatingConsoleLog,
|
|
108
|
+
} from "../session/rotating-log.js";
|
|
104
109
|
import { buildSessionRecap } from "../session/recap.js";
|
|
105
110
|
import { computeTranscriptStats } from "../session/transcript.js";
|
|
106
111
|
import { deriveSessionTitle } from "../session/senti-naming.js";
|
|
@@ -1811,7 +1816,10 @@ export function shouldPublishListenerPresenceHeartbeat({
|
|
|
1811
1816
|
if (elapsedMs >= keepaliveMs) {
|
|
1812
1817
|
return { publish: true, fingerprint, reason: "keepalive" };
|
|
1813
1818
|
}
|
|
1814
|
-
|
|
1819
|
+
if (elapsedMs >= minIntervalMs) {
|
|
1820
|
+
return { publish: true, fingerprint, reason: "interval" };
|
|
1821
|
+
}
|
|
1822
|
+
return { publish: false, fingerprint, reason: "interval" };
|
|
1815
1823
|
}
|
|
1816
1824
|
|
|
1817
1825
|
async function publishListenerPresenceEvent({
|
|
@@ -4475,6 +4483,17 @@ export function registerSessionCommand(program) {
|
|
|
4475
4483
|
"900",
|
|
4476
4484
|
)
|
|
4477
4485
|
.option("--no-coaching", "Do not emit periodic in-session success reminders")
|
|
4486
|
+
.option("--log-file <path>", "Also write listener output to a bounded rotating log file")
|
|
4487
|
+
.option(
|
|
4488
|
+
"--log-max-bytes <bytes>",
|
|
4489
|
+
`Rotate --log-file after this many bytes (default ${DEFAULT_ROTATING_LOG_MAX_BYTES})`,
|
|
4490
|
+
String(DEFAULT_ROTATING_LOG_MAX_BYTES),
|
|
4491
|
+
)
|
|
4492
|
+
.option(
|
|
4493
|
+
"--log-max-files <n>",
|
|
4494
|
+
`Total log files to retain including the active file (default ${DEFAULT_ROTATING_LOG_MAX_FILES})`,
|
|
4495
|
+
String(DEFAULT_ROTATING_LOG_MAX_FILES),
|
|
4496
|
+
)
|
|
4478
4497
|
.action(async (options) => {
|
|
4479
4498
|
const normalizedSessionId = resolveSessionIdOption(options);
|
|
4480
4499
|
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
@@ -4511,6 +4530,9 @@ export function registerSessionCommand(program) {
|
|
|
4511
4530
|
if (!["ndjson", "text"].includes(emitFormat)) {
|
|
4512
4531
|
throw new Error("--emit must be one of: ndjson, text.");
|
|
4513
4532
|
}
|
|
4533
|
+
const listenLogPath = normalizeString(options.logFile)
|
|
4534
|
+
? path.resolve(targetPath, String(options.logFile))
|
|
4535
|
+
: "";
|
|
4514
4536
|
// Optional wake hook: run a host command on each matched event so the
|
|
4515
4537
|
// host can resume/wake its agent (the notify->resume bridge).
|
|
4516
4538
|
const emitWakeNotice = (payload = {}) => {
|
|
@@ -4571,6 +4593,14 @@ export function registerSessionCommand(program) {
|
|
|
4571
4593
|
if (options.fromNow && options.since !== undefined) {
|
|
4572
4594
|
throw new Error("Use either --from-now or --since, not both.");
|
|
4573
4595
|
}
|
|
4596
|
+
const restoreConsoleLog = listenLogPath
|
|
4597
|
+
? installRotatingConsoleLog({
|
|
4598
|
+
logPath: listenLogPath,
|
|
4599
|
+
maxBytes: parsePositiveInteger(options.logMaxBytes, "log-max-bytes", DEFAULT_ROTATING_LOG_MAX_BYTES),
|
|
4600
|
+
maxFiles: parsePositiveInteger(options.logMaxFiles, "log-max-files", DEFAULT_ROTATING_LOG_MAX_FILES),
|
|
4601
|
+
tee: true,
|
|
4602
|
+
})
|
|
4603
|
+
: null;
|
|
4574
4604
|
const ac = new AbortController();
|
|
4575
4605
|
const onSigint = () => ac.abort();
|
|
4576
4606
|
process.on("SIGINT", onSigint);
|
|
@@ -4802,6 +4832,9 @@ export function registerSessionCommand(program) {
|
|
|
4802
4832
|
if (coachingTimer) {
|
|
4803
4833
|
clearInterval(coachingTimer);
|
|
4804
4834
|
}
|
|
4835
|
+
if (restoreConsoleLog) {
|
|
4836
|
+
restoreConsoleLog();
|
|
4837
|
+
}
|
|
4805
4838
|
process.removeListener("SIGINT", onSigint);
|
|
4806
4839
|
}
|
|
4807
4840
|
});
|
|
@@ -4826,6 +4859,17 @@ export function registerSessionCommand(program) {
|
|
|
4826
4859
|
.option("--checkpoint-idle <seconds>", "Seconds after latest source event before idle checkpoint attempt (default 600)", "600")
|
|
4827
4860
|
.option("--no-checkpoints", "Disable durable checkpoint generation")
|
|
4828
4861
|
.option("--no-checkpoint-closeout", "Skip the final closeout checkpoint when the daemon stops")
|
|
4862
|
+
.option("--log-file <path>", "Also write daemon output to a bounded rotating log file")
|
|
4863
|
+
.option(
|
|
4864
|
+
"--log-max-bytes <bytes>",
|
|
4865
|
+
`Rotate --log-file after this many bytes (default ${DEFAULT_ROTATING_LOG_MAX_BYTES})`,
|
|
4866
|
+
String(DEFAULT_ROTATING_LOG_MAX_BYTES),
|
|
4867
|
+
)
|
|
4868
|
+
.option(
|
|
4869
|
+
"--log-max-files <n>",
|
|
4870
|
+
`Total log files to retain including the active file (default ${DEFAULT_ROTATING_LOG_MAX_FILES})`,
|
|
4871
|
+
String(DEFAULT_ROTATING_LOG_MAX_FILES),
|
|
4872
|
+
)
|
|
4829
4873
|
.option("--once", "Run one Senti health tick and exit (CI/dogfood smoke)")
|
|
4830
4874
|
.option("--json", "Emit machine-readable output")
|
|
4831
4875
|
.action(async (sessionId, options, command) => {
|
|
@@ -4840,6 +4884,18 @@ export function registerSessionCommand(program) {
|
|
|
4840
4884
|
"tick-interval",
|
|
4841
4885
|
30,
|
|
4842
4886
|
);
|
|
4887
|
+
const daemonLogPath = normalizeString(options.logFile)
|
|
4888
|
+
? path.resolve(targetPath, String(options.logFile))
|
|
4889
|
+
: "";
|
|
4890
|
+
const restoreConsoleLog = daemonLogPath
|
|
4891
|
+
? installRotatingConsoleLog({
|
|
4892
|
+
logPath: daemonLogPath,
|
|
4893
|
+
maxBytes: parsePositiveInteger(options.logMaxBytes, "log-max-bytes", DEFAULT_ROTATING_LOG_MAX_BYTES),
|
|
4894
|
+
maxFiles: parsePositiveInteger(options.logMaxFiles, "log-max-files", DEFAULT_ROTATING_LOG_MAX_FILES),
|
|
4895
|
+
tee: true,
|
|
4896
|
+
})
|
|
4897
|
+
: null;
|
|
4898
|
+
try {
|
|
4843
4899
|
// One manager per session: refuse to double-run unless --force.
|
|
4844
4900
|
// A stale pid file (reboot, hard kill) reads as not-running and is
|
|
4845
4901
|
// safely overwritten.
|
|
@@ -4883,26 +4939,32 @@ export function registerSessionCommand(program) {
|
|
|
4883
4939
|
};
|
|
4884
4940
|
|
|
4885
4941
|
if (options.once) {
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
stopped
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4942
|
+
try {
|
|
4943
|
+
const payload = await runTickAndBuildPayload();
|
|
4944
|
+
const stopped = await daemon.stop("once_complete");
|
|
4945
|
+
payload.running = false;
|
|
4946
|
+
payload.stopped = {
|
|
4947
|
+
stopped: Boolean(stopped?.stopped),
|
|
4948
|
+
reason: stopped?.reason || "once_complete",
|
|
4949
|
+
checkpointCloseout: stopped?.checkpointCloseout || null,
|
|
4950
|
+
};
|
|
4951
|
+
if (emitJson) {
|
|
4952
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
4953
|
+
return;
|
|
4954
|
+
}
|
|
4955
|
+
const checkpoint = payload.summary?.checkpoint || {};
|
|
4956
|
+
const recap = payload.summary?.recap || {};
|
|
4957
|
+
console.log(
|
|
4958
|
+
pc.green(
|
|
4959
|
+
`senti tick: relayed=${Number(payload.summary?.humanMessages?.relayed || 0)} recap=${recap.emitted ? recap.mode || "emitted" : recap.reason || "none"} checkpoint=${checkpoint.created ? checkpoint.checkpointId || "created" : checkpoint.reason || "none"}`,
|
|
4960
|
+
),
|
|
4961
|
+
);
|
|
4896
4962
|
return;
|
|
4963
|
+
} finally {
|
|
4964
|
+
if (restoreConsoleLog) {
|
|
4965
|
+
restoreConsoleLog();
|
|
4966
|
+
}
|
|
4897
4967
|
}
|
|
4898
|
-
const checkpoint = payload.summary?.checkpoint || {};
|
|
4899
|
-
const recap = payload.summary?.recap || {};
|
|
4900
|
-
console.log(
|
|
4901
|
-
pc.green(
|
|
4902
|
-
`senti tick: relayed=${Number(payload.summary?.humanMessages?.relayed || 0)} recap=${recap.emitted ? recap.mode || "emitted" : recap.reason || "none"} checkpoint=${checkpoint.created ? checkpoint.checkpointId || "created" : checkpoint.reason || "none"}`,
|
|
4903
|
-
),
|
|
4904
|
-
);
|
|
4905
|
-
return;
|
|
4906
4968
|
}
|
|
4907
4969
|
|
|
4908
4970
|
const controller = new AbortController();
|
|
@@ -4992,6 +5054,14 @@ export function registerSessionCommand(program) {
|
|
|
4992
5054
|
} else {
|
|
4993
5055
|
console.log(pc.gray(`senti daemon stopped (${stopped?.reason || "signal"}).`));
|
|
4994
5056
|
}
|
|
5057
|
+
if (restoreConsoleLog) {
|
|
5058
|
+
restoreConsoleLog();
|
|
5059
|
+
}
|
|
5060
|
+
}
|
|
5061
|
+
} finally {
|
|
5062
|
+
if (restoreConsoleLog) {
|
|
5063
|
+
restoreConsoleLog();
|
|
5064
|
+
}
|
|
4995
5065
|
}
|
|
4996
5066
|
});
|
|
4997
5067
|
|