@way_marks/server 4.0.2 → 4.2.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/dist/api/routes/agent-monitor.js +187 -0
- package/dist/api/server.js +13 -0
- package/dist/approvals/handler.js +13 -2
- package/dist/collectors/claude.js +753 -0
- package/dist/collectors/codex.js +516 -0
- package/dist/collectors/copilot.js +373 -0
- package/dist/collectors/multi-collector.js +221 -0
- package/dist/collectors/process.js +257 -0
- package/dist/collectors/rate-limit.js +161 -0
- package/dist/collectors/secrets.js +43 -0
- package/dist/collectors/types.js +47 -0
- package/dist/mcp/server.js +14 -0
- package/dist/mcp/tools/agent-monitor.js +272 -0
- package/package.json +1 -1
- package/src/ui-dist/assets/{index-DNdosrlQ.css → index-BJAvjazt.css} +1 -1
- package/src/ui-dist/assets/index-CcybEu0u.js +87 -0
- package/src/ui-dist/index.html +2 -2
- package/src/ui-dist/assets/index-BEo79vjN.js +0 -87
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Express route handlers for the agent-monitor REST API.
|
|
4
|
+
*
|
|
5
|
+
* Mount in packages/server/src/api/server.ts:
|
|
6
|
+
*
|
|
7
|
+
* import { createAgentMonitorRouter } from './routes/agent-monitor';
|
|
8
|
+
* app.use('/api/agent-monitor', createAgentMonitorRouter(collector));
|
|
9
|
+
*
|
|
10
|
+
* Endpoints:
|
|
11
|
+
* GET /api/agent-monitor/sessions → all sessions (+ filter query params)
|
|
12
|
+
* GET /api/agent-monitor/sessions/:id → single session detail (with toolCalls, fileAccesses)
|
|
13
|
+
* GET /api/agent-monitor/rate-limits → Claude + Codex rate limits
|
|
14
|
+
* GET /api/agent-monitor/ports → agent ports + orphan ports
|
|
15
|
+
* GET /api/agent-monitor/snapshot → full snapshot (sessions + rateLimits + ports)
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.createAgentMonitorRouter = createAgentMonitorRouter;
|
|
19
|
+
const express_1 = require("express");
|
|
20
|
+
// ─── Router factory ───────────────────────────────────────────────────────────
|
|
21
|
+
/**
|
|
22
|
+
* Create an Express router for the agent-monitor endpoints.
|
|
23
|
+
*
|
|
24
|
+
* @param getSnapshot Function that returns the latest CollectorSnapshot.
|
|
25
|
+
* Pass `() => collector.tick()` for on-demand collection,
|
|
26
|
+
* or `() => lastSnapshot` for a cached snapshot from a timer.
|
|
27
|
+
*/
|
|
28
|
+
function createAgentMonitorRouter(getSnapshot) {
|
|
29
|
+
const router = (0, express_1.Router)();
|
|
30
|
+
// ── GET /sessions ──────────────────────────────────────────────────────────
|
|
31
|
+
router.get('/sessions', (_req, res) => {
|
|
32
|
+
const snapshot = getSnapshot();
|
|
33
|
+
let sessions = snapshot.sessions;
|
|
34
|
+
const agent = _req.query['agent'];
|
|
35
|
+
const status = _req.query['status'];
|
|
36
|
+
if (agent && agent !== 'all') {
|
|
37
|
+
sessions = sessions.filter((s) => s.agentCli === agent);
|
|
38
|
+
}
|
|
39
|
+
if (status && status !== 'all') {
|
|
40
|
+
if (status === 'active') {
|
|
41
|
+
sessions = sessions.filter((s) => s.status === 'thinking' || s.status === 'executing');
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
sessions = sessions.filter((s) => s.status === status);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
res.json({
|
|
48
|
+
sessions: sessions.map(sessionSummary),
|
|
49
|
+
count: sessions.length,
|
|
50
|
+
collectedAt: snapshot.collectedAt,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
// ── GET /sessions/:id ──────────────────────────────────────────────────────
|
|
54
|
+
router.get('/sessions/:id', (req, res) => {
|
|
55
|
+
const snapshot = getSnapshot();
|
|
56
|
+
const session = snapshot.sessions.find((s) => s.sessionId === req.params['id']);
|
|
57
|
+
if (!session) {
|
|
58
|
+
res.status(404).json({ error: 'Session not found' });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Full detail including toolCalls and fileAccesses
|
|
62
|
+
res.json({ session: sessionDetail(session), collectedAt: snapshot.collectedAt });
|
|
63
|
+
});
|
|
64
|
+
// ── GET /rate-limits ───────────────────────────────────────────────────────
|
|
65
|
+
router.get('/rate-limits', (_req, res) => {
|
|
66
|
+
const snapshot = getSnapshot();
|
|
67
|
+
res.json({
|
|
68
|
+
rateLimits: snapshot.rateLimits.map((r) => ({
|
|
69
|
+
source: r.source,
|
|
70
|
+
fiveHour: r.fiveHourPct != null ? {
|
|
71
|
+
usedPercent: r.fiveHourPct,
|
|
72
|
+
resetsAt: r.fiveHourResetsAt,
|
|
73
|
+
resetsAtIso: r.fiveHourResetsAt
|
|
74
|
+
? new Date(r.fiveHourResetsAt * 1000).toISOString()
|
|
75
|
+
: undefined,
|
|
76
|
+
} : null,
|
|
77
|
+
sevenDay: r.sevenDayPct != null ? {
|
|
78
|
+
usedPercent: r.sevenDayPct,
|
|
79
|
+
resetsAt: r.sevenDayResetsAt,
|
|
80
|
+
resetsAtIso: r.sevenDayResetsAt
|
|
81
|
+
? new Date(r.sevenDayResetsAt * 1000).toISOString()
|
|
82
|
+
: undefined,
|
|
83
|
+
} : null,
|
|
84
|
+
updatedAt: r.updatedAt,
|
|
85
|
+
updatedAtIso: r.updatedAt
|
|
86
|
+
? new Date(r.updatedAt * 1000).toISOString()
|
|
87
|
+
: undefined,
|
|
88
|
+
})),
|
|
89
|
+
collectedAt: snapshot.collectedAt,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
// ── GET /ports ─────────────────────────────────────────────────────────────
|
|
93
|
+
router.get('/ports', (_req, res) => {
|
|
94
|
+
const snapshot = getSnapshot();
|
|
95
|
+
const agentPorts = [];
|
|
96
|
+
for (const s of snapshot.sessions) {
|
|
97
|
+
for (const child of s.children) {
|
|
98
|
+
if (child.port != null) {
|
|
99
|
+
agentPorts.push({
|
|
100
|
+
sessionId: s.sessionId,
|
|
101
|
+
agentCli: s.agentCli,
|
|
102
|
+
pid: child.pid,
|
|
103
|
+
port: child.port,
|
|
104
|
+
command: child.command,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
res.json({
|
|
110
|
+
agentPorts,
|
|
111
|
+
orphanPorts: snapshot.orphanPorts,
|
|
112
|
+
collectedAt: snapshot.collectedAt,
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
// ── GET /snapshot ──────────────────────────────────────────────────────────
|
|
116
|
+
// Returns the raw `CollectorSnapshot`. Two consumers depend on this exact
|
|
117
|
+
// shape: (1) the MCP process's `fetchSnapshotFromApi()`, whose handlers walk
|
|
118
|
+
// `s.subagents.length`, `s.children`, `s.totalInputTokens`, etc.; (2) the web
|
|
119
|
+
// dashboard's `useAgentSnapshot()` hook, whose `AgentSession` TS type is the
|
|
120
|
+
// raw `collectors/types.ts` shape. Earlier versions ran sessions through
|
|
121
|
+
// `sessionSummary()` here, which collapsed `subagents` → `subagentCount` and
|
|
122
|
+
// `tokens.input` etc. — silently breaking both consumers (the web limped
|
|
123
|
+
// along on `?? 0` guards; the MCP crashed on the missing arrays). The slim
|
|
124
|
+
// summary shape lives on `/sessions` for the CLI's table view.
|
|
125
|
+
router.get('/snapshot', (_req, res) => {
|
|
126
|
+
const snapshot = getSnapshot();
|
|
127
|
+
res.json({
|
|
128
|
+
sessions: snapshot.sessions,
|
|
129
|
+
rateLimits: snapshot.rateLimits,
|
|
130
|
+
orphanPorts: snapshot.orphanPorts,
|
|
131
|
+
collectedAt: snapshot.collectedAt,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
return router;
|
|
135
|
+
}
|
|
136
|
+
function sessionSummary(s) {
|
|
137
|
+
return {
|
|
138
|
+
agentCli: s.agentCli,
|
|
139
|
+
pid: s.pid,
|
|
140
|
+
sessionId: s.sessionId,
|
|
141
|
+
cwd: s.cwd,
|
|
142
|
+
projectName: s.projectName,
|
|
143
|
+
startedAt: s.startedAt,
|
|
144
|
+
startedAtIso: new Date(s.startedAt).toISOString(),
|
|
145
|
+
status: s.status,
|
|
146
|
+
model: s.model,
|
|
147
|
+
effort: s.effort || undefined,
|
|
148
|
+
contextPercent: Math.round(s.contextPercent * 10) / 10,
|
|
149
|
+
contextWindow: s.contextWindow,
|
|
150
|
+
tokens: {
|
|
151
|
+
input: s.totalInputTokens,
|
|
152
|
+
output: s.totalOutputTokens,
|
|
153
|
+
cacheRead: s.totalCacheRead,
|
|
154
|
+
cacheCreate: s.totalCacheCreate,
|
|
155
|
+
total: s.totalInputTokens + s.totalOutputTokens + s.totalCacheRead + s.totalCacheCreate,
|
|
156
|
+
},
|
|
157
|
+
turnCount: s.turnCount,
|
|
158
|
+
currentTasks: s.currentTasks,
|
|
159
|
+
memMb: s.memMb,
|
|
160
|
+
version: s.version || undefined,
|
|
161
|
+
git: {
|
|
162
|
+
branch: s.gitBranch || undefined,
|
|
163
|
+
added: s.gitAdded,
|
|
164
|
+
modified: s.gitModified,
|
|
165
|
+
},
|
|
166
|
+
initialPrompt: s.initialPrompt || undefined,
|
|
167
|
+
subagentCount: s.subagents.length,
|
|
168
|
+
childCount: s.children.length,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function sessionDetail(s) {
|
|
172
|
+
return {
|
|
173
|
+
...sessionSummary(s),
|
|
174
|
+
subagents: s.subagents,
|
|
175
|
+
children: s.children,
|
|
176
|
+
toolCalls: s.toolCalls,
|
|
177
|
+
fileAccesses: s.fileAccesses,
|
|
178
|
+
tokenHistory: s.tokenHistory,
|
|
179
|
+
contextHistory: s.contextHistory,
|
|
180
|
+
compactionCount: s.compactionCount,
|
|
181
|
+
memFileCount: s.memFileCount,
|
|
182
|
+
memLineCount: s.memLineCount,
|
|
183
|
+
pendingSinceMs: s.pendingSinceMs,
|
|
184
|
+
thinkingSinceMs: s.thinkingSinceMs,
|
|
185
|
+
firstAssistantText: s.firstAssistantText || undefined,
|
|
186
|
+
};
|
|
187
|
+
}
|
package/dist/api/server.js
CHANGED
|
@@ -47,6 +47,8 @@ const handler_1 = require("../approvals/handler");
|
|
|
47
47
|
const manager_2 = require("../approval/manager");
|
|
48
48
|
const manager_3 = require("../escalation/manager");
|
|
49
49
|
const events_1 = require("./events");
|
|
50
|
+
const multi_collector_1 = require("../collectors/multi-collector");
|
|
51
|
+
const agent_monitor_1 = require("./routes/agent-monitor");
|
|
50
52
|
// Import registry for Phase 2 hub navigation
|
|
51
53
|
const registryPath = path.join(process.env.HOME || process.env.USERPROFILE || '', '.waymark', 'registry.json');
|
|
52
54
|
function getRegistryProjects() {
|
|
@@ -161,6 +163,17 @@ else {
|
|
|
161
163
|
console.warn('[waymark] ui-dist/ not found — dashboard will return a setup banner. ' +
|
|
162
164
|
'Run `npm run build -w @way_marks/web` to build the dashboard.');
|
|
163
165
|
}
|
|
166
|
+
// Agent monitor — MultiCollector on a 2 s timer (mirrors abtop polling interval).
|
|
167
|
+
// .unref() so the timer doesn't keep the event loop alive on SIGTERM; the API
|
|
168
|
+
// process should exit cleanly via the http server close.
|
|
169
|
+
const agentCollector = new multi_collector_1.MultiCollector();
|
|
170
|
+
let latestAgentSnapshot = agentCollector.tick();
|
|
171
|
+
const agentCollectorTimer = setInterval(() => {
|
|
172
|
+
latestAgentSnapshot = agentCollector.tick();
|
|
173
|
+
}, 2000);
|
|
174
|
+
agentCollectorTimer.unref();
|
|
175
|
+
// Mount agent-monitor REST API
|
|
176
|
+
app.use('/api/agent-monitor', (0, agent_monitor_1.createAgentMonitorRouter)(() => latestAgentSnapshot));
|
|
164
177
|
// GET /api/events — Server-Sent Events stream for live UI updates
|
|
165
178
|
app.get('/api/events', (req, res) => {
|
|
166
179
|
const detach = (0, events_1.attachSubscriber)(res);
|
|
@@ -50,12 +50,23 @@ async function approvePendingAction(actionId, approvedBy = 'ui') {
|
|
|
50
50
|
let after_snapshot;
|
|
51
51
|
if (action.tool_name === 'write_file') {
|
|
52
52
|
const { path: filePath, content } = JSON.parse(action.input_payload);
|
|
53
|
-
|
|
53
|
+
// Resolve against WAYMARK_PROJECT_ROOT, not process.cwd(). The policy engine
|
|
54
|
+
// (loadConfig in policies/engine.ts) uses the same env var; using cwd here
|
|
55
|
+
// would cause a relative target_path to resolve to a different absolute
|
|
56
|
+
// path than the one the policy was originally evaluated against — which
|
|
57
|
+
// would then re-trigger a policy block on approval.
|
|
58
|
+
const projectRoot = process.env.WAYMARK_PROJECT_ROOT || process.cwd();
|
|
59
|
+
const resolvedPath = path.isAbsolute(filePath)
|
|
60
|
+
? path.resolve(filePath)
|
|
61
|
+
: path.resolve(projectRoot, filePath);
|
|
54
62
|
// Re-check current policies — they may have changed since the action was queued
|
|
55
63
|
const currentConfig = (0, engine_1.loadConfig)();
|
|
56
64
|
const recheck = (0, engine_1.checkFileAction)(resolvedPath, 'write', currentConfig);
|
|
57
65
|
if (recheck.decision === 'block') {
|
|
58
|
-
return {
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
error: `Approval blocked: policy changed since action was recorded (${recheck.reason}). Resolved path: ${resolvedPath}`,
|
|
69
|
+
};
|
|
59
70
|
}
|
|
60
71
|
fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
|
|
61
72
|
fs.writeFileSync(resolvedPath, content, 'utf8');
|