codex-agent-view 0.4.4 → 0.4.6
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/.codex-plugin/plugin.json +4 -4
- package/README.ko.md +15 -12
- package/README.md +16 -13
- package/package.json +2 -2
- package/public/app.js +265 -129
- package/public/index.html +14 -12
- package/public/styles.css +106 -1
- package/scripts/send-hook.mjs +10 -3
- package/skills/codex-agent-view/SKILL.md +46 -14
- package/skills/show-agents/SKILL.md +11 -1
- package/src/core/index.mjs +0 -1
- package/src/core/monitor-store.mjs +250 -18
- package/src/core/normalize-hook-payload.mjs +91 -0
|
@@ -5,6 +5,7 @@ const DEFAULT_LIMITS = Object.freeze({
|
|
|
5
5
|
maxAgentsPerSession: 100,
|
|
6
6
|
maxDiagnostics: 100,
|
|
7
7
|
maxSessions: 50,
|
|
8
|
+
staleAfterMs: 5 * 60 * 1000,
|
|
8
9
|
});
|
|
9
10
|
|
|
10
11
|
function positiveInteger(value, name) {
|
|
@@ -19,6 +20,8 @@ function createSession(event) {
|
|
|
19
20
|
session_id: event.session_id,
|
|
20
21
|
workspace_label: null,
|
|
21
22
|
workspace_label_observed_at_ms: null,
|
|
23
|
+
task_summary: null,
|
|
24
|
+
task_summary_observed_at_ms: null,
|
|
22
25
|
first_seen_at_ms: event.received_at_ms,
|
|
23
26
|
last_seen_at_ms: event.received_at_ms,
|
|
24
27
|
agents: new Map(),
|
|
@@ -42,6 +45,22 @@ function createSession(event) {
|
|
|
42
45
|
};
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
function applyTaskSummary(session, event) {
|
|
49
|
+
if (event.type !== "turn_started") {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (
|
|
53
|
+
session.task_summary_observed_at_ms !== null &&
|
|
54
|
+
event.received_at_ms < session.task_summary_observed_at_ms
|
|
55
|
+
) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
session.task_summary_observed_at_ms = event.received_at_ms;
|
|
59
|
+
if (session.task_summary === null && "task_summary" in event) {
|
|
60
|
+
session.task_summary = event.task_summary;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
45
64
|
function applyWorkspaceLabel(session, event) {
|
|
46
65
|
if (!("workspace_label" in event)) {
|
|
47
66
|
return;
|
|
@@ -56,7 +75,30 @@ function applyWorkspaceLabel(session, event) {
|
|
|
56
75
|
session.workspace_label_observed_at_ms = event.received_at_ms;
|
|
57
76
|
}
|
|
58
77
|
|
|
59
|
-
function
|
|
78
|
+
function hasActiveState(session) {
|
|
79
|
+
return (
|
|
80
|
+
session.permission.status === "waiting_for_user" ||
|
|
81
|
+
session.root_turn.status === "running" ||
|
|
82
|
+
[...session.agents.values()].some(({ status }) => status === "running") ||
|
|
83
|
+
[...session.tools.values()].some(({ status }) => status === "running")
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isStaleActiveSession(session, nowMs, staleAfterMs) {
|
|
88
|
+
return (
|
|
89
|
+
!session.lifecycle.end_observed &&
|
|
90
|
+
hasActiveState(session) &&
|
|
91
|
+
nowMs - session.last_seen_at_ms >= staleAfterMs
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function deriveSessionStatus(session, nowMs, staleAfterMs) {
|
|
96
|
+
if (session.lifecycle.end_observed) {
|
|
97
|
+
return "completed";
|
|
98
|
+
}
|
|
99
|
+
if (isStaleActiveSession(session, nowMs, staleAfterMs)) {
|
|
100
|
+
return "completion_not_observed";
|
|
101
|
+
}
|
|
60
102
|
if (session.permission.status === "waiting_for_user") {
|
|
61
103
|
return "waiting_for_user";
|
|
62
104
|
}
|
|
@@ -67,12 +109,92 @@ function deriveSessionStatus(session) {
|
|
|
67
109
|
) {
|
|
68
110
|
return "running";
|
|
69
111
|
}
|
|
70
|
-
if (session.
|
|
112
|
+
if (session.root_turn.status === "completed") {
|
|
71
113
|
return "completed";
|
|
72
114
|
}
|
|
73
115
|
return "observed";
|
|
74
116
|
}
|
|
75
117
|
|
|
118
|
+
function settleRunningState(session, status, options = {}) {
|
|
119
|
+
const turnId = options.turnId;
|
|
120
|
+
const settledAgentIds = new Set();
|
|
121
|
+
const settledToolUseIds = new Set();
|
|
122
|
+
for (const agent of session.agents.values()) {
|
|
123
|
+
if (
|
|
124
|
+
agent.status === "running" &&
|
|
125
|
+
(turnId === undefined || agent.turn_id === turnId)
|
|
126
|
+
) {
|
|
127
|
+
agent.status = status;
|
|
128
|
+
settledAgentIds.add(agent.agent_id);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const tool of session.tools.values()) {
|
|
132
|
+
if (
|
|
133
|
+
tool.status === "running" &&
|
|
134
|
+
(turnId === undefined || tool.turn_id === turnId)
|
|
135
|
+
) {
|
|
136
|
+
tool.status = status;
|
|
137
|
+
settledToolUseIds.add(tool.tool_use_id);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
for (const activity of session.recent_activities) {
|
|
141
|
+
if (
|
|
142
|
+
activity.status === "running" &&
|
|
143
|
+
((activity.type === "subagent_started" &&
|
|
144
|
+
settledAgentIds.has(activity.agent_id)) ||
|
|
145
|
+
(activity.type === "tool_started" &&
|
|
146
|
+
settledToolUseIds.has(activity.tool_use_id)))
|
|
147
|
+
) {
|
|
148
|
+
activity.status = status;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function clearPermission(session, activityStatus) {
|
|
154
|
+
const permission = session.permission;
|
|
155
|
+
if (permission.status !== "waiting_for_user") {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
for (const activity of session.recent_activities) {
|
|
159
|
+
if (
|
|
160
|
+
activity.type === "permission_requested" &&
|
|
161
|
+
activity.status === "waiting_for_user" &&
|
|
162
|
+
activity.turn_id === permission.turn_id &&
|
|
163
|
+
activity.tool_name === permission.tool_name
|
|
164
|
+
) {
|
|
165
|
+
activity.status = activityStatus;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
session.permission = { status: "idle" };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function settleRootTurnActivity(session, status) {
|
|
173
|
+
for (const activity of session.recent_activities) {
|
|
174
|
+
if (
|
|
175
|
+
activity.type === "turn_started" &&
|
|
176
|
+
activity.status === "running" &&
|
|
177
|
+
activity.turn_id === session.root_turn.turn_id
|
|
178
|
+
) {
|
|
179
|
+
activity.status = status;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function resetTransientState(session) {
|
|
186
|
+
session.agents.clear();
|
|
187
|
+
session.tools.clear();
|
|
188
|
+
session.root_turn = {
|
|
189
|
+
status: "idle",
|
|
190
|
+
turn_id: null,
|
|
191
|
+
started_at_ms: null,
|
|
192
|
+
stopped_at_ms: null,
|
|
193
|
+
has_out_of_order_events: false,
|
|
194
|
+
};
|
|
195
|
+
session.permission = { status: "idle" };
|
|
196
|
+
}
|
|
197
|
+
|
|
76
198
|
function touchMapEntry(map, key, value) {
|
|
77
199
|
map.delete(key);
|
|
78
200
|
map.set(key, value);
|
|
@@ -91,7 +213,14 @@ function addActivity(session, event, status, limit) {
|
|
|
91
213
|
received_at_ms: event.received_at_ms,
|
|
92
214
|
};
|
|
93
215
|
|
|
94
|
-
for (const field of [
|
|
216
|
+
for (const field of [
|
|
217
|
+
"turn_id",
|
|
218
|
+
"agent_id",
|
|
219
|
+
"agent_type",
|
|
220
|
+
"tool_name",
|
|
221
|
+
"tool_use_id",
|
|
222
|
+
"session_start_source",
|
|
223
|
+
]) {
|
|
95
224
|
if (field in event) {
|
|
96
225
|
activity[field] = event[field];
|
|
97
226
|
}
|
|
@@ -107,11 +236,35 @@ function applySessionEvent(session, event, limits) {
|
|
|
107
236
|
const lifecycle = session.lifecycle;
|
|
108
237
|
if (event.type === "session_started") {
|
|
109
238
|
const resumedAfterEnd = lifecycle.end_observed;
|
|
239
|
+
const startsNewEpoch =
|
|
240
|
+
resumedAfterEnd ||
|
|
241
|
+
event.session_start_source === "resume" ||
|
|
242
|
+
event.session_start_source === "clear";
|
|
243
|
+
if (event.session_start_source === "compact") {
|
|
244
|
+
if (resumedAfterEnd) {
|
|
245
|
+
return "stale";
|
|
246
|
+
}
|
|
247
|
+
lifecycle.start_observed = true;
|
|
248
|
+
lifecycle.started_at_ms ??= event.received_at_ms;
|
|
249
|
+
addActivity(session, event, "observed", limits.maxActivitiesPerSession);
|
|
250
|
+
return "applied";
|
|
251
|
+
}
|
|
252
|
+
if (lifecycle.start_observed && !startsNewEpoch) {
|
|
253
|
+
return "duplicate";
|
|
254
|
+
}
|
|
255
|
+
if (startsNewEpoch) {
|
|
256
|
+
if (!resumedAfterEnd) {
|
|
257
|
+
settleRootTurnActivity(session, "completion_not_observed");
|
|
258
|
+
clearPermission(session, "completion_not_observed");
|
|
259
|
+
settleRunningState(session, "completion_not_observed");
|
|
260
|
+
}
|
|
261
|
+
resetTransientState(session);
|
|
262
|
+
}
|
|
110
263
|
lifecycle.start_observed = true;
|
|
111
|
-
lifecycle.started_at_ms
|
|
264
|
+
lifecycle.started_at_ms = event.received_at_ms;
|
|
112
265
|
lifecycle.end_observed = false;
|
|
113
266
|
lifecycle.ended_at_ms = null;
|
|
114
|
-
lifecycle.has_out_of_order_events
|
|
267
|
+
lifecycle.has_out_of_order_events = false;
|
|
115
268
|
addActivity(session, event, "observed", limits.maxActivitiesPerSession);
|
|
116
269
|
return "applied";
|
|
117
270
|
}
|
|
@@ -122,9 +275,11 @@ function applySessionEvent(session, event, limits) {
|
|
|
122
275
|
lifecycle.end_observed = true;
|
|
123
276
|
lifecycle.ended_at_ms = event.received_at_ms;
|
|
124
277
|
lifecycle.has_out_of_order_events = !lifecycle.start_observed;
|
|
278
|
+
settleRootTurnActivity(session, "completed");
|
|
125
279
|
session.root_turn.status = "completed";
|
|
126
280
|
session.root_turn.stopped_at_ms ??= event.received_at_ms;
|
|
127
|
-
session
|
|
281
|
+
clearPermission(session, "interrupted");
|
|
282
|
+
settleRunningState(session, "interrupted");
|
|
128
283
|
addActivity(session, event, "completed", limits.maxActivitiesPerSession);
|
|
129
284
|
return "applied";
|
|
130
285
|
}
|
|
@@ -132,9 +287,10 @@ function applySessionEvent(session, event, limits) {
|
|
|
132
287
|
function applyTurnEvent(session, event, limits) {
|
|
133
288
|
const turn = session.root_turn;
|
|
134
289
|
if (event.type === "turn_started") {
|
|
135
|
-
if (turn.turn_id === event.turn_id
|
|
136
|
-
return "duplicate";
|
|
290
|
+
if (turn.turn_id === event.turn_id) {
|
|
291
|
+
return turn.status === "running" ? "duplicate" : "stale";
|
|
137
292
|
}
|
|
293
|
+
settleRunningState(session, "completion_not_observed");
|
|
138
294
|
session.root_turn = {
|
|
139
295
|
status: "running",
|
|
140
296
|
turn_id: event.turn_id,
|
|
@@ -142,7 +298,12 @@ function applyTurnEvent(session, event, limits) {
|
|
|
142
298
|
stopped_at_ms: null,
|
|
143
299
|
has_out_of_order_events: false,
|
|
144
300
|
};
|
|
145
|
-
|
|
301
|
+
if (
|
|
302
|
+
session.permission.status !== "waiting_for_user" ||
|
|
303
|
+
session.permission.turn_id !== event.turn_id
|
|
304
|
+
) {
|
|
305
|
+
clearPermission(session, "completion_not_observed");
|
|
306
|
+
}
|
|
146
307
|
addActivity(session, event, "running", limits.maxActivitiesPerSession);
|
|
147
308
|
return "applied";
|
|
148
309
|
}
|
|
@@ -151,6 +312,9 @@ function applyTurnEvent(session, event, limits) {
|
|
|
151
312
|
return "duplicate";
|
|
152
313
|
}
|
|
153
314
|
const startObserved = turn.turn_id === event.turn_id && turn.started_at_ms !== null;
|
|
315
|
+
if (startObserved) {
|
|
316
|
+
settleRootTurnActivity(session, "completed");
|
|
317
|
+
}
|
|
154
318
|
session.root_turn = {
|
|
155
319
|
status: "completed",
|
|
156
320
|
turn_id: event.turn_id,
|
|
@@ -158,7 +322,15 @@ function applyTurnEvent(session, event, limits) {
|
|
|
158
322
|
stopped_at_ms: event.received_at_ms,
|
|
159
323
|
has_out_of_order_events: !startObserved,
|
|
160
324
|
};
|
|
161
|
-
|
|
325
|
+
if (
|
|
326
|
+
session.permission.status === "waiting_for_user" &&
|
|
327
|
+
session.permission.turn_id === event.turn_id
|
|
328
|
+
) {
|
|
329
|
+
clearPermission(session, "completion_not_observed");
|
|
330
|
+
}
|
|
331
|
+
settleRunningState(session, "completion_not_observed", {
|
|
332
|
+
turnId: event.turn_id,
|
|
333
|
+
});
|
|
162
334
|
addActivity(
|
|
163
335
|
session,
|
|
164
336
|
event,
|
|
@@ -174,6 +346,7 @@ function applySubagentEvent(session, event, limits) {
|
|
|
174
346
|
agent = {
|
|
175
347
|
agent_id: event.agent_id,
|
|
176
348
|
agent_type: event.agent_type,
|
|
349
|
+
turn_id: event.turn_id,
|
|
177
350
|
status: "unknown",
|
|
178
351
|
started_at_ms: null,
|
|
179
352
|
stopped_at_ms: null,
|
|
@@ -207,6 +380,7 @@ function applySubagentEvent(session, event, limits) {
|
|
|
207
380
|
}
|
|
208
381
|
|
|
209
382
|
agent.agent_type = event.agent_type;
|
|
383
|
+
agent.turn_id = event.turn_id;
|
|
210
384
|
agent.last_seen_at_ms = Math.max(agent.last_seen_at_ms, event.received_at_ms);
|
|
211
385
|
touchMapEntry(session.agents, event.agent_id, agent);
|
|
212
386
|
trimMap(session.agents, limits.maxAgentsPerSession);
|
|
@@ -264,7 +438,7 @@ function applyToolEvent(session, event, limits) {
|
|
|
264
438
|
permission.turn_id === event.turn_id &&
|
|
265
439
|
event.received_at_ms >= permission.requested_at_ms
|
|
266
440
|
) {
|
|
267
|
-
session
|
|
441
|
+
clearPermission(session, "completed");
|
|
268
442
|
}
|
|
269
443
|
}
|
|
270
444
|
|
|
@@ -310,9 +484,29 @@ function applyPermissionEvent(session, event, limits) {
|
|
|
310
484
|
}
|
|
311
485
|
|
|
312
486
|
function applyEvent(session, event, limits) {
|
|
313
|
-
if (event.type === "session_started"
|
|
487
|
+
if (event.type === "session_started") {
|
|
488
|
+
return applySessionEvent(session, event, limits);
|
|
489
|
+
}
|
|
490
|
+
if (
|
|
491
|
+
session.lifecycle.end_observed &&
|
|
492
|
+
event.type !== "tool_completed" &&
|
|
493
|
+
event.type !== "subagent_stopped"
|
|
494
|
+
) {
|
|
495
|
+
return event.type === "session_ended" ? "duplicate" : "stale";
|
|
496
|
+
}
|
|
497
|
+
if (event.type === "session_ended") {
|
|
314
498
|
return applySessionEvent(session, event, limits);
|
|
315
499
|
}
|
|
500
|
+
if (
|
|
501
|
+
session.root_turn.status === "completed" &&
|
|
502
|
+
session.root_turn.turn_id === event.turn_id &&
|
|
503
|
+
(event.type === "turn_started" ||
|
|
504
|
+
event.type === "permission_requested" ||
|
|
505
|
+
event.type === "tool_started" ||
|
|
506
|
+
event.type === "subagent_started")
|
|
507
|
+
) {
|
|
508
|
+
return "stale";
|
|
509
|
+
}
|
|
316
510
|
if (event.type === "turn_started" || event.type === "turn_stopped") {
|
|
317
511
|
return applyTurnEvent(session, event, limits);
|
|
318
512
|
}
|
|
@@ -325,21 +519,51 @@ function applyEvent(session, event, limits) {
|
|
|
325
519
|
return applyPermissionEvent(session, event, limits);
|
|
326
520
|
}
|
|
327
521
|
|
|
328
|
-
function snapshotSession(session) {
|
|
522
|
+
function snapshotSession(session, nowMs, staleAfterMs) {
|
|
523
|
+
const staleActive = isStaleActiveSession(session, nowMs, staleAfterMs);
|
|
329
524
|
return {
|
|
330
525
|
session_id: session.session_id,
|
|
331
526
|
workspace_label: session.workspace_label,
|
|
332
|
-
|
|
527
|
+
task_summary: session.task_summary,
|
|
528
|
+
status: deriveSessionStatus(session, nowMs, staleAfterMs),
|
|
333
529
|
first_seen_at_ms: session.first_seen_at_ms,
|
|
334
530
|
last_seen_at_ms: session.last_seen_at_ms,
|
|
335
531
|
agents: [...session.agents.values()]
|
|
336
|
-
.map(({ start_observed, stop_observed, ...agent }) => ({
|
|
532
|
+
.map(({ start_observed, stop_observed, ...agent }) => ({
|
|
533
|
+
...agent,
|
|
534
|
+
...(staleActive && agent.status === "running"
|
|
535
|
+
? { status: "completion_not_observed" }
|
|
536
|
+
: {}),
|
|
537
|
+
}))
|
|
337
538
|
.sort((left, right) => right.last_seen_at_ms - left.last_seen_at_ms),
|
|
338
|
-
|
|
539
|
+
tools: [...session.tools.values()]
|
|
540
|
+
.map(({ start_observed, completion_observed, ...tool }) => ({
|
|
541
|
+
...tool,
|
|
542
|
+
...(staleActive && tool.status === "running"
|
|
543
|
+
? { status: "completion_not_observed" }
|
|
544
|
+
: {}),
|
|
545
|
+
}))
|
|
546
|
+
.sort((left, right) => right.last_seen_at_ms - left.last_seen_at_ms),
|
|
547
|
+
root_turn: {
|
|
548
|
+
...session.root_turn,
|
|
549
|
+
...(staleActive && session.root_turn.status === "running"
|
|
550
|
+
? { status: "completion_not_observed" }
|
|
551
|
+
: {}),
|
|
552
|
+
},
|
|
339
553
|
recent_activities: session.recent_activities.map((activity) => ({
|
|
340
554
|
...activity,
|
|
555
|
+
...(staleActive &&
|
|
556
|
+
(activity.status === "running" ||
|
|
557
|
+
activity.status === "waiting_for_user")
|
|
558
|
+
? { status: "completion_not_observed" }
|
|
559
|
+
: {}),
|
|
341
560
|
})),
|
|
342
|
-
permission: {
|
|
561
|
+
permission: {
|
|
562
|
+
...session.permission,
|
|
563
|
+
...(staleActive && session.permission.status === "waiting_for_user"
|
|
564
|
+
? { status: "completion_not_observed" }
|
|
565
|
+
: {}),
|
|
566
|
+
},
|
|
343
567
|
};
|
|
344
568
|
}
|
|
345
569
|
|
|
@@ -362,6 +586,10 @@ export function createMonitorStore(options = {}) {
|
|
|
362
586
|
options.maxSessions ?? DEFAULT_LIMITS.maxSessions,
|
|
363
587
|
"maxSessions",
|
|
364
588
|
),
|
|
589
|
+
staleAfterMs: positiveInteger(
|
|
590
|
+
options.staleAfterMs ?? DEFAULT_LIMITS.staleAfterMs,
|
|
591
|
+
"staleAfterMs",
|
|
592
|
+
),
|
|
365
593
|
};
|
|
366
594
|
const now = options.now ?? Date.now;
|
|
367
595
|
if (typeof now !== "function") {
|
|
@@ -409,6 +637,7 @@ export function createMonitorStore(options = {}) {
|
|
|
409
637
|
}
|
|
410
638
|
|
|
411
639
|
applyWorkspaceLabel(session, event);
|
|
640
|
+
applyTaskSummary(session, event);
|
|
412
641
|
|
|
413
642
|
session.first_seen_at_ms = Math.min(
|
|
414
643
|
session.first_seen_at_ms,
|
|
@@ -425,12 +654,15 @@ export function createMonitorStore(options = {}) {
|
|
|
425
654
|
}
|
|
426
655
|
|
|
427
656
|
function getSnapshot() {
|
|
657
|
+
const snapshotAtMs = now();
|
|
428
658
|
return {
|
|
429
659
|
schema_version: 1,
|
|
430
660
|
source_of_truth: "hook",
|
|
431
661
|
updated_at_ms: updatedAtMs,
|
|
432
662
|
sessions: [...sessions.values()]
|
|
433
|
-
.map(
|
|
663
|
+
.map((session) =>
|
|
664
|
+
snapshotSession(session, snapshotAtMs, limits.staleAfterMs),
|
|
665
|
+
)
|
|
434
666
|
.sort((left, right) => right.last_seen_at_ms - left.last_seen_at_ms),
|
|
435
667
|
diagnostics: diagnostics.map((diagnostic) => ({ ...diagnostic })),
|
|
436
668
|
};
|
|
@@ -11,11 +11,38 @@ const NORMALIZED_EVENT_TYPES = Object.freeze({
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
const SESSION_EVENT_TYPES = new Set(["session_started", "session_ended"]);
|
|
14
|
+
const SESSION_START_SOURCES = new Set([
|
|
15
|
+
"startup",
|
|
16
|
+
"resume",
|
|
17
|
+
"clear",
|
|
18
|
+
"compact",
|
|
19
|
+
]);
|
|
14
20
|
|
|
15
21
|
const MAX_IDENTIFIER_LENGTH = 512;
|
|
16
22
|
const MAX_LABEL_LENGTH = 256;
|
|
17
23
|
const MAX_WORKSPACE_LABEL_LENGTH = 120;
|
|
24
|
+
const MAX_PROMPT_INSPECTION_LENGTH = 4_096;
|
|
25
|
+
const MAX_TASK_SUMMARY_LENGTH = 180;
|
|
18
26
|
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/;
|
|
27
|
+
const CONTROL_CHARACTERS_GLOBAL = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
28
|
+
|
|
29
|
+
const URL =
|
|
30
|
+
/\b[A-Z][A-Z0-9+.-]*:\/\/[^\s<>"'`]+|\bwww\.[^\s<>"'`]+/giu;
|
|
31
|
+
const EMAIL = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/giu;
|
|
32
|
+
const LABELED_CREDENTIAL =
|
|
33
|
+
/\b(?:[A-Z0-9]+[_-])*(?:api[_-]?key|access[_-]?token|auth(?:orization)?|bearer|credential|key|password|passwd|private[_-]?key|refresh[_-]?token|secret|token)\b\s*(?:=|:)\s*(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s,;]+)/giu;
|
|
34
|
+
const BEARER_CREDENTIAL = /\bBearer\s+[A-Za-z0-9._~+\/-]+=*/giu;
|
|
35
|
+
const PREFIXED_SECRET =
|
|
36
|
+
/\b(?:AKIA[0-9A-Z]{16}|AIza[A-Za-z0-9_-]{20,}|github_pat_[A-Za-z0-9_]{12,}|gh[pousr]_[A-Za-z0-9_]{12,}|glpat-[A-Za-z0-9_-]{12,}|npm_[A-Za-z0-9_]{12,}|sk-[A-Za-z0-9_-]{12,}|(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{12,}|whsec_[A-Za-z0-9]{12,}|xox[baprs]-[A-Za-z0-9-]{12,})\b/gu;
|
|
37
|
+
const JWT = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu;
|
|
38
|
+
const PRIVATE_KEY_BLOCK =
|
|
39
|
+
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY-----|$)/giu;
|
|
40
|
+
const WINDOWS_ABSOLUTE_PATH =
|
|
41
|
+
/(^|[\s("'`=:[{])[A-Za-z]:[\\/][^\s<>"'`)\]},;]*/gu;
|
|
42
|
+
const UNC_ABSOLUTE_PATH =
|
|
43
|
+
/(^|[\s("'`=:[{])\\\\[^\s<>"'`)\]},;]+(?:\\[^\s<>"'`)\]},;]+)+/gu;
|
|
44
|
+
const POSIX_ABSOLUTE_PATH =
|
|
45
|
+
/(^|[\s("'`=:[{])\/(?!\/)[^\s<>"'`)\]},;]*(?:\/[^\s<>"'`)\]},;]+)*/gu;
|
|
19
46
|
|
|
20
47
|
function isObject(value) {
|
|
21
48
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -86,6 +113,52 @@ function optionalWorkspaceLabel(payload) {
|
|
|
86
113
|
return label;
|
|
87
114
|
}
|
|
88
115
|
|
|
116
|
+
function replaceAbsolutePaths(value) {
|
|
117
|
+
return value
|
|
118
|
+
.replace(UNC_ABSOLUTE_PATH, (_match, prefix) => `${prefix}[path]`)
|
|
119
|
+
.replace(WINDOWS_ABSOLUTE_PATH, (_match, prefix) => `${prefix}[path]`)
|
|
120
|
+
.replace(POSIX_ABSOLUTE_PATH, (_match, prefix) => `${prefix}[path]`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Derive a short, display-safe hint from an untrusted UserPromptSubmit prompt.
|
|
125
|
+
* The caller must discard the raw prompt after this synchronous derivation.
|
|
126
|
+
*/
|
|
127
|
+
export function deriveTaskSummary(value) {
|
|
128
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let summary = value
|
|
133
|
+
.slice(0, MAX_PROMPT_INSPECTION_LENGTH)
|
|
134
|
+
.replace(PRIVATE_KEY_BLOCK, "[credential]")
|
|
135
|
+
.replace(CONTROL_CHARACTERS_GLOBAL, " ")
|
|
136
|
+
.replace(URL, "[link]")
|
|
137
|
+
.replace(EMAIL, "[email]")
|
|
138
|
+
.replace(LABELED_CREDENTIAL, "[credential]")
|
|
139
|
+
.replace(BEARER_CREDENTIAL, "[credential]")
|
|
140
|
+
.replace(PREFIXED_SECRET, "[credential]")
|
|
141
|
+
.replace(JWT, "[credential]");
|
|
142
|
+
summary = replaceAbsolutePaths(summary).replace(/\s+/gu, " ").trim();
|
|
143
|
+
|
|
144
|
+
if (!summary || !summary.replace(/\[(?:credential|email|link|path)\]/gu, "").trim()) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const characters = Array.from(summary);
|
|
149
|
+
if (characters.length <= MAX_TASK_SUMMARY_LENGTH) {
|
|
150
|
+
return summary;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const bounded = characters
|
|
154
|
+
.slice(0, MAX_TASK_SUMMARY_LENGTH - 1)
|
|
155
|
+
.join("")
|
|
156
|
+
.trimEnd();
|
|
157
|
+
const lastSpace = bounded.lastIndexOf(" ");
|
|
158
|
+
const readableBoundary = lastSpace >= Math.floor(MAX_TASK_SUMMARY_LENGTH * 0.6);
|
|
159
|
+
return `${readableBoundary ? bounded.slice(0, lastSpace) : bounded}…`;
|
|
160
|
+
}
|
|
161
|
+
|
|
89
162
|
/**
|
|
90
163
|
* Validate an untrusted Codex hook payload and retain only monitor-safe fields.
|
|
91
164
|
* Raw prompts, tool input/output, paths, and assistant messages are never copied.
|
|
@@ -122,11 +195,29 @@ export function normalizeHookPayload(payload, options = {}) {
|
|
|
122
195
|
}
|
|
123
196
|
|
|
124
197
|
const event = commonEvent(payload, type, receivedAtMs);
|
|
198
|
+
if (
|
|
199
|
+
type === "session_started" &&
|
|
200
|
+
typeof payload.source === "string" &&
|
|
201
|
+
SESSION_START_SOURCES.has(payload.source)
|
|
202
|
+
) {
|
|
203
|
+
event.session_start_source = payload.source;
|
|
204
|
+
}
|
|
125
205
|
const workspaceLabel = optionalWorkspaceLabel(payload);
|
|
126
206
|
if (workspaceLabel) {
|
|
127
207
|
event.workspace_label = workspaceLabel;
|
|
128
208
|
}
|
|
129
209
|
|
|
210
|
+
if (type === "turn_started") {
|
|
211
|
+
const taskSummary = deriveTaskSummary(
|
|
212
|
+
typeof payload.task_summary === "string"
|
|
213
|
+
? payload.task_summary
|
|
214
|
+
: payload.prompt,
|
|
215
|
+
);
|
|
216
|
+
if (taskSummary) {
|
|
217
|
+
event.task_summary = taskSummary;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
130
221
|
if (type === "subagent_started" || type === "subagent_stopped") {
|
|
131
222
|
for (const [field, maxLength] of [
|
|
132
223
|
["agent_id", MAX_IDENTIFIER_LENGTH],
|