sentinelayer-cli 0.39.1 → 0.40.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/package.json +1 -1
- package/src/agents/ai-governance/tools/base.js +19 -166
- package/src/agents/backend/tools/base.js +33 -186
- package/src/agents/code-quality/tools/base.js +27 -154
- package/src/agents/data-layer/tools/base.js +35 -180
- package/src/agents/documentation/tools/base.js +18 -146
- package/src/agents/infrastructure/tools/base.js +17 -164
- package/src/agents/observability/tools/base.js +35 -180
- package/src/agents/performance/tools/base.js +35 -205
- package/src/agents/release/tools/base.js +35 -180
- package/src/agents/reliability/tools/base.js +35 -180
- package/src/agents/security/tools/base.js +33 -187
- package/src/agents/shared-tools/domain-base.js +246 -0
- package/src/agents/supply-chain/tools/base.js +15 -144
- package/src/agents/testing/tools/base.js +33 -184
- package/src/cli.js +5 -0
- package/src/commands/mcp.js +1 -1
- package/src/commands/ring-owner.js +58 -0
- package/src/commands/session.js +92 -31
- package/src/legacy-cli.js +2 -2
- package/src/mcp/smoke.js +9 -8
- package/src/pocket/ring-owner.js +97 -0
- package/src/review/local-review.js +9 -5
- package/src/review/omargate-orchestrator.js +111 -14
- package/src/review/spec-binding.js +16 -1
- package/src/scan/generator.js +1 -1
- package/src/session/checkpoints.js +194 -0
- package/src/session/pricing-ledger.js +2 -0
- package/src/session/recap.js +13 -1
|
@@ -1,151 +1,22 @@
|
|
|
1
|
-
// Shared helpers for
|
|
1
|
+
// Shared helpers for Dima's (supply-chain) domain tools (#A22).
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
createFinding as createDomainFinding,
|
|
5
|
+
toPosix,
|
|
6
|
+
walkRepoFiles as walkDomainRepoFiles,
|
|
7
|
+
} from "../../shared-tools/domain-base.js";
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
const DEFAULT_PERSONA = "supply-chain";
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
".git",
|
|
11
|
-
"node_modules",
|
|
12
|
-
".venv",
|
|
13
|
-
".next",
|
|
14
|
-
"dist",
|
|
15
|
-
"build",
|
|
16
|
-
"coverage",
|
|
17
|
-
".sentinelayer",
|
|
18
|
-
".sentinel",
|
|
19
|
-
".turbo",
|
|
20
|
-
".idea",
|
|
21
|
-
".vscode",
|
|
22
|
-
"__pycache__",
|
|
23
|
-
".cache",
|
|
24
|
-
]);
|
|
25
|
-
const MAX_FILE_SIZE_BYTES = 1024 * 1024;
|
|
26
|
-
const SEVERITIES = Object.freeze(["P0", "P1", "P2", "P3"]);
|
|
11
|
+
export { toPosix };
|
|
27
12
|
|
|
28
|
-
export function
|
|
29
|
-
return
|
|
13
|
+
export function createFinding(options = {}) {
|
|
14
|
+
return createDomainFinding(options, {
|
|
15
|
+
defaultPersona: DEFAULT_PERSONA,
|
|
16
|
+
defaultKind: DEFAULT_PERSONA,
|
|
17
|
+
});
|
|
30
18
|
}
|
|
31
19
|
|
|
32
|
-
export function
|
|
33
|
-
|
|
34
|
-
kind,
|
|
35
|
-
file,
|
|
36
|
-
line = 0,
|
|
37
|
-
evidence = "",
|
|
38
|
-
rootCause = "",
|
|
39
|
-
recommendedFix = "",
|
|
40
|
-
confidence = null,
|
|
41
|
-
tool = "",
|
|
42
|
-
persona = "supply-chain",
|
|
43
|
-
} = {}) {
|
|
44
|
-
const normalizedSeverity = SEVERITIES.includes(String(severity || "").toUpperCase())
|
|
45
|
-
? String(severity).toUpperCase()
|
|
46
|
-
: "P2";
|
|
47
|
-
return {
|
|
48
|
-
persona,
|
|
49
|
-
tool: String(tool || "").trim(),
|
|
50
|
-
kind: String(kind || "").trim() || "supply-chain",
|
|
51
|
-
severity: normalizedSeverity,
|
|
52
|
-
file: toPosix(file || ""),
|
|
53
|
-
line: Number.isFinite(Number(line)) ? Math.max(0, Math.floor(Number(line))) : 0,
|
|
54
|
-
evidence: String(evidence || "").trim().slice(0, 400),
|
|
55
|
-
rootCause: String(rootCause || "").trim(),
|
|
56
|
-
recommendedFix: String(recommendedFix || "").trim(),
|
|
57
|
-
confidence:
|
|
58
|
-
confidence === null || confidence === undefined
|
|
59
|
-
? null
|
|
60
|
-
: Math.max(0, Math.min(1, Number(confidence) || 0)),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function readIgnorePatterns(filePath) {
|
|
65
|
-
try {
|
|
66
|
-
const raw = await fsp.readFile(filePath, "utf-8");
|
|
67
|
-
return String(raw || "")
|
|
68
|
-
.split(/\r?\n/)
|
|
69
|
-
.map((line) => line.trim())
|
|
70
|
-
.filter((line) => line && !line.startsWith("#"));
|
|
71
|
-
} catch (err) {
|
|
72
|
-
if (err && typeof err === "object" && err.code === "ENOENT") {
|
|
73
|
-
return [];
|
|
74
|
-
}
|
|
75
|
-
throw err;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function createIgnoreMatcher(rootPath) {
|
|
80
|
-
const matcher = ignore();
|
|
81
|
-
const gitignore = await readIgnorePatterns(path.join(rootPath, ".gitignore"));
|
|
82
|
-
const sentinel = await readIgnorePatterns(
|
|
83
|
-
path.join(rootPath, ".sentinelayerignore")
|
|
84
|
-
);
|
|
85
|
-
matcher.add([...gitignore, ...sentinel]);
|
|
86
|
-
return (relativePath, isDirectory) => {
|
|
87
|
-
const normalized = toPosix(relativePath);
|
|
88
|
-
if (!normalized) {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
const candidate = isDirectory ? `${normalized}/` : normalized;
|
|
92
|
-
return matcher.ignores(candidate);
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export async function* walkRepoFiles({
|
|
97
|
-
rootPath = process.cwd(),
|
|
98
|
-
extensions = new Set(),
|
|
99
|
-
maxFileSize = MAX_FILE_SIZE_BYTES,
|
|
100
|
-
} = {}) {
|
|
101
|
-
const resolvedRoot = path.resolve(rootPath);
|
|
102
|
-
const ignoreMatcher = await createIgnoreMatcher(resolvedRoot);
|
|
103
|
-
const wantedExtensions =
|
|
104
|
-
extensions instanceof Set
|
|
105
|
-
? extensions
|
|
106
|
-
: new Set(Array.isArray(extensions) ? extensions : []);
|
|
107
|
-
const stack = [resolvedRoot];
|
|
108
|
-
while (stack.length > 0) {
|
|
109
|
-
const current = stack.pop();
|
|
110
|
-
let entries = [];
|
|
111
|
-
try {
|
|
112
|
-
entries = await fsp.readdir(current, { withFileTypes: true });
|
|
113
|
-
} catch {
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
for (const entry of entries) {
|
|
117
|
-
const fullPath = path.join(current, entry.name);
|
|
118
|
-
const relativePath = toPosix(path.relative(resolvedRoot, fullPath));
|
|
119
|
-
if (entry.isDirectory()) {
|
|
120
|
-
if (!relativePath || DEFAULT_IGNORED_DIRS.has(entry.name)) {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (ignoreMatcher(relativePath, true)) {
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
stack.push(fullPath);
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
if (!entry.isFile()) {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
if (ignoreMatcher(relativePath, false)) {
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
const ext = path.extname(entry.name).toLowerCase();
|
|
136
|
-
if (wantedExtensions.size > 0 && !wantedExtensions.has(ext) && !wantedExtensions.has("")) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
let stat = null;
|
|
140
|
-
try {
|
|
141
|
-
stat = await fsp.stat(fullPath);
|
|
142
|
-
} catch {
|
|
143
|
-
stat = null;
|
|
144
|
-
}
|
|
145
|
-
if (!stat || stat.size > maxFileSize) {
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
yield { fullPath, relativePath };
|
|
149
|
-
}
|
|
150
|
-
}
|
|
20
|
+
export async function* walkRepoFiles(options = {}) {
|
|
21
|
+
yield* walkDomainRepoFiles(options);
|
|
151
22
|
}
|
|
@@ -1,188 +1,18 @@
|
|
|
1
1
|
// Shared helpers for Priya's (testing) domain tools (#A15).
|
|
2
|
-
// Same pattern as security/tools/base.js and backend/tools/base.js — default
|
|
3
|
-
// persona is "testing". Later refactor (#A25+) can hoist to shared-tools/.
|
|
4
2
|
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_IGNORED_DIRS,
|
|
5
|
+
MAX_FILE_SIZE_BYTES,
|
|
6
|
+
SEVERITIES,
|
|
7
|
+
createFinding as createDomainFinding,
|
|
8
|
+
findLineMatches,
|
|
9
|
+
getLineContent,
|
|
10
|
+
normalizeSeverity,
|
|
11
|
+
toPosix,
|
|
12
|
+
walkRepoFiles as walkDomainRepoFiles,
|
|
13
|
+
} from "../../shared-tools/domain-base.js";
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const DEFAULT_IGNORED_DIRS = new Set([
|
|
12
|
-
".git",
|
|
13
|
-
"node_modules",
|
|
14
|
-
".venv",
|
|
15
|
-
".next",
|
|
16
|
-
"dist",
|
|
17
|
-
"build",
|
|
18
|
-
"coverage",
|
|
19
|
-
".sentinelayer",
|
|
20
|
-
".sentinel",
|
|
21
|
-
".turbo",
|
|
22
|
-
".idea",
|
|
23
|
-
".vscode",
|
|
24
|
-
"__pycache__",
|
|
25
|
-
".cache",
|
|
26
|
-
]);
|
|
27
|
-
const MAX_FILE_SIZE_BYTES = 1024 * 1024;
|
|
28
|
-
const SEVERITIES = Object.freeze(["P0", "P1", "P2", "P3"]);
|
|
29
|
-
|
|
30
|
-
export function toPosix(value) {
|
|
31
|
-
return String(value || "").replace(/\\/g, "/");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function normalizeSeverity(value) {
|
|
35
|
-
const normalized = String(value || "").trim().toUpperCase();
|
|
36
|
-
if (SEVERITIES.includes(normalized)) {
|
|
37
|
-
return normalized;
|
|
38
|
-
}
|
|
39
|
-
return "P2";
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function createFinding({
|
|
43
|
-
severity,
|
|
44
|
-
kind,
|
|
45
|
-
file,
|
|
46
|
-
line = 0,
|
|
47
|
-
evidence = "",
|
|
48
|
-
rootCause = "",
|
|
49
|
-
recommendedFix = "",
|
|
50
|
-
confidence = null,
|
|
51
|
-
tool = "",
|
|
52
|
-
persona = "testing",
|
|
53
|
-
} = {}) {
|
|
54
|
-
return {
|
|
55
|
-
persona,
|
|
56
|
-
tool: String(tool || "").trim(),
|
|
57
|
-
kind: String(kind || "").trim() || "testing",
|
|
58
|
-
severity: normalizeSeverity(severity),
|
|
59
|
-
file: toPosix(file || ""),
|
|
60
|
-
line: Number.isFinite(Number(line)) ? Math.max(0, Math.floor(Number(line))) : 0,
|
|
61
|
-
evidence: String(evidence || "").trim().slice(0, 400),
|
|
62
|
-
rootCause: String(rootCause || "").trim(),
|
|
63
|
-
recommendedFix: String(recommendedFix || "").trim(),
|
|
64
|
-
confidence:
|
|
65
|
-
confidence === null || confidence === undefined
|
|
66
|
-
? null
|
|
67
|
-
: Math.max(0, Math.min(1, Number(confidence) || 0)),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function readIgnorePatterns(filePath) {
|
|
72
|
-
try {
|
|
73
|
-
const raw = await fsp.readFile(filePath, "utf-8");
|
|
74
|
-
return String(raw || "")
|
|
75
|
-
.split(/\r?\n/)
|
|
76
|
-
.map((line) => line.trim())
|
|
77
|
-
.filter((line) => line && !line.startsWith("#"));
|
|
78
|
-
} catch (err) {
|
|
79
|
-
if (err && typeof err === "object" && err.code === "ENOENT") {
|
|
80
|
-
return [];
|
|
81
|
-
}
|
|
82
|
-
throw err;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function createIgnoreMatcher(rootPath) {
|
|
87
|
-
const matcher = ignore();
|
|
88
|
-
const gitignore = await readIgnorePatterns(path.join(rootPath, ".gitignore"));
|
|
89
|
-
const sentinel = await readIgnorePatterns(
|
|
90
|
-
path.join(rootPath, ".sentinelayerignore")
|
|
91
|
-
);
|
|
92
|
-
matcher.add([...gitignore, ...sentinel]);
|
|
93
|
-
return (relativePath, isDirectory) => {
|
|
94
|
-
const normalized = toPosix(relativePath);
|
|
95
|
-
if (!normalized) {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
const candidate = isDirectory ? `${normalized}/` : normalized;
|
|
99
|
-
return matcher.ignores(candidate);
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export async function* walkRepoFiles({
|
|
104
|
-
rootPath = process.cwd(),
|
|
105
|
-
extensions = new Set(),
|
|
106
|
-
maxFileSize = MAX_FILE_SIZE_BYTES,
|
|
107
|
-
} = {}) {
|
|
108
|
-
const resolvedRoot = path.resolve(rootPath);
|
|
109
|
-
const ignoreMatcher = await createIgnoreMatcher(resolvedRoot);
|
|
110
|
-
const wantedExtensions =
|
|
111
|
-
extensions instanceof Set
|
|
112
|
-
? extensions
|
|
113
|
-
: new Set(Array.isArray(extensions) ? extensions : []);
|
|
114
|
-
const stack = [resolvedRoot];
|
|
115
|
-
while (stack.length > 0) {
|
|
116
|
-
const current = stack.pop();
|
|
117
|
-
let entries = [];
|
|
118
|
-
try {
|
|
119
|
-
entries = await fsp.readdir(current, { withFileTypes: true });
|
|
120
|
-
} catch {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
for (const entry of entries) {
|
|
124
|
-
const fullPath = path.join(current, entry.name);
|
|
125
|
-
const relativePath = toPosix(path.relative(resolvedRoot, fullPath));
|
|
126
|
-
if (entry.isDirectory()) {
|
|
127
|
-
if (!relativePath || DEFAULT_IGNORED_DIRS.has(entry.name)) {
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
if (ignoreMatcher(relativePath, true)) {
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
stack.push(fullPath);
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
if (!entry.isFile()) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
if (ignoreMatcher(relativePath, false)) {
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
const ext = path.extname(entry.name).toLowerCase();
|
|
143
|
-
if (wantedExtensions.size > 0 && !wantedExtensions.has(ext) && !wantedExtensions.has("")) {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
let stat = null;
|
|
147
|
-
try {
|
|
148
|
-
stat = await fsp.stat(fullPath);
|
|
149
|
-
} catch {
|
|
150
|
-
stat = null;
|
|
151
|
-
}
|
|
152
|
-
if (!stat || stat.size > maxFileSize) {
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
yield { fullPath, relativePath, stat };
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export function findLineMatches(content, pattern) {
|
|
161
|
-
const text = String(content || "");
|
|
162
|
-
if (!pattern) {
|
|
163
|
-
return [];
|
|
164
|
-
}
|
|
165
|
-
const global = new RegExp(
|
|
166
|
-
pattern.source,
|
|
167
|
-
pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`
|
|
168
|
-
);
|
|
169
|
-
const matches = [];
|
|
170
|
-
let match;
|
|
171
|
-
while ((match = global.exec(text)) !== null) {
|
|
172
|
-
const lineIndex = text.slice(0, match.index).split(/\r?\n/).length;
|
|
173
|
-
matches.push({
|
|
174
|
-
index: match.index,
|
|
175
|
-
line: lineIndex,
|
|
176
|
-
match: match[0],
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
return matches;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function getLineContent(content, line) {
|
|
183
|
-
const lines = String(content || "").split(/\r?\n/);
|
|
184
|
-
return (lines[Math.max(0, (Number(line) || 1) - 1)] || "").trim();
|
|
185
|
-
}
|
|
15
|
+
const DEFAULT_PERSONA = "testing";
|
|
186
16
|
|
|
187
17
|
const TEST_FILE_PATTERNS = [
|
|
188
18
|
/\.test\.(js|jsx|ts|tsx|mjs|cjs)$/,
|
|
@@ -194,9 +24,28 @@ const TEST_FILE_PATTERNS = [
|
|
|
194
24
|
/(^|\/)tests?\/[^/]*\.py$/,
|
|
195
25
|
];
|
|
196
26
|
|
|
27
|
+
export {
|
|
28
|
+
DEFAULT_IGNORED_DIRS,
|
|
29
|
+
MAX_FILE_SIZE_BYTES,
|
|
30
|
+
SEVERITIES,
|
|
31
|
+
findLineMatches,
|
|
32
|
+
getLineContent,
|
|
33
|
+
normalizeSeverity,
|
|
34
|
+
toPosix,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function createFinding(options = {}) {
|
|
38
|
+
return createDomainFinding(options, {
|
|
39
|
+
defaultPersona: DEFAULT_PERSONA,
|
|
40
|
+
defaultKind: DEFAULT_PERSONA,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function* walkRepoFiles(options = {}) {
|
|
45
|
+
yield* walkDomainRepoFiles(options, { entryMetadata: "stat" });
|
|
46
|
+
}
|
|
47
|
+
|
|
197
48
|
export function isTestFile(relativePath) {
|
|
198
49
|
const normalized = toPosix(relativePath);
|
|
199
50
|
return TEST_FILE_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
200
51
|
}
|
|
201
|
-
|
|
202
|
-
export { DEFAULT_IGNORED_DIRS, MAX_FILE_SIZE_BYTES, SEVERITIES };
|
package/src/cli.js
CHANGED
|
@@ -124,6 +124,11 @@ const COMMAND_REGISTRARS = {
|
|
|
124
124
|
exportName: "registerSessionCommand",
|
|
125
125
|
needsLegacy: false,
|
|
126
126
|
},
|
|
127
|
+
"ring-owner": {
|
|
128
|
+
loader: () => import("./commands/ring-owner.js"),
|
|
129
|
+
exportName: "registerRingOwnerCommand",
|
|
130
|
+
needsLegacy: false,
|
|
131
|
+
},
|
|
127
132
|
};
|
|
128
133
|
|
|
129
134
|
const COMMAND_SET = new Set(Object.keys(COMMAND_REGISTRARS));
|
package/src/commands/mcp.js
CHANGED
|
@@ -236,7 +236,7 @@ Docs: https://github.com/mrrCarter/create-sentinelayer/blob/main/docs/mcp.md`,
|
|
|
236
236
|
.description(
|
|
237
237
|
"Smoke the hosted MCP resource with an in-memory bearer; prints only redacted proof"
|
|
238
238
|
)
|
|
239
|
-
.option("--session <id>", "Optional Senti session id to read through
|
|
239
|
+
.option("--session <id>", "Optional Senti session id to read through sessions_events_list")
|
|
240
240
|
.option("--limit <n>", "Maximum session events to read when --session is set", "5")
|
|
241
241
|
.option(
|
|
242
242
|
"--scope <scopes>",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
import { ringOwner } from "../pocket/ring-owner.js";
|
|
4
|
+
|
|
5
|
+
function shouldEmitJson(options, command) {
|
|
6
|
+
if (options && options.json) return true;
|
|
7
|
+
return typeof command?.optsWithGlobals === "function" ? Boolean(command.optsWithGlobals().json) : false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function registerRingOwnerCommand(program) {
|
|
11
|
+
program
|
|
12
|
+
.command("ring-owner")
|
|
13
|
+
.description("Ring the owner's phone about a decision that needs them (Senti Pocket dial)")
|
|
14
|
+
.argument("<question>", "The question / decision to put to the owner")
|
|
15
|
+
.requiredOption("--session <id>", "The session this decision belongs to")
|
|
16
|
+
.option("--kind <kind>", "decisionYours | pickOption | go | info | checkpointReady", "decisionYours")
|
|
17
|
+
.option(
|
|
18
|
+
"--option <label>",
|
|
19
|
+
"A pickOption choice (repeat for multiple)",
|
|
20
|
+
(value, acc) => { acc.push(value); return acc; },
|
|
21
|
+
[],
|
|
22
|
+
)
|
|
23
|
+
.option("--what-we-need <text>", "A short 'what we need' lead-in for the ring")
|
|
24
|
+
.option("--checkpoint <id>", "An optional checkpoint id for jump-to context")
|
|
25
|
+
.option("--idempotency-key <key>", "Dedupe key so an accidental retry rings only once")
|
|
26
|
+
.option("--gateway-url <url>", "Pocket gateway base URL (else the SENTI_POCKET_URL env var)")
|
|
27
|
+
.option("--json", "Emit machine-readable output")
|
|
28
|
+
.action(async (question, options, command) => {
|
|
29
|
+
const emitJson = shouldEmitJson(options, command);
|
|
30
|
+
try {
|
|
31
|
+
const result = await ringOwner(question, {
|
|
32
|
+
kind: options.kind,
|
|
33
|
+
sessionId: options.session,
|
|
34
|
+
options: options.option || [],
|
|
35
|
+
whatWeNeed: options.whatWeNeed,
|
|
36
|
+
checkpointId: options.checkpoint,
|
|
37
|
+
idempotencyKey: options.idempotencyKey,
|
|
38
|
+
gatewayUrl: options.gatewayUrl,
|
|
39
|
+
env: process.env,
|
|
40
|
+
});
|
|
41
|
+
if (emitJson) {
|
|
42
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (result && result.dispatched) {
|
|
46
|
+
const replay = result.idempotent ? " (idempotent replay — already ringing)" : "";
|
|
47
|
+
process.stdout.write(`Ringing the owner — dialId ${result.dialId}${replay}\n`);
|
|
48
|
+
} else {
|
|
49
|
+
const reason = result && result.reason ? `: ${result.reason}` : "";
|
|
50
|
+
process.stdout.write(`Not dispatched${reason} (dialId ${(result && result.dialId) || "n/a"})\n`);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
process.stderr.write(`ring-owner failed: ${err && err.message ? err.message : String(err)}\n`);
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
package/src/commands/session.js
CHANGED
|
@@ -138,6 +138,7 @@ import {
|
|
|
138
138
|
generateSessionCheckpoint,
|
|
139
139
|
generateSessionCheckpointBatch,
|
|
140
140
|
listSessionCheckpoints,
|
|
141
|
+
showSessionCheckpoint,
|
|
141
142
|
} from "../session/checkpoints.js";
|
|
142
143
|
import {
|
|
143
144
|
buildCodexExecResumeInvocation,
|
|
@@ -2317,7 +2318,7 @@ export async function resolveMessageActionIdentity({
|
|
|
2317
2318
|
return resolveSessionSayIdentity({ sessionId, agentId: agentSeed, targetPath, env });
|
|
2318
2319
|
}
|
|
2319
2320
|
|
|
2320
|
-
async function
|
|
2321
|
+
async function recordPublishedSessionAgentActivity(
|
|
2321
2322
|
sessionId,
|
|
2322
2323
|
agent = {},
|
|
2323
2324
|
{ targetPath = process.cwd() } = {},
|
|
@@ -2328,33 +2329,27 @@ async function ensureSessionSayAgentRegistered(
|
|
|
2328
2329
|
}
|
|
2329
2330
|
|
|
2330
2331
|
try {
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
)
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2332
|
+
const snapshot = await rememberAgentIdentity(sessionId, {
|
|
2333
|
+
agentId,
|
|
2334
|
+
model: normalizeString(agent.model) || "cli",
|
|
2335
|
+
displayName: normalizeString(agent.displayName),
|
|
2336
|
+
provider: normalizeString(agent.provider),
|
|
2337
|
+
clientKind: normalizeString(agent.clientKind) || "cli",
|
|
2338
|
+
role: sessionSayRegistryRole(agent.role),
|
|
2339
|
+
targetPath,
|
|
2340
|
+
});
|
|
2341
|
+
return {
|
|
2342
|
+
persisted: true,
|
|
2343
|
+
agentId: snapshot.agentId,
|
|
2344
|
+
lastActivityAt: snapshot.lastActivityAt,
|
|
2345
|
+
};
|
|
2346
|
+
} catch (error) {
|
|
2347
|
+
return {
|
|
2348
|
+
persisted: false,
|
|
2349
|
+
reason: "activity_update_failed",
|
|
2350
|
+
detail: normalizeString(error?.message),
|
|
2351
|
+
};
|
|
2342
2352
|
}
|
|
2343
|
-
|
|
2344
|
-
const registered = await rememberAgentIdentity(sessionId, {
|
|
2345
|
-
agentId,
|
|
2346
|
-
model: normalizeString(agent.model) || "cli",
|
|
2347
|
-
displayName: normalizeString(agent.displayName),
|
|
2348
|
-
provider: normalizeString(agent.provider),
|
|
2349
|
-
clientKind: normalizeString(agent.clientKind) || "cli",
|
|
2350
|
-
role: sessionSayRegistryRole(agent.role),
|
|
2351
|
-
targetPath,
|
|
2352
|
-
});
|
|
2353
|
-
|
|
2354
|
-
return {
|
|
2355
|
-
persisted: true,
|
|
2356
|
-
agentId: registered.agentId,
|
|
2357
|
-
};
|
|
2358
2353
|
}
|
|
2359
2354
|
|
|
2360
2355
|
async function resolveSessionAgentEnvelope(
|
|
@@ -3645,9 +3640,6 @@ export function registerSessionCommand(program) {
|
|
|
3645
3640
|
role: options.role,
|
|
3646
3641
|
displayName: options.displayName,
|
|
3647
3642
|
});
|
|
3648
|
-
const agentRegistration = await ensureSessionSayAgentRegistered(normalizedSessionId, agent, {
|
|
3649
|
-
targetPath,
|
|
3650
|
-
});
|
|
3651
3643
|
const event = createAgentEvent({
|
|
3652
3644
|
event: "session_message",
|
|
3653
3645
|
agent,
|
|
@@ -3699,6 +3691,10 @@ export function registerSessionCommand(program) {
|
|
|
3699
3691
|
targetPath,
|
|
3700
3692
|
syncRemote: false,
|
|
3701
3693
|
});
|
|
3694
|
+
const agentActivity = await recordPublishedSessionAgentActivity(normalizedSessionId, agent, {
|
|
3695
|
+
targetPath,
|
|
3696
|
+
});
|
|
3697
|
+
const agentRegistration = agentActivity;
|
|
3702
3698
|
const payload = {
|
|
3703
3699
|
command: "session say",
|
|
3704
3700
|
targetPath,
|
|
@@ -3710,6 +3706,7 @@ export function registerSessionCommand(program) {
|
|
|
3710
3706
|
identitySource: identity.source,
|
|
3711
3707
|
identityWarning: identity.identityWarning || undefined,
|
|
3712
3708
|
agentRegistration,
|
|
3709
|
+
agentActivity,
|
|
3713
3710
|
remoteSync: remoteSync || undefined,
|
|
3714
3711
|
remoteConfirmationAnchor: remoteConfirmationAnchor || undefined,
|
|
3715
3712
|
remoteConfirmation: remoteConfirmation || undefined,
|
|
@@ -3822,6 +3819,9 @@ export function registerSessionCommand(program) {
|
|
|
3822
3819
|
targetPath,
|
|
3823
3820
|
syncRemote: false,
|
|
3824
3821
|
});
|
|
3822
|
+
const agentActivity = await recordPublishedSessionAgentActivity(normalizedSessionId, agent, {
|
|
3823
|
+
targetPath,
|
|
3824
|
+
});
|
|
3825
3825
|
const payload = {
|
|
3826
3826
|
command: "session post-agent",
|
|
3827
3827
|
targetPath,
|
|
@@ -3830,6 +3830,7 @@ export function registerSessionCommand(program) {
|
|
|
3830
3830
|
event: persisted,
|
|
3831
3831
|
materializedLocalSession: localSession.materialized,
|
|
3832
3832
|
refreshedLocalSession: Boolean(localSession.refreshed),
|
|
3833
|
+
agentActivity,
|
|
3833
3834
|
remoteSync,
|
|
3834
3835
|
remoteConfirmationAnchor,
|
|
3835
3836
|
remoteConfirmation,
|
|
@@ -6151,7 +6152,7 @@ export function registerSessionCommand(program) {
|
|
|
6151
6152
|
|
|
6152
6153
|
const checkpoint = session
|
|
6153
6154
|
.command("checkpoint")
|
|
6154
|
-
.description("List, create, and generate durable session checkpoints");
|
|
6155
|
+
.description("List, show, create, and generate durable session checkpoints");
|
|
6155
6156
|
|
|
6156
6157
|
checkpoint
|
|
6157
6158
|
.command("list <sessionId>")
|
|
@@ -6187,6 +6188,66 @@ export function registerSessionCommand(program) {
|
|
|
6187
6188
|
}
|
|
6188
6189
|
});
|
|
6189
6190
|
|
|
6191
|
+
checkpoint
|
|
6192
|
+
.command("show <sessionId> <checkpointId>")
|
|
6193
|
+
.description("Show a checkpoint and a bounded read-only source event window")
|
|
6194
|
+
.option("--context-events <n>", "Adjacent events to include before/after the checkpoint range (default 3)", "3")
|
|
6195
|
+
.option("--max-events <n>", "Maximum events to fetch for restore context (default 120, max 200)", "120")
|
|
6196
|
+
.option("--path <path>", "Workspace path for auth/session context", ".")
|
|
6197
|
+
.option("--json", "Emit machine-readable output")
|
|
6198
|
+
.action(async (sessionId, checkpointId, options, command) => {
|
|
6199
|
+
const normalizedSessionId = normalizeString(sessionId);
|
|
6200
|
+
const normalizedCheckpointId = normalizeString(checkpointId);
|
|
6201
|
+
if (!normalizedSessionId) {
|
|
6202
|
+
throw new Error("session id is required.");
|
|
6203
|
+
}
|
|
6204
|
+
if (!normalizedCheckpointId) {
|
|
6205
|
+
throw new Error("checkpoint id is required.");
|
|
6206
|
+
}
|
|
6207
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
6208
|
+
const result = await showSessionCheckpoint(normalizedSessionId, normalizedCheckpointId, {
|
|
6209
|
+
targetPath,
|
|
6210
|
+
contextEvents: options.contextEvents,
|
|
6211
|
+
maxEvents: options.maxEvents,
|
|
6212
|
+
});
|
|
6213
|
+
const payload = {
|
|
6214
|
+
command: "session checkpoint show",
|
|
6215
|
+
targetPath,
|
|
6216
|
+
...result,
|
|
6217
|
+
};
|
|
6218
|
+
if (shouldEmitJson(options, command)) {
|
|
6219
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
6220
|
+
return;
|
|
6221
|
+
}
|
|
6222
|
+
|
|
6223
|
+
console.log(pc.bold(`Checkpoint ${result.checkpointId}`));
|
|
6224
|
+
console.log(formatCheckpointLine(result.checkpoint));
|
|
6225
|
+
const sourceRange = `#${result.window.startSequence}-${result.window.endSequence}`;
|
|
6226
|
+
const contextSummary =
|
|
6227
|
+
`${result.window.sourceEventCount}/${result.window.sourceEventCountExpected} source` +
|
|
6228
|
+
`, ${result.window.beforeContextCount} before` +
|
|
6229
|
+
`, ${result.window.afterContextCount} after`;
|
|
6230
|
+
console.log(pc.gray(`Read-only restore window ${sourceRange}: ${contextSummary}.`));
|
|
6231
|
+
if (!result.ok) {
|
|
6232
|
+
console.log(pc.yellow(`Remote event window unavailable: ${result.reason || "unknown"}`));
|
|
6233
|
+
return;
|
|
6234
|
+
}
|
|
6235
|
+
if (result.window.partial) {
|
|
6236
|
+
const missing = result.window.missingSourceEvents
|
|
6237
|
+
? ` missing=${result.window.missingSourceEvents}`
|
|
6238
|
+
: "";
|
|
6239
|
+
const truncated = result.window.truncatedByLimit ? " truncated-by-limit" : "";
|
|
6240
|
+
console.log(pc.yellow(`Partial checkpoint source range:${missing}${truncated}`.trim()));
|
|
6241
|
+
}
|
|
6242
|
+
for (const event of result.window.events) {
|
|
6243
|
+
const sequence = eventSequenceNumber(event);
|
|
6244
|
+
const inSource =
|
|
6245
|
+
sequence >= result.window.startSequence &&
|
|
6246
|
+
sequence <= result.window.endSequence;
|
|
6247
|
+
console.log(`${inSource ? "*" : " "} ${formatEventLine(event)}`);
|
|
6248
|
+
}
|
|
6249
|
+
});
|
|
6250
|
+
|
|
6190
6251
|
checkpoint
|
|
6191
6252
|
.command("create <sessionId>")
|
|
6192
6253
|
.description("Create a durable checkpoint anchored to a canonical sequence range")
|
package/src/legacy-cli.js
CHANGED
|
@@ -2517,7 +2517,7 @@ jobs:
|
|
|
2517
2517
|
fi
|
|
2518
2518
|
- name: Run Omar Gate
|
|
2519
2519
|
id: omar
|
|
2520
|
-
uses: mrrCarter/sentinelayer-v1-action@
|
|
2520
|
+
uses: mrrCarter/sentinelayer-v1-action@8afb7dbb42a5e1c25233d422c4cabe401ba02380
|
|
2521
2521
|
with:
|
|
2522
2522
|
github_token: \${{ github.token }}
|
|
2523
2523
|
openai_api_key: \${{ secrets.OPENAI_API_KEY }}
|
|
@@ -2626,7 +2626,7 @@ jobs:
|
|
|
2626
2626
|
},
|
|
2627
2627
|
"scan": {
|
|
2628
2628
|
"mode": env("OMAR_SCAN_MODE", "deep"),
|
|
2629
|
-
"action_ref": "mrrCarter/sentinelayer-v1-action@
|
|
2629
|
+
"action_ref": "mrrCarter/sentinelayer-v1-action@8afb7dbb42a5e1c25233d422c4cabe401ba02380",
|
|
2630
2630
|
"llm_provider": env("OMAR_LLM_PROVIDER", "openai"),
|
|
2631
2631
|
"model": env("OMAR_MODEL", "gpt-5.3-codex"),
|
|
2632
2632
|
"model_fallback": env("OMAR_MODEL_FALLBACK", "gpt-4.1-mini"),
|