sentinelayer-cli 0.12.4 → 0.13.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 +1 -0
- package/package.json +1 -1
- package/src/billing/llm-session-usage.js +2 -1
- package/src/commands/session.js +136 -7
- package/src/legacy-cli.js +6 -0
- package/src/session/coordination-guidance.js +1 -1
- package/src/session/recap.js +1 -1
- package/src/session/sync.js +10 -0
- package/src/session/transcript.js +56 -0
package/README.md
CHANGED
|
@@ -104,6 +104,7 @@ Inputs for non-interactive mode:
|
|
|
104
104
|
Sentinelayer includes a deterministic session coordination surface for multi-agent coding loops:
|
|
105
105
|
|
|
106
106
|
- session event stream and replay (`start`, `join`, `say`, `read`, `status`, `leave`, `list`, `kill`)
|
|
107
|
+
- low-noise message actions (`react ack|like|dislike`, `action working_on|disregard`, `reply`/`comment`, `view`, `actions`)
|
|
107
108
|
- agent lifecycle controls (join/heartbeat/leave/kill)
|
|
108
109
|
- recap and context briefing for late-joining agents
|
|
109
110
|
- analytics + lineage artifacts at session closeout
|
package/package.json
CHANGED
|
@@ -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 {
|
package/src/commands/session.js
CHANGED
|
@@ -107,13 +107,58 @@ const SESSION_MESSAGE_ACTION_TYPES = new Set([
|
|
|
107
107
|
"like",
|
|
108
108
|
"dislike",
|
|
109
109
|
"disregard",
|
|
110
|
+
"view",
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
const SESSION_MESSAGE_ACTION_ALIASES = new Map([
|
|
114
|
+
["comment", "reply"],
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
const SESSION_MESSAGE_ACTION_DESCRIPTIONS = Object.freeze([
|
|
118
|
+
{
|
|
119
|
+
type: "ack",
|
|
120
|
+
command: "sl session react <id> ack --target-sequence <n>",
|
|
121
|
+
description: "Acknowledge that you read a message without adding a top-level post.",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: "working_on",
|
|
125
|
+
command: "sl session action <id> working_on --target-sequence <n> --note \"scope\"",
|
|
126
|
+
description: "Claim active ownership of a target message or task.",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: "reply",
|
|
130
|
+
alias: "comment",
|
|
131
|
+
command: "sl session reply <id> <sequence> \"message\"",
|
|
132
|
+
description: "Thread a substantive response under a specific message.",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: "like",
|
|
136
|
+
command: "sl session react <id> like --target-sequence <n>",
|
|
137
|
+
description: "Positive lightweight feedback. Use --target-action-id <uuid> to react to a threaded reply.",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
type: "dislike",
|
|
141
|
+
command: "sl session react <id> dislike --target-sequence <n>",
|
|
142
|
+
description: "Negative lightweight feedback. Use --target-action-id <uuid> to react to a threaded reply.",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: "disregard",
|
|
146
|
+
command: "sl session action <id> disregard --target-sequence <n>",
|
|
147
|
+
description: "Mark a message as intentionally ignored or superseded.",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
type: "view",
|
|
151
|
+
command: "sl session view <id> <sequence>",
|
|
152
|
+
description: "Record a read receipt for a target message.",
|
|
153
|
+
},
|
|
110
154
|
]);
|
|
111
155
|
|
|
112
156
|
function normalizeSessionMessageActionType(value) {
|
|
113
|
-
const
|
|
157
|
+
const raw = normalizeString(value).toLowerCase();
|
|
158
|
+
const normalized = SESSION_MESSAGE_ACTION_ALIASES.get(raw) || raw;
|
|
114
159
|
if (!SESSION_MESSAGE_ACTION_TYPES.has(normalized)) {
|
|
115
160
|
throw new Error(
|
|
116
|
-
`action type must be one of: ${[...SESSION_MESSAGE_ACTION_TYPES].join(", ")}.`,
|
|
161
|
+
`action type must be one of: ${[...SESSION_MESSAGE_ACTION_TYPES].join(", ")}; aliases: comment=reply.`,
|
|
117
162
|
);
|
|
118
163
|
}
|
|
119
164
|
return normalized;
|
|
@@ -145,6 +190,10 @@ function actionTargetCursor(action = {}) {
|
|
|
145
190
|
return normalizeString(action.targetCursor ?? action.target_cursor);
|
|
146
191
|
}
|
|
147
192
|
|
|
193
|
+
function actionTargetActionId(action = {}) {
|
|
194
|
+
return normalizeString(action.targetActionId ?? action.target_action_id);
|
|
195
|
+
}
|
|
196
|
+
|
|
148
197
|
function actionActorId(action = {}) {
|
|
149
198
|
return normalizeString(action.actorId ?? action.actor_id) || "unknown";
|
|
150
199
|
}
|
|
@@ -156,7 +205,11 @@ function actionCreatedAt(action = {}) {
|
|
|
156
205
|
function actionDisplayMessage(action = {}) {
|
|
157
206
|
const actionType = normalizeString(action.actionType ?? action.action_type).toLowerCase();
|
|
158
207
|
const targetSequence = actionTargetSequence(action);
|
|
159
|
-
const
|
|
208
|
+
const targetActionId = actionTargetActionId(action);
|
|
209
|
+
const parentLabel = targetSequence ? `#${targetSequence}` : actionTargetCursor(action) || "";
|
|
210
|
+
const targetLabel = targetActionId
|
|
211
|
+
? `action:${targetActionId}${parentLabel ? ` (${parentLabel})` : ""}`
|
|
212
|
+
: parentLabel || "target";
|
|
160
213
|
const note = normalizeString(action.note);
|
|
161
214
|
if (note) return `${actionType} ${targetLabel}: ${note}`;
|
|
162
215
|
return `${actionType} ${targetLabel}`;
|
|
@@ -172,6 +225,7 @@ function buildSessionActionEvent(sessionId, action = {}) {
|
|
|
172
225
|
actionType,
|
|
173
226
|
targetSequenceId: actionTargetSequence(action),
|
|
174
227
|
targetCursor: actionTargetCursor(action),
|
|
228
|
+
targetActionId: actionTargetActionId(action),
|
|
175
229
|
actorId: actionActorId(action),
|
|
176
230
|
note: normalizeString(action.note),
|
|
177
231
|
createdAt: actionCreatedAt(action),
|
|
@@ -192,6 +246,7 @@ function buildSessionActionEvent(sessionId, action = {}) {
|
|
|
192
246
|
actionType,
|
|
193
247
|
targetSequenceId: actionTargetSequence(action),
|
|
194
248
|
targetCursor: actionTargetCursor(action) || null,
|
|
249
|
+
targetActionId: actionTargetActionId(action) || null,
|
|
195
250
|
note: normalizeString(action.note) || null,
|
|
196
251
|
message: actionDisplayMessage(action),
|
|
197
252
|
source: "session_action",
|
|
@@ -259,10 +314,15 @@ function defaultActionIdempotencyKey({
|
|
|
259
314
|
actionType,
|
|
260
315
|
targetSequenceId,
|
|
261
316
|
targetCursor,
|
|
317
|
+
targetActionId,
|
|
262
318
|
note,
|
|
263
319
|
agentId,
|
|
264
320
|
} = {}) {
|
|
265
|
-
const target =
|
|
321
|
+
const target = normalizeString(targetActionId)
|
|
322
|
+
? `action:${normalizeString(targetActionId)}`
|
|
323
|
+
: targetSequenceId
|
|
324
|
+
? `seq:${targetSequenceId}`
|
|
325
|
+
: `cursor:${normalizeString(targetCursor)}`;
|
|
266
326
|
const noteHash = note ? shortSha256(note) : "none";
|
|
267
327
|
const actor = normalizeString(agentId) || "user";
|
|
268
328
|
return `cli:${normalizeString(actionType).toLowerCase()}:${target}:${actor}:${noteHash}`;
|
|
@@ -1768,6 +1828,7 @@ export function registerSessionCommand(program) {
|
|
|
1768
1828
|
commandName = "session action",
|
|
1769
1829
|
targetSequenceId: targetSequenceIdOverride = null,
|
|
1770
1830
|
targetCursor: targetCursorOverride = "",
|
|
1831
|
+
targetActionId: targetActionIdOverride = "",
|
|
1771
1832
|
note: noteOverride = "",
|
|
1772
1833
|
} = {}) {
|
|
1773
1834
|
const normalizedSessionId = normalizeString(sessionId);
|
|
@@ -1780,8 +1841,9 @@ export function registerSessionCommand(program) {
|
|
|
1780
1841
|
targetSequenceIdOverride ||
|
|
1781
1842
|
parseOptionalPositiveInteger(options.targetSequence, "target-sequence");
|
|
1782
1843
|
const targetCursor = normalizeString(targetCursorOverride) || normalizeString(options.targetCursor);
|
|
1783
|
-
|
|
1784
|
-
|
|
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.");
|
|
1785
1847
|
}
|
|
1786
1848
|
await ensureLocalSessionForRemoteCommand(normalizedSessionId, { targetPath });
|
|
1787
1849
|
const note = normalizeString(noteOverride) || normalizeString(options.note);
|
|
@@ -1792,6 +1854,7 @@ export function registerSessionCommand(program) {
|
|
|
1792
1854
|
actionType: normalizedActionType,
|
|
1793
1855
|
targetSequenceId,
|
|
1794
1856
|
targetCursor,
|
|
1857
|
+
targetActionId,
|
|
1795
1858
|
note,
|
|
1796
1859
|
agentId,
|
|
1797
1860
|
});
|
|
@@ -1801,6 +1864,7 @@ export function registerSessionCommand(program) {
|
|
|
1801
1864
|
targetPath,
|
|
1802
1865
|
targetSequenceId,
|
|
1803
1866
|
targetCursor,
|
|
1867
|
+
targetActionId,
|
|
1804
1868
|
note,
|
|
1805
1869
|
metadata: {
|
|
1806
1870
|
source: "cli",
|
|
@@ -1837,11 +1901,37 @@ export function registerSessionCommand(program) {
|
|
|
1837
1901
|
return payload;
|
|
1838
1902
|
}
|
|
1839
1903
|
|
|
1904
|
+
session
|
|
1905
|
+
.command("actions")
|
|
1906
|
+
.description("List supported low-noise message actions with examples")
|
|
1907
|
+
.option("--json", "Emit machine-readable output")
|
|
1908
|
+
.action((options, command) => {
|
|
1909
|
+
const payload = {
|
|
1910
|
+
command: "session actions",
|
|
1911
|
+
actions: SESSION_MESSAGE_ACTION_DESCRIPTIONS,
|
|
1912
|
+
};
|
|
1913
|
+
if (shouldEmitJson(options, command)) {
|
|
1914
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1915
|
+
return payload;
|
|
1916
|
+
}
|
|
1917
|
+
console.log(pc.bold("Supported session message actions"));
|
|
1918
|
+
for (const action of SESSION_MESSAGE_ACTION_DESCRIPTIONS) {
|
|
1919
|
+
const alias = action.alias ? ` (alias: ${action.alias})` : "";
|
|
1920
|
+
console.log(`${pc.cyan(action.type)}${alias}`);
|
|
1921
|
+
console.log(` ${action.description}`);
|
|
1922
|
+
console.log(pc.gray(` ${action.command}`));
|
|
1923
|
+
}
|
|
1924
|
+
return payload;
|
|
1925
|
+
});
|
|
1926
|
+
|
|
1840
1927
|
session
|
|
1841
1928
|
.command("action <sessionId> <actionType>")
|
|
1842
|
-
.description(
|
|
1929
|
+
.description(
|
|
1930
|
+
"Create a message action for a target session event (ack, working_on, reply/comment, like, dislike, disregard, view)",
|
|
1931
|
+
)
|
|
1843
1932
|
.option("--target-sequence <n>", "Target event sequence id")
|
|
1844
1933
|
.option("--target-cursor <cursor>", "Target event cursor")
|
|
1934
|
+
.option("--target-action-id <uuid>", "Target a threaded reply/action by action UUID")
|
|
1845
1935
|
.option("--note <text>", "Optional action note or reply body")
|
|
1846
1936
|
.option("--agent <id>", "Agent id for local idempotency metadata", "cli-user")
|
|
1847
1937
|
.option("--idempotency-key <key>", "Explicit idempotency key")
|
|
@@ -1856,6 +1946,7 @@ export function registerSessionCommand(program) {
|
|
|
1856
1946
|
.description("React to or acknowledge a target session event with ack, like, or dislike")
|
|
1857
1947
|
.option("--target-sequence <n>", "Target event sequence id")
|
|
1858
1948
|
.option("--target-cursor <cursor>", "Target event cursor")
|
|
1949
|
+
.option("--target-action-id <uuid>", "Target a threaded reply/action by action UUID")
|
|
1859
1950
|
.option("--agent <id>", "Agent id for local idempotency metadata", "cli-user")
|
|
1860
1951
|
.option("--idempotency-key <key>", "Explicit idempotency key")
|
|
1861
1952
|
.option("--path <path>", "Workspace path for the session", ".")
|
|
@@ -1894,6 +1985,44 @@ export function registerSessionCommand(program) {
|
|
|
1894
1985
|
});
|
|
1895
1986
|
});
|
|
1896
1987
|
|
|
1988
|
+
session
|
|
1989
|
+
.command("comment <sessionId> <targetSequenceId> <message...>")
|
|
1990
|
+
.description("Alias for `session reply`; add a threaded comment to a target event")
|
|
1991
|
+
.option("--agent <id>", "Agent id for local idempotency metadata", "cli-user")
|
|
1992
|
+
.option("--idempotency-key <key>", "Explicit idempotency key")
|
|
1993
|
+
.option("--path <path>", "Workspace path for the session", ".")
|
|
1994
|
+
.option("--json", "Emit machine-readable output")
|
|
1995
|
+
.action(async (sessionId, targetSequenceId, messageParts, options, command) => {
|
|
1996
|
+
const message = Array.isArray(messageParts) ? messageParts.join(" ") : messageParts;
|
|
1997
|
+
await runMessageActionCommand({
|
|
1998
|
+
sessionId,
|
|
1999
|
+
actionType: "reply",
|
|
2000
|
+
options,
|
|
2001
|
+
command,
|
|
2002
|
+
commandName: "session comment",
|
|
2003
|
+
targetSequenceId: parsePositiveInteger(targetSequenceId, "targetSequenceId", 0),
|
|
2004
|
+
note: message,
|
|
2005
|
+
});
|
|
2006
|
+
});
|
|
2007
|
+
|
|
2008
|
+
session
|
|
2009
|
+
.command("view <sessionId> <targetSequenceId>")
|
|
2010
|
+
.description("Record a read receipt for a target session event")
|
|
2011
|
+
.option("--agent <id>", "Agent id for local idempotency metadata", "cli-user")
|
|
2012
|
+
.option("--idempotency-key <key>", "Explicit idempotency key")
|
|
2013
|
+
.option("--path <path>", "Workspace path for the session", ".")
|
|
2014
|
+
.option("--json", "Emit machine-readable output")
|
|
2015
|
+
.action(async (sessionId, targetSequenceId, options, command) => {
|
|
2016
|
+
await runMessageActionCommand({
|
|
2017
|
+
sessionId,
|
|
2018
|
+
actionType: "view",
|
|
2019
|
+
options,
|
|
2020
|
+
command,
|
|
2021
|
+
commandName: "session view",
|
|
2022
|
+
targetSequenceId: parsePositiveInteger(targetSequenceId, "targetSequenceId", 0),
|
|
2023
|
+
});
|
|
2024
|
+
});
|
|
2025
|
+
|
|
1897
2026
|
session
|
|
1898
2027
|
.command("listen")
|
|
1899
2028
|
.description("Background-poll a session for events addressed to this agent or broadcast")
|
package/src/legacy-cli.js
CHANGED
|
@@ -218,6 +218,12 @@ function printUsage() {
|
|
|
218
218
|
console.log(" sl session say <id> \"assign: @agent <task>\" Create task assignment + lease");
|
|
219
219
|
console.log(" sl session say <id> \"assign: @*:reviewer <task>\" Wildcard route to least-busy role");
|
|
220
220
|
console.log(" sl session say <id> \"accepted: task <task-id>\" / \"done: task <task-id>\" Task transitions");
|
|
221
|
+
console.log(" sl session actions List low-noise actions and examples");
|
|
222
|
+
console.log(" sl session react <id> ack --target-sequence <n> ACK/like/dislike without a new post");
|
|
223
|
+
console.log(" sl session action <id> working_on --target-sequence <n> Claim work on a message");
|
|
224
|
+
console.log(" sl session reply <id> <seq> \"msg\" Thread a response under a message");
|
|
225
|
+
console.log(" sl session comment <id> <seq> \"msg\" Alias for threaded reply");
|
|
226
|
+
console.log(" sl session view <id> <seq> Record a read receipt");
|
|
221
227
|
console.log(" sl session read <id> --tail 20 Read session stream events");
|
|
222
228
|
console.log(" sl session status <id> --json Show session health, agents, runs, leases");
|
|
223
229
|
console.log(" sl session leave <id> Leave a session");
|
|
@@ -6,7 +6,7 @@ export const COORDINATION_ETIQUETTE_ITEMS = Object.freeze([
|
|
|
6
6
|
"Before implementation, post a short plan and file claims with `sl session say <id> \"plan: <scope>; files: <paths>\"`.",
|
|
7
7
|
"Claim shared files before editing with `lock: <file> - <intent>` and release them with `unlock: <file> - done`.",
|
|
8
8
|
"Run a background listener for replies: `sl session listen --session <id> --agent <your-name> --interval 60 --active-interval 5 --emit ndjson`; this idles at 60s and switches to 5s after human activity. If background polling is unavailable, fall back to `sl session sync <id> --json` then `sl session read <id> --tail 20 --json` every 5 minutes.",
|
|
9
|
-
"Use message actions for low-noise coordination: `sl session react <id> ack --target-sequence <n>` for ACKs, `sl session action <id> working_on --target-sequence <n>` for ownership, and `sl session reply <id> <sequence> \"<message>\"`
|
|
9
|
+
"Use message actions for low-noise coordination before posting a new top-level message: `sl session react <id> ack --target-sequence <n>` for ACKs, `sl session action <id> working_on --target-sequence <n>` for ownership, `sl session view <id> <sequence>` for read receipts, and `sl session reply <id> <sequence> \"<message>\"` / `sl session comment <id> <sequence> \"<message>\"` for threaded responses. Run `sl session actions` to list all action types.",
|
|
10
10
|
"Search before asking peers to restate context: `sl session search <id> \"<topic>\" --limit 10`.",
|
|
11
11
|
"Run `sl review --diff` after each finished file or PR-ready diff and post the result summary back to the session.",
|
|
12
12
|
"Post findings through `sl session say <id> \"finding: [P2] <title> in <file>:<line>\"` with enough context for a peer to act.",
|
package/src/session/recap.js
CHANGED
|
@@ -418,7 +418,7 @@ const AGENT_JOIN_RULES = [
|
|
|
418
418
|
"",
|
|
419
419
|
"**Writing back** — You can use **markdown**: bold, italic, lists, fenced code, and `inline code`. The web dashboard renders it. Plain text also works. Keep posts terse and technical — link to the work, don't recap it.",
|
|
420
420
|
"",
|
|
421
|
-
"**Actions and threading** — ACK or claim work with message actions instead of top-level chatter: `sl session react <id> ack --target-sequence <n
|
|
421
|
+
"**Actions and threading** — ACK, view, react, or claim work with message actions instead of top-level chatter: `sl session react <id> ack --target-sequence <n>`, `sl session view <id> <sequence>`, or `sl session action <id> working_on --target-sequence <n>`. Reply to a specific message with `sl session reply <id> <sequence> \"<message>\"`, `sl session comment <id> <sequence> \"<message>\"`, or `sl session say <id> \"<message>\" --reply-to <sequence>`; only start a new top-level post for a new topic. Run `sl session actions` for the full list.",
|
|
422
422
|
"",
|
|
423
423
|
"**Search before asking** — Use `sl session search <id> \"<topic>\" --limit 10` to recover old context before asking another agent to re-paste or summarize what is already in the transcript.",
|
|
424
424
|
"",
|
package/src/session/sync.js
CHANGED
|
@@ -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 =
|