sentinelayer-cli 0.12.5 → 0.13.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentinelayer-cli",
3
- "version": "0.12.5",
3
+ "version": "0.13.1",
4
4
  "description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -31,6 +31,7 @@ export async function recordCliLlmSessionUsage({
31
31
  sourceCommand = "",
32
32
  provider = "",
33
33
  metadata = {},
34
+ syncRemote = true,
34
35
  } = {}) {
35
36
  const normalizedSessionId = normalizeString(sessionId);
36
37
  const normalizedAgentId = normalizeString(agentId);
@@ -80,7 +81,7 @@ export async function recordCliLlmSessionUsage({
80
81
  ...metadata,
81
82
  },
82
83
  },
83
- { targetPath },
84
+ { targetPath, syncRemote },
84
85
  );
85
86
  } catch (error) {
86
87
  return {
@@ -134,12 +134,12 @@ const SESSION_MESSAGE_ACTION_DESCRIPTIONS = Object.freeze([
134
134
  {
135
135
  type: "like",
136
136
  command: "sl session react <id> like --target-sequence <n>",
137
- description: "Positive lightweight feedback.",
137
+ description: "Positive lightweight feedback. Use --target-action-id <uuid> to react to a threaded reply.",
138
138
  },
139
139
  {
140
140
  type: "dislike",
141
141
  command: "sl session react <id> dislike --target-sequence <n>",
142
- description: "Negative lightweight feedback.",
142
+ description: "Negative lightweight feedback. Use --target-action-id <uuid> to react to a threaded reply.",
143
143
  },
144
144
  {
145
145
  type: "disregard",
@@ -190,6 +190,10 @@ function actionTargetCursor(action = {}) {
190
190
  return normalizeString(action.targetCursor ?? action.target_cursor);
191
191
  }
192
192
 
193
+ function actionTargetActionId(action = {}) {
194
+ return normalizeString(action.targetActionId ?? action.target_action_id);
195
+ }
196
+
193
197
  function actionActorId(action = {}) {
194
198
  return normalizeString(action.actorId ?? action.actor_id) || "unknown";
195
199
  }
@@ -201,7 +205,11 @@ function actionCreatedAt(action = {}) {
201
205
  function actionDisplayMessage(action = {}) {
202
206
  const actionType = normalizeString(action.actionType ?? action.action_type).toLowerCase();
203
207
  const targetSequence = actionTargetSequence(action);
204
- const targetLabel = targetSequence ? `#${targetSequence}` : actionTargetCursor(action) || "target";
208
+ const targetActionId = actionTargetActionId(action);
209
+ const parentLabel = targetSequence ? `#${targetSequence}` : actionTargetCursor(action) || "";
210
+ const targetLabel = targetActionId
211
+ ? `action:${targetActionId}${parentLabel ? ` (${parentLabel})` : ""}`
212
+ : parentLabel || "target";
205
213
  const note = normalizeString(action.note);
206
214
  if (note) return `${actionType} ${targetLabel}: ${note}`;
207
215
  return `${actionType} ${targetLabel}`;
@@ -217,6 +225,7 @@ function buildSessionActionEvent(sessionId, action = {}) {
217
225
  actionType,
218
226
  targetSequenceId: actionTargetSequence(action),
219
227
  targetCursor: actionTargetCursor(action),
228
+ targetActionId: actionTargetActionId(action),
220
229
  actorId: actionActorId(action),
221
230
  note: normalizeString(action.note),
222
231
  createdAt: actionCreatedAt(action),
@@ -237,6 +246,7 @@ function buildSessionActionEvent(sessionId, action = {}) {
237
246
  actionType,
238
247
  targetSequenceId: actionTargetSequence(action),
239
248
  targetCursor: actionTargetCursor(action) || null,
249
+ targetActionId: actionTargetActionId(action) || null,
240
250
  note: normalizeString(action.note) || null,
241
251
  message: actionDisplayMessage(action),
242
252
  source: "session_action",
@@ -304,10 +314,15 @@ function defaultActionIdempotencyKey({
304
314
  actionType,
305
315
  targetSequenceId,
306
316
  targetCursor,
317
+ targetActionId,
307
318
  note,
308
319
  agentId,
309
320
  } = {}) {
310
- const target = targetSequenceId ? `seq:${targetSequenceId}` : `cursor:${normalizeString(targetCursor)}`;
321
+ const target = normalizeString(targetActionId)
322
+ ? `action:${normalizeString(targetActionId)}`
323
+ : targetSequenceId
324
+ ? `seq:${targetSequenceId}`
325
+ : `cursor:${normalizeString(targetCursor)}`;
311
326
  const noteHash = note ? shortSha256(note) : "none";
312
327
  const actor = normalizeString(agentId) || "user";
313
328
  return `cli:${normalizeString(actionType).toLowerCase()}:${target}:${actor}:${noteHash}`;
@@ -1813,6 +1828,7 @@ export function registerSessionCommand(program) {
1813
1828
  commandName = "session action",
1814
1829
  targetSequenceId: targetSequenceIdOverride = null,
1815
1830
  targetCursor: targetCursorOverride = "",
1831
+ targetActionId: targetActionIdOverride = "",
1816
1832
  note: noteOverride = "",
1817
1833
  } = {}) {
1818
1834
  const normalizedSessionId = normalizeString(sessionId);
@@ -1825,8 +1841,9 @@ export function registerSessionCommand(program) {
1825
1841
  targetSequenceIdOverride ||
1826
1842
  parseOptionalPositiveInteger(options.targetSequence, "target-sequence");
1827
1843
  const targetCursor = normalizeString(targetCursorOverride) || normalizeString(options.targetCursor);
1828
- if (!targetSequenceId && !targetCursor) {
1829
- throw new Error("Provide --target-sequence or --target-cursor.");
1844
+ const targetActionId = normalizeString(targetActionIdOverride) || normalizeString(options.targetActionId);
1845
+ if (!targetSequenceId && !targetCursor && !targetActionId) {
1846
+ throw new Error("Provide --target-sequence, --target-cursor, or --target-action-id.");
1830
1847
  }
1831
1848
  await ensureLocalSessionForRemoteCommand(normalizedSessionId, { targetPath });
1832
1849
  const note = normalizeString(noteOverride) || normalizeString(options.note);
@@ -1837,6 +1854,7 @@ export function registerSessionCommand(program) {
1837
1854
  actionType: normalizedActionType,
1838
1855
  targetSequenceId,
1839
1856
  targetCursor,
1857
+ targetActionId,
1840
1858
  note,
1841
1859
  agentId,
1842
1860
  });
@@ -1846,6 +1864,7 @@ export function registerSessionCommand(program) {
1846
1864
  targetPath,
1847
1865
  targetSequenceId,
1848
1866
  targetCursor,
1867
+ targetActionId,
1849
1868
  note,
1850
1869
  metadata: {
1851
1870
  source: "cli",
@@ -1912,6 +1931,7 @@ export function registerSessionCommand(program) {
1912
1931
  )
1913
1932
  .option("--target-sequence <n>", "Target event sequence id")
1914
1933
  .option("--target-cursor <cursor>", "Target event cursor")
1934
+ .option("--target-action-id <uuid>", "Target a threaded reply/action by action UUID")
1915
1935
  .option("--note <text>", "Optional action note or reply body")
1916
1936
  .option("--agent <id>", "Agent id for local idempotency metadata", "cli-user")
1917
1937
  .option("--idempotency-key <key>", "Explicit idempotency key")
@@ -1926,6 +1946,7 @@ export function registerSessionCommand(program) {
1926
1946
  .description("React to or acknowledge a target session event with ack, like, or dislike")
1927
1947
  .option("--target-sequence <n>", "Target event sequence id")
1928
1948
  .option("--target-cursor <cursor>", "Target event cursor")
1949
+ .option("--target-action-id <uuid>", "Target a threaded reply/action by action UUID")
1929
1950
  .option("--agent <id>", "Agent id for local idempotency metadata", "cli-user")
1930
1951
  .option("--idempotency-key <key>", "Explicit idempotency key")
1931
1952
  .option("--path <path>", "Workspace path for the session", ".")
@@ -55,6 +55,14 @@ export function sessionEventIdentityKeys(event = {}) {
55
55
  if (messageId) {
56
56
  keys.push(`message:${messageId}`);
57
57
  }
58
+ const actionId = typeof payload.actionId === "string"
59
+ ? payload.actionId.trim()
60
+ : typeof payload.action_id === "string"
61
+ ? payload.action_id.trim()
62
+ : "";
63
+ if (actionId) {
64
+ keys.push(`action:${actionId}`);
65
+ }
58
66
  const timestamp = timestampKey(event.ts, event.timestamp, event.at);
59
67
  const hasPayloadSignal = Object.keys(payload).length > 0;
60
68
  const hasFingerprintSignal =
@@ -1245,6 +1245,7 @@ export async function listSessionMessageActions(
1245
1245
  {
1246
1246
  targetPath = process.cwd(),
1247
1247
  targetSequenceId = null,
1248
+ targetActionId = "",
1248
1249
  limit = SESSION_ACTION_FETCH_LIMIT,
1249
1250
  timeoutMs = DEFAULT_SYNC_TIMEOUT_MS,
1250
1251
  forceCircuitProbe = false,
@@ -1294,6 +1295,10 @@ export async function listSessionMessageActions(
1294
1295
  if (Number.isFinite(normalizedTargetSequence) && normalizedTargetSequence > 0) {
1295
1296
  query.set("targetSequenceId", String(Math.floor(normalizedTargetSequence)));
1296
1297
  }
1298
+ const normalizedTargetActionId = normalizeString(targetActionId);
1299
+ if (normalizedTargetActionId) {
1300
+ query.set("targetActionId", normalizedTargetActionId);
1301
+ }
1297
1302
  query.set(
1298
1303
  "limit",
1299
1304
  String(Math.max(1, Math.min(SESSION_ACTION_FETCH_LIMIT, normalizePositiveInteger(limit, 200))))
@@ -1351,6 +1356,7 @@ export async function createSessionMessageAction(
1351
1356
  targetPath = process.cwd(),
1352
1357
  targetSequenceId = null,
1353
1358
  targetCursor = "",
1359
+ targetActionId = "",
1354
1360
  note = "",
1355
1361
  metadata = {},
1356
1362
  idempotencyKey = "",
@@ -1402,6 +1408,10 @@ export async function createSessionMessageAction(
1402
1408
  if (normalizedTargetCursor) {
1403
1409
  body.targetCursor = normalizedTargetCursor;
1404
1410
  }
1411
+ const normalizedTargetActionId = normalizeString(targetActionId);
1412
+ if (normalizedTargetActionId) {
1413
+ body.targetActionId = normalizedTargetActionId;
1414
+ }
1405
1415
  const normalizedNote = normalizeString(note);
1406
1416
  if (normalizedNote) {
1407
1417
  body.note = normalizedNote;
@@ -155,7 +155,63 @@ function eventTimestamp(event) {
155
155
  return normalize(event?.ts || event?.timestamp);
156
156
  }
157
157
 
158
+ function actionTargetLabel(payload = {}) {
159
+ const targetActionId = normalize(payload.targetActionId || payload.target_action_id);
160
+ const targetSequence = Number(payload.targetSequenceId || payload.target_sequence_id || 0);
161
+ const targetCursor = normalize(payload.targetCursor || payload.target_cursor);
162
+ const parent =
163
+ Number.isFinite(targetSequence) && targetSequence > 0
164
+ ? `#${Math.floor(targetSequence)}`
165
+ : targetCursor
166
+ ? `cursor ${targetCursor}`
167
+ : "";
168
+ if (targetActionId) {
169
+ return parent ? `reply action ${targetActionId} under ${parent}` : `reply action ${targetActionId}`;
170
+ }
171
+ return parent || "target";
172
+ }
173
+
174
+ function actionBody(event) {
175
+ const payload = event && typeof event.payload === "object" ? event.payload : {};
176
+ const kind = normalize(event?.event || event?.type);
177
+ const actionType = normalize(payload.actionType || payload.action_type || kind.replace(/^session_/, ""));
178
+ const target = actionTargetLabel(payload);
179
+ const actionId = normalize(payload.actionId || payload.action_id);
180
+ const note = normalize(payload.note);
181
+ const metadata = [];
182
+ if (actionId) metadata.push(`Action ID: \`${actionId}\``);
183
+ if (actionType) metadata.push(`Action: \`${actionType}\``);
184
+ if (kind === "session_reply") {
185
+ return [
186
+ `**Reply to:** \`${target}\``,
187
+ ...metadata,
188
+ note ? "" : null,
189
+ note || normalize(payload.message),
190
+ ].filter((line) => line !== null && line !== "").join("\n");
191
+ }
192
+ if (kind === "session_reaction") {
193
+ return [
194
+ `**Reaction:** \`${actionType || "reaction"}\` on \`${target}\``,
195
+ ...metadata,
196
+ note ? `Note: ${note}` : null,
197
+ ].filter(Boolean).join("\n");
198
+ }
199
+ if (kind === "session_action") {
200
+ return [
201
+ `**Session action:** \`${actionType || "action"}\` on \`${target}\``,
202
+ ...metadata,
203
+ note ? "" : null,
204
+ note,
205
+ ].filter((line) => line !== null && line !== "").join("\n");
206
+ }
207
+ return "";
208
+ }
209
+
158
210
  function eventBody(event) {
211
+ const kind = normalize(event?.event || event?.type);
212
+ if (kind === "session_action" || kind === "session_reply" || kind === "session_reaction") {
213
+ return actionBody(event);
214
+ }
159
215
  const payload = event && typeof event.payload === "object" ? event.payload : {};
160
216
  // session_usage carries the response inside payload.response.text
161
217
  const responseText =