@damaall/ccx 0.1.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/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/commands/report.d.ts +8 -0
- package/dist/commands/report.d.ts.map +1 -0
- package/dist/commands/report.js +115 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/reuse.d.ts +7 -0
- package/dist/commands/reuse.d.ts.map +1 -0
- package/dist/commands/reuse.js +167 -0
- package/dist/commands/reuse.js.map +1 -0
- package/dist/commands/watch.d.ts +10 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +201 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/core/cursor-reader.d.ts +24 -0
- package/dist/core/cursor-reader.d.ts.map +1 -0
- package/dist/core/cursor-reader.js +128 -0
- package/dist/core/cursor-reader.js.map +1 -0
- package/dist/core/data-source-adapter.d.ts +239 -0
- package/dist/core/data-source-adapter.d.ts.map +1 -0
- package/dist/core/data-source-adapter.js +85 -0
- package/dist/core/data-source-adapter.js.map +1 -0
- package/dist/core/identity-resolver.d.ts +27 -0
- package/dist/core/identity-resolver.d.ts.map +1 -0
- package/dist/core/identity-resolver.js +55 -0
- package/dist/core/identity-resolver.js.map +1 -0
- package/dist/core/inbox-reader.d.ts +20 -0
- package/dist/core/inbox-reader.d.ts.map +1 -0
- package/dist/core/inbox-reader.js +105 -0
- package/dist/core/inbox-reader.js.map +1 -0
- package/dist/core/paths.d.ts +28 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +46 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/pricing.d.ts +25 -0
- package/dist/core/pricing.d.ts.map +1 -0
- package/dist/core/pricing.js +108 -0
- package/dist/core/pricing.js.map +1 -0
- package/dist/core/redact.d.ts +15 -0
- package/dist/core/redact.d.ts.map +1 -0
- package/dist/core/redact.js +60 -0
- package/dist/core/redact.js.map +1 -0
- package/dist/core/session-discovery.d.ts +26 -0
- package/dist/core/session-discovery.d.ts.map +1 -0
- package/dist/core/session-discovery.js +89 -0
- package/dist/core/session-discovery.js.map +1 -0
- package/dist/core/snapshot-manager.d.ts +29 -0
- package/dist/core/snapshot-manager.d.ts.map +1 -0
- package/dist/core/snapshot-manager.js +120 -0
- package/dist/core/snapshot-manager.js.map +1 -0
- package/dist/core/snapshot-reader.d.ts +23 -0
- package/dist/core/snapshot-reader.d.ts.map +1 -0
- package/dist/core/snapshot-reader.js +142 -0
- package/dist/core/snapshot-reader.js.map +1 -0
- package/dist/core/state-aggregator.d.ts +36 -0
- package/dist/core/state-aggregator.d.ts.map +1 -0
- package/dist/core/state-aggregator.js +218 -0
- package/dist/core/state-aggregator.js.map +1 -0
- package/dist/core/task-reader.d.ts +14 -0
- package/dist/core/task-reader.d.ts.map +1 -0
- package/dist/core/task-reader.js +55 -0
- package/dist/core/task-reader.js.map +1 -0
- package/dist/core/team-reader.d.ts +18 -0
- package/dist/core/team-reader.d.ts.map +1 -0
- package/dist/core/team-reader.js +68 -0
- package/dist/core/team-reader.js.map +1 -0
- package/dist/core/types.d.ts +105 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/watcher.d.ts +16 -0
- package/dist/core/watcher.d.ts.map +1 -0
- package/dist/core/watcher.js +194 -0
- package/dist/core/watcher.js.map +1 -0
- package/dist/guard/hard-limit.d.ts +13 -0
- package/dist/guard/hard-limit.d.ts.map +1 -0
- package/dist/guard/hard-limit.js +79 -0
- package/dist/guard/hard-limit.js.map +1 -0
- package/dist/guard/soft-limit.d.ts +5 -0
- package/dist/guard/soft-limit.d.ts.map +1 -0
- package/dist/guard/soft-limit.js +22 -0
- package/dist/guard/soft-limit.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +171 -0
- package/dist/index.js.map +1 -0
- package/dist/report/json.d.ts +6 -0
- package/dist/report/json.d.ts.map +1 -0
- package/dist/report/json.js +4 -0
- package/dist/report/json.js.map +1 -0
- package/dist/report/markdown.d.ts +6 -0
- package/dist/report/markdown.d.ts.map +1 -0
- package/dist/report/markdown.js +57 -0
- package/dist/report/markdown.js.map +1 -0
- package/dist/report/redact-snapshot.d.ts +3 -0
- package/dist/report/redact-snapshot.d.ts.map +1 -0
- package/dist/report/redact-snapshot.js +21 -0
- package/dist/report/redact-snapshot.js.map +1 -0
- package/dist/report/terminal.d.ts +3 -0
- package/dist/report/terminal.d.ts.map +1 -0
- package/dist/report/terminal.js +85 -0
- package/dist/report/terminal.js.map +1 -0
- package/dist/ui/AgentPanel.d.ts +13 -0
- package/dist/ui/AgentPanel.d.ts.map +1 -0
- package/dist/ui/AgentPanel.js +61 -0
- package/dist/ui/AgentPanel.js.map +1 -0
- package/dist/ui/AlertBanner.d.ts +11 -0
- package/dist/ui/AlertBanner.d.ts.map +1 -0
- package/dist/ui/AlertBanner.js +19 -0
- package/dist/ui/AlertBanner.js.map +1 -0
- package/dist/ui/CompletionBanner.d.ts +12 -0
- package/dist/ui/CompletionBanner.d.ts.map +1 -0
- package/dist/ui/CompletionBanner.js +11 -0
- package/dist/ui/CompletionBanner.js.map +1 -0
- package/dist/ui/CostBar.d.ts +12 -0
- package/dist/ui/CostBar.d.ts.map +1 -0
- package/dist/ui/CostBar.js +29 -0
- package/dist/ui/CostBar.js.map +1 -0
- package/dist/ui/Dashboard.d.ts +10 -0
- package/dist/ui/Dashboard.d.ts.map +1 -0
- package/dist/ui/Dashboard.js +70 -0
- package/dist/ui/Dashboard.js.map +1 -0
- package/dist/ui/TaskPanel.d.ts +11 -0
- package/dist/ui/TaskPanel.d.ts.map +1 -0
- package/dist/ui/TaskPanel.js +41 -0
- package/dist/ui/TaskPanel.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* watch command:ink Dashboard UI
|
|
3
|
+
*
|
|
4
|
+
* --plain 回退到純文字模式(Day 1 的實作)
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { render } from 'ink';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { startWatch } from '../core/watcher.js';
|
|
10
|
+
import { listTeamNames } from '../core/team-reader.js';
|
|
11
|
+
import { Dashboard } from '../ui/Dashboard.js';
|
|
12
|
+
import { sendOsNotification } from '../guard/soft-limit.js';
|
|
13
|
+
import { killAllMembers } from '../guard/hard-limit.js';
|
|
14
|
+
import { formatCost, formatTokens, totalTokenCount, formatDuration, formatTimeSince } from '../core/pricing.js';
|
|
15
|
+
export async function runWatch(options) {
|
|
16
|
+
const teamName = options.team ?? await autoDetectTeam();
|
|
17
|
+
if (!teamName) {
|
|
18
|
+
console.error(chalk.red('No active team found.'));
|
|
19
|
+
console.error('Usage: ccx watch <team-name>');
|
|
20
|
+
console.error('Run "ccx ls --active" to see active teams.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
// --kill 啟動確認
|
|
24
|
+
if (options.kill && options.budget) {
|
|
25
|
+
console.log(chalk.yellow.bold(`⚠ Hard Limit enabled.`));
|
|
26
|
+
console.log(chalk.yellow(` When cost exceeds $${options.budget}, ccx will send C-c to agent tmux panes.`));
|
|
27
|
+
console.log(chalk.yellow(` This may cause unfinished work to be lost.`));
|
|
28
|
+
console.log();
|
|
29
|
+
}
|
|
30
|
+
let handle;
|
|
31
|
+
try {
|
|
32
|
+
handle = await startWatch({
|
|
33
|
+
teamName,
|
|
34
|
+
budget: options.budget,
|
|
35
|
+
stuckTimeoutMs: options.stuckTimeout ? options.stuckTimeout * 1000 : undefined,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
console.error(chalk.red(err instanceof Error ? err.message : 'Failed to start watch'));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
// Guard: OS notification + hard limit
|
|
43
|
+
if (options.notify || options.kill) {
|
|
44
|
+
handle.aggregator.on('event', async (event) => {
|
|
45
|
+
if (event.type === 'cost_threshold') {
|
|
46
|
+
if (options.notify) {
|
|
47
|
+
await sendOsNotification('ccx budget alert', `${teamName}: ${formatCost(event.current)} / ${formatCost(event.budget)}`);
|
|
48
|
+
}
|
|
49
|
+
if (options.kill && event.current >= event.budget) {
|
|
50
|
+
const state = handle.aggregator.getState();
|
|
51
|
+
const paneIds = getPaneIds(state);
|
|
52
|
+
if (paneIds.length > 0) {
|
|
53
|
+
await killAllMembers(paneIds);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (options.plain) {
|
|
60
|
+
// ─── Plain text mode ───
|
|
61
|
+
let shuttingDown = false;
|
|
62
|
+
const gracefulShutdown = async () => {
|
|
63
|
+
if (shuttingDown) {
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
shuttingDown = true;
|
|
67
|
+
try {
|
|
68
|
+
await handle.stop();
|
|
69
|
+
console.log(chalk.dim(`\nSaved to: ${handle.snapshotManager.sessionPath}`));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
console.error(chalk.dim('\nSnapshot save failed.'));
|
|
73
|
+
}
|
|
74
|
+
process.exit(0);
|
|
75
|
+
};
|
|
76
|
+
process.on('SIGINT', gracefulShutdown);
|
|
77
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
78
|
+
runPlainMode(handle);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// ─── ink mode ───
|
|
82
|
+
// 需要 TTY 支援 raw mode,否則 fallback 到 plain mode
|
|
83
|
+
if (!process.stdin.isTTY) {
|
|
84
|
+
console.error(chalk.yellow('stdin is not a TTY — falling back to --plain mode.'));
|
|
85
|
+
let shuttingDown = false;
|
|
86
|
+
const gracefulShutdown = async () => {
|
|
87
|
+
if (shuttingDown) {
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
shuttingDown = true;
|
|
91
|
+
try {
|
|
92
|
+
await handle.stop();
|
|
93
|
+
console.log(chalk.dim(`\nSaved to: ${handle.snapshotManager.sessionPath}`));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
console.error(chalk.dim('\nSnapshot save failed.'));
|
|
97
|
+
}
|
|
98
|
+
process.exit(0);
|
|
99
|
+
};
|
|
100
|
+
process.on('SIGINT', gracefulShutdown);
|
|
101
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
102
|
+
runPlainMode(handle);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// ink 在 raw mode 下攔截 Ctrl+C(作為 \x03 byte),自動呼叫 exit()
|
|
106
|
+
// 按 q / Escape 也會在 Dashboard 內呼叫 exit()
|
|
107
|
+
// waitUntilExit() 在 ink 退出後 resolve → 清理 → process.exit
|
|
108
|
+
const instance = render(React.createElement(Dashboard, {
|
|
109
|
+
aggregator: handle.aggregator,
|
|
110
|
+
budget: options.budget ?? null,
|
|
111
|
+
hardLimit: options.kill ?? false,
|
|
112
|
+
sessionPath: handle.snapshotManager.sessionPath,
|
|
113
|
+
}));
|
|
114
|
+
// SIGTERM(kill 指令)→ 觸發 ink unmount
|
|
115
|
+
process.on('SIGTERM', () => instance.unmount());
|
|
116
|
+
await instance.waitUntilExit();
|
|
117
|
+
// ink 已釋放 raw mode,Ctrl+C 恢復為 SIGINT
|
|
118
|
+
// 如果清理過程中使用者按 Ctrl+C → force exit
|
|
119
|
+
process.on('SIGINT', () => process.exit(1));
|
|
120
|
+
try {
|
|
121
|
+
await handle.stop();
|
|
122
|
+
console.log(chalk.dim(`Saved to: ${handle.snapshotManager.sessionPath}`));
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
console.error(chalk.dim('Snapshot save failed.'));
|
|
126
|
+
}
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ─── Plain text mode (Day 1 fallback) ───
|
|
131
|
+
function runPlainMode(handle) {
|
|
132
|
+
const renderPlain = () => {
|
|
133
|
+
const state = handle.aggregator.getState();
|
|
134
|
+
process.stdout.write('\x1B[2J\x1B[H');
|
|
135
|
+
process.stdout.write(formatPlainDashboard(state));
|
|
136
|
+
};
|
|
137
|
+
renderPlain();
|
|
138
|
+
setInterval(renderPlain, 2000);
|
|
139
|
+
handle.aggregator.on('event', async (event) => {
|
|
140
|
+
if (event.type === 'team_deleted') {
|
|
141
|
+
console.log(chalk.yellow(`\nTeam deleted. Snapshot saved to: ${handle.snapshotManager.sessionPath}`));
|
|
142
|
+
await handle.stop();
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
if (event.type === 'watcher_error') {
|
|
146
|
+
console.error(chalk.dim(`[watcher] ${event.source}: ${event.message}`));
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function formatPlainDashboard(state) {
|
|
151
|
+
const lines = [];
|
|
152
|
+
const elapsed = formatDuration(state.elapsedMs);
|
|
153
|
+
lines.push(`ccx watch: ${state.teamName} (${elapsed}) Cost: ${formatCost(state.totalCost)}`);
|
|
154
|
+
lines.push('─'.repeat(72));
|
|
155
|
+
lines.push(`${'AGENTS'.padEnd(30)}${'STATUS'.padEnd(12)}${'ACTIVE'.padEnd(10)}${'TOKENS'.padEnd(10)}COST`);
|
|
156
|
+
for (let i = 0; i < state.agents.length; i++) {
|
|
157
|
+
const a = state.agents[i];
|
|
158
|
+
const prefix = i === state.agents.length - 1 ? '└─' : '├─';
|
|
159
|
+
const name = `${a.name} (${a.model})`.slice(0, 26);
|
|
160
|
+
const active = a.lastActivityAt > 0
|
|
161
|
+
? formatTimeSince(a.lastActivityAt) : '──';
|
|
162
|
+
const tokens = formatTokens(totalTokenCount(a.tokenUsage));
|
|
163
|
+
lines.push(`${prefix} ${name.padEnd(28)}${a.status.padEnd(12)}${active.padEnd(10)}${tokens.padEnd(10)}${formatCost(a.cost)}`);
|
|
164
|
+
}
|
|
165
|
+
lines.push(`${''.padEnd(30)}${'TOTAL'.padEnd(12)}${''.padEnd(10)}${formatTokens(totalTokenCount(state.totalTokens)).padEnd(10)}${formatCost(state.totalCost)}`);
|
|
166
|
+
lines.push('');
|
|
167
|
+
lines.push('TASKS');
|
|
168
|
+
for (const t of state.tasks) {
|
|
169
|
+
const status = t.status === 'in_progress' ? '[working]' : t.status === 'completed' ? '[done]' : `[${t.status}]`;
|
|
170
|
+
lines.push(`#${t.id.padEnd(5)} ${status.padEnd(12)} ${t.subject.slice(0, 30).padEnd(32)} ${t.owner || '──'}`);
|
|
171
|
+
}
|
|
172
|
+
if (state.alerts.length > 0) {
|
|
173
|
+
lines.push('');
|
|
174
|
+
lines.push('ALERTS');
|
|
175
|
+
for (const a of state.alerts.slice(-3)) {
|
|
176
|
+
lines.push(`⚠ ${a.message}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
lines.push('');
|
|
180
|
+
return lines.join('\n');
|
|
181
|
+
}
|
|
182
|
+
// ─── Auto-detect ───
|
|
183
|
+
async function autoDetectTeam() {
|
|
184
|
+
const teams = await listTeamNames();
|
|
185
|
+
if (teams.length === 1)
|
|
186
|
+
return teams[0];
|
|
187
|
+
if (teams.length === 0)
|
|
188
|
+
return null;
|
|
189
|
+
console.log(chalk.yellow('Multiple active teams found:'));
|
|
190
|
+
for (const name of teams) {
|
|
191
|
+
console.log(` - ${name}`);
|
|
192
|
+
}
|
|
193
|
+
console.log(chalk.dim('Specify one: ccx watch <team-name>'));
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
function getPaneIds(state) {
|
|
197
|
+
// 目前沒有直接拿到 paneId 的方式(config.json 的 tmuxPaneId 通常是空的)
|
|
198
|
+
// 這是 v1.5 hard limit 的限制
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/commands/watch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FileCursor, JournalEntry } from './types.js';
|
|
2
|
+
export interface CursorReadResult {
|
|
3
|
+
readonly newEntries: readonly JournalEntry[];
|
|
4
|
+
readonly rawLines: readonly string[];
|
|
5
|
+
readonly cursor: FileCursor;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 建立新的 FileCursor
|
|
9
|
+
*/
|
|
10
|
+
export declare function createCursor(path: string, agentId: string): FileCursor;
|
|
11
|
+
/**
|
|
12
|
+
* 從 cursor 的 lastByteOffset 開始讀取新增內容
|
|
13
|
+
* 回傳 parsed entries(有 usage 的行)和 raw lines(全部行,用於 identity resolve)
|
|
14
|
+
*/
|
|
15
|
+
export declare function readIncremental(cursor: FileCursor): Promise<CursorReadResult>;
|
|
16
|
+
/**
|
|
17
|
+
* Full scan:從頭讀取整個檔案(startup 用)
|
|
18
|
+
*/
|
|
19
|
+
export declare function readFull(path: string, agentId: string): Promise<CursorReadResult>;
|
|
20
|
+
/**
|
|
21
|
+
* 更新 cursor 的 identity 狀態
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveCursorIdentity(cursor: FileCursor, name: string): FileCursor;
|
|
24
|
+
//# sourceMappingURL=cursor-reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-reader.d.ts","sourceRoot":"","sources":["../../src/core/cursor-reader.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAc,YAAY,EAAe,MAAM,YAAY,CAAA;AAInF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,SAAS,YAAY,EAAE,CAAA;IAC5C,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAC5B;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAYtE;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyDnF;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAGvF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,GACX,UAAU,CAMZ"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CursorReader:byte-offset based JSONL 差量讀取
|
|
3
|
+
*
|
|
4
|
+
* 每個 JSONL 檔案維護獨立狀態:
|
|
5
|
+
* - UNRESOLVED: 每行的 raw text 都傳給 IdentityResolver 直到找到 teammate_id
|
|
6
|
+
* - RESOLVED: 只累加 token counts + 維護 ring buffer
|
|
7
|
+
*
|
|
8
|
+
* 設計原則:
|
|
9
|
+
* - 只存 offset + aggregated counts,不存全部 parsed records
|
|
10
|
+
* - Line-boundary parsing:記錄 lastCompleteLineEndOffset
|
|
11
|
+
* - Truncation detection:newSize < lastOffset → reset to 0
|
|
12
|
+
*/
|
|
13
|
+
import { stat } from 'node:fs/promises';
|
|
14
|
+
import { createReadStream } from 'node:fs';
|
|
15
|
+
import { createInterface } from 'node:readline';
|
|
16
|
+
import { parseJournalLine } from './data-source-adapter.js';
|
|
17
|
+
import { addUsage, emptyUsage } from './pricing.js';
|
|
18
|
+
const RING_BUFFER_SIZE = 20;
|
|
19
|
+
/**
|
|
20
|
+
* 建立新的 FileCursor
|
|
21
|
+
*/
|
|
22
|
+
export function createCursor(path, agentId) {
|
|
23
|
+
return {
|
|
24
|
+
path,
|
|
25
|
+
agentId,
|
|
26
|
+
lastByteOffset: 0,
|
|
27
|
+
lastInode: 0,
|
|
28
|
+
state: 'UNRESOLVED',
|
|
29
|
+
resolvedName: null,
|
|
30
|
+
accumulated: emptyUsage(),
|
|
31
|
+
model: null,
|
|
32
|
+
recentEntries: [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 從 cursor 的 lastByteOffset 開始讀取新增內容
|
|
37
|
+
* 回傳 parsed entries(有 usage 的行)和 raw lines(全部行,用於 identity resolve)
|
|
38
|
+
*/
|
|
39
|
+
export async function readIncremental(cursor) {
|
|
40
|
+
const fileStat = await stat(cursor.path).catch(() => null);
|
|
41
|
+
if (!fileStat) {
|
|
42
|
+
return { newEntries: [], rawLines: [], cursor };
|
|
43
|
+
}
|
|
44
|
+
const currentInode = fileStat.ino;
|
|
45
|
+
const currentSize = fileStat.size;
|
|
46
|
+
// Truncation/replacement detection
|
|
47
|
+
const wasReplaced = cursor.lastInode !== 0 && currentInode !== cursor.lastInode;
|
|
48
|
+
const wasTruncated = currentSize < cursor.lastByteOffset;
|
|
49
|
+
let startOffset = cursor.lastByteOffset;
|
|
50
|
+
let resetAccumulated = cursor.accumulated;
|
|
51
|
+
if (wasReplaced || wasTruncated) {
|
|
52
|
+
startOffset = 0;
|
|
53
|
+
resetAccumulated = emptyUsage();
|
|
54
|
+
}
|
|
55
|
+
if (currentSize <= startOffset) {
|
|
56
|
+
return {
|
|
57
|
+
newEntries: [],
|
|
58
|
+
rawLines: [],
|
|
59
|
+
cursor: { ...cursor, lastInode: currentInode },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// 讀取新增部分
|
|
63
|
+
const { entries, rawLines, bytesRead } = await readLines(cursor.path, startOffset, currentSize);
|
|
64
|
+
// 累加 token usage
|
|
65
|
+
let accumulated = resetAccumulated;
|
|
66
|
+
let model = cursor.model;
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
accumulated = addUsage(accumulated, entry.message.usage);
|
|
69
|
+
if (!model && entry.message.model) {
|
|
70
|
+
model = entry.message.model;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// 更新 ring buffer
|
|
74
|
+
const existingRecent = wasReplaced || wasTruncated ? [] : [...cursor.recentEntries];
|
|
75
|
+
const allRecent = [...existingRecent, ...entries];
|
|
76
|
+
const recentEntries = allRecent.slice(-RING_BUFFER_SIZE);
|
|
77
|
+
const updatedCursor = {
|
|
78
|
+
...cursor,
|
|
79
|
+
lastByteOffset: startOffset + bytesRead,
|
|
80
|
+
lastInode: currentInode,
|
|
81
|
+
accumulated,
|
|
82
|
+
model,
|
|
83
|
+
recentEntries,
|
|
84
|
+
};
|
|
85
|
+
return { newEntries: entries, rawLines, cursor: updatedCursor };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Full scan:從頭讀取整個檔案(startup 用)
|
|
89
|
+
*/
|
|
90
|
+
export async function readFull(path, agentId) {
|
|
91
|
+
const cursor = createCursor(path, agentId);
|
|
92
|
+
return readIncremental(cursor);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 更新 cursor 的 identity 狀態
|
|
96
|
+
*/
|
|
97
|
+
export function resolveCursorIdentity(cursor, name) {
|
|
98
|
+
return {
|
|
99
|
+
...cursor,
|
|
100
|
+
state: 'RESOLVED',
|
|
101
|
+
resolvedName: name,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async function readLines(filePath, startOffset, endOffset) {
|
|
105
|
+
const entries = [];
|
|
106
|
+
const rawLines = [];
|
|
107
|
+
let bytesRead = 0;
|
|
108
|
+
const stream = createReadStream(filePath, {
|
|
109
|
+
start: startOffset,
|
|
110
|
+
end: endOffset - 1,
|
|
111
|
+
encoding: 'utf-8',
|
|
112
|
+
});
|
|
113
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
114
|
+
let lastCompleteLineEnd = 0;
|
|
115
|
+
for await (const line of rl) {
|
|
116
|
+
const lineBytes = Buffer.byteLength(line, 'utf-8') + 1; // +1 for newline
|
|
117
|
+
lastCompleteLineEnd += lineBytes;
|
|
118
|
+
rawLines.push(line);
|
|
119
|
+
const result = parseJournalLine(line);
|
|
120
|
+
if (result.ok) {
|
|
121
|
+
entries.push(result.data);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
stream.destroy();
|
|
125
|
+
bytesRead = lastCompleteLineEnd;
|
|
126
|
+
return { entries, rawLines, bytesRead };
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=cursor-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-reader.js","sourceRoot":"","sources":["../../src/core/cursor-reader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 資料來源適配層:zod schema validation + 容錯 JSON 解析
|
|
3
|
+
*/
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
export declare const TeamMemberSchema: z.ZodObject<{
|
|
6
|
+
agentId: z.ZodString;
|
|
7
|
+
name: z.ZodString;
|
|
8
|
+
agentType: z.ZodString;
|
|
9
|
+
model: z.ZodDefault<z.ZodString>;
|
|
10
|
+
joinedAt: z.ZodNumber;
|
|
11
|
+
tmuxPaneId: z.ZodDefault<z.ZodString>;
|
|
12
|
+
cwd: z.ZodDefault<z.ZodString>;
|
|
13
|
+
subscriptions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
agentId: string;
|
|
16
|
+
name: string;
|
|
17
|
+
agentType: string;
|
|
18
|
+
model: string;
|
|
19
|
+
joinedAt: number;
|
|
20
|
+
tmuxPaneId: string;
|
|
21
|
+
cwd: string;
|
|
22
|
+
subscriptions: string[];
|
|
23
|
+
}, {
|
|
24
|
+
agentId: string;
|
|
25
|
+
name: string;
|
|
26
|
+
agentType: string;
|
|
27
|
+
joinedAt: number;
|
|
28
|
+
model?: string | undefined;
|
|
29
|
+
tmuxPaneId?: string | undefined;
|
|
30
|
+
cwd?: string | undefined;
|
|
31
|
+
subscriptions?: string[] | undefined;
|
|
32
|
+
}>;
|
|
33
|
+
export declare const TeamConfigSchema: z.ZodObject<{
|
|
34
|
+
name: z.ZodString;
|
|
35
|
+
description: z.ZodDefault<z.ZodString>;
|
|
36
|
+
createdAt: z.ZodNumber;
|
|
37
|
+
leadAgentId: z.ZodString;
|
|
38
|
+
leadSessionId: z.ZodString;
|
|
39
|
+
members: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
40
|
+
agentId: z.ZodString;
|
|
41
|
+
name: z.ZodString;
|
|
42
|
+
agentType: z.ZodString;
|
|
43
|
+
model: z.ZodDefault<z.ZodString>;
|
|
44
|
+
joinedAt: z.ZodNumber;
|
|
45
|
+
tmuxPaneId: z.ZodDefault<z.ZodString>;
|
|
46
|
+
cwd: z.ZodDefault<z.ZodString>;
|
|
47
|
+
subscriptions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
48
|
+
}, "strip", z.ZodTypeAny, {
|
|
49
|
+
agentId: string;
|
|
50
|
+
name: string;
|
|
51
|
+
agentType: string;
|
|
52
|
+
model: string;
|
|
53
|
+
joinedAt: number;
|
|
54
|
+
tmuxPaneId: string;
|
|
55
|
+
cwd: string;
|
|
56
|
+
subscriptions: string[];
|
|
57
|
+
}, {
|
|
58
|
+
agentId: string;
|
|
59
|
+
name: string;
|
|
60
|
+
agentType: string;
|
|
61
|
+
joinedAt: number;
|
|
62
|
+
model?: string | undefined;
|
|
63
|
+
tmuxPaneId?: string | undefined;
|
|
64
|
+
cwd?: string | undefined;
|
|
65
|
+
subscriptions?: string[] | undefined;
|
|
66
|
+
}>, "many">>;
|
|
67
|
+
}, "strip", z.ZodTypeAny, {
|
|
68
|
+
name: string;
|
|
69
|
+
description: string;
|
|
70
|
+
createdAt: number;
|
|
71
|
+
leadAgentId: string;
|
|
72
|
+
leadSessionId: string;
|
|
73
|
+
members: {
|
|
74
|
+
agentId: string;
|
|
75
|
+
name: string;
|
|
76
|
+
agentType: string;
|
|
77
|
+
model: string;
|
|
78
|
+
joinedAt: number;
|
|
79
|
+
tmuxPaneId: string;
|
|
80
|
+
cwd: string;
|
|
81
|
+
subscriptions: string[];
|
|
82
|
+
}[];
|
|
83
|
+
}, {
|
|
84
|
+
name: string;
|
|
85
|
+
createdAt: number;
|
|
86
|
+
leadAgentId: string;
|
|
87
|
+
leadSessionId: string;
|
|
88
|
+
description?: string | undefined;
|
|
89
|
+
members?: {
|
|
90
|
+
agentId: string;
|
|
91
|
+
name: string;
|
|
92
|
+
agentType: string;
|
|
93
|
+
joinedAt: number;
|
|
94
|
+
model?: string | undefined;
|
|
95
|
+
tmuxPaneId?: string | undefined;
|
|
96
|
+
cwd?: string | undefined;
|
|
97
|
+
subscriptions?: string[] | undefined;
|
|
98
|
+
}[] | undefined;
|
|
99
|
+
}>;
|
|
100
|
+
export declare const TaskDataSchema: z.ZodObject<{
|
|
101
|
+
id: z.ZodString;
|
|
102
|
+
subject: z.ZodDefault<z.ZodString>;
|
|
103
|
+
description: z.ZodDefault<z.ZodString>;
|
|
104
|
+
activeForm: z.ZodDefault<z.ZodString>;
|
|
105
|
+
owner: z.ZodDefault<z.ZodString>;
|
|
106
|
+
status: z.ZodDefault<z.ZodEnum<["pending", "in_progress", "completed", "deleted"]>>;
|
|
107
|
+
blocks: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
108
|
+
blockedBy: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
109
|
+
}, "strip", z.ZodTypeAny, {
|
|
110
|
+
status: "pending" | "in_progress" | "completed" | "deleted";
|
|
111
|
+
description: string;
|
|
112
|
+
id: string;
|
|
113
|
+
subject: string;
|
|
114
|
+
activeForm: string;
|
|
115
|
+
owner: string;
|
|
116
|
+
blocks: string[];
|
|
117
|
+
blockedBy: string[];
|
|
118
|
+
}, {
|
|
119
|
+
id: string;
|
|
120
|
+
status?: "pending" | "in_progress" | "completed" | "deleted" | undefined;
|
|
121
|
+
description?: string | undefined;
|
|
122
|
+
subject?: string | undefined;
|
|
123
|
+
activeForm?: string | undefined;
|
|
124
|
+
owner?: string | undefined;
|
|
125
|
+
blocks?: string[] | undefined;
|
|
126
|
+
blockedBy?: string[] | undefined;
|
|
127
|
+
}>;
|
|
128
|
+
export declare const InboxMessageSchema: z.ZodObject<{
|
|
129
|
+
from: z.ZodString;
|
|
130
|
+
text: z.ZodDefault<z.ZodString>;
|
|
131
|
+
summary: z.ZodDefault<z.ZodString>;
|
|
132
|
+
timestamp: z.ZodString;
|
|
133
|
+
color: z.ZodDefault<z.ZodString>;
|
|
134
|
+
read: z.ZodDefault<z.ZodBoolean>;
|
|
135
|
+
}, "strip", z.ZodTypeAny, {
|
|
136
|
+
from: string;
|
|
137
|
+
text: string;
|
|
138
|
+
summary: string;
|
|
139
|
+
timestamp: string;
|
|
140
|
+
color: string;
|
|
141
|
+
read: boolean;
|
|
142
|
+
}, {
|
|
143
|
+
from: string;
|
|
144
|
+
timestamp: string;
|
|
145
|
+
text?: string | undefined;
|
|
146
|
+
summary?: string | undefined;
|
|
147
|
+
color?: string | undefined;
|
|
148
|
+
read?: boolean | undefined;
|
|
149
|
+
}>;
|
|
150
|
+
export declare const JournalEntrySchema: z.ZodObject<{
|
|
151
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
152
|
+
agentId: z.ZodOptional<z.ZodString>;
|
|
153
|
+
timestamp: z.ZodString;
|
|
154
|
+
message: z.ZodObject<{
|
|
155
|
+
model: z.ZodDefault<z.ZodString>;
|
|
156
|
+
usage: z.ZodObject<{
|
|
157
|
+
input_tokens: z.ZodDefault<z.ZodNumber>;
|
|
158
|
+
output_tokens: z.ZodDefault<z.ZodNumber>;
|
|
159
|
+
cache_creation_input_tokens: z.ZodDefault<z.ZodNumber>;
|
|
160
|
+
cache_read_input_tokens: z.ZodDefault<z.ZodNumber>;
|
|
161
|
+
}, "strip", z.ZodTypeAny, {
|
|
162
|
+
input_tokens: number;
|
|
163
|
+
output_tokens: number;
|
|
164
|
+
cache_creation_input_tokens: number;
|
|
165
|
+
cache_read_input_tokens: number;
|
|
166
|
+
}, {
|
|
167
|
+
input_tokens?: number | undefined;
|
|
168
|
+
output_tokens?: number | undefined;
|
|
169
|
+
cache_creation_input_tokens?: number | undefined;
|
|
170
|
+
cache_read_input_tokens?: number | undefined;
|
|
171
|
+
}>;
|
|
172
|
+
}, "strip", z.ZodTypeAny, {
|
|
173
|
+
model: string;
|
|
174
|
+
usage: {
|
|
175
|
+
input_tokens: number;
|
|
176
|
+
output_tokens: number;
|
|
177
|
+
cache_creation_input_tokens: number;
|
|
178
|
+
cache_read_input_tokens: number;
|
|
179
|
+
};
|
|
180
|
+
}, {
|
|
181
|
+
usage: {
|
|
182
|
+
input_tokens?: number | undefined;
|
|
183
|
+
output_tokens?: number | undefined;
|
|
184
|
+
cache_creation_input_tokens?: number | undefined;
|
|
185
|
+
cache_read_input_tokens?: number | undefined;
|
|
186
|
+
};
|
|
187
|
+
model?: string | undefined;
|
|
188
|
+
}>;
|
|
189
|
+
}, "strip", z.ZodTypeAny, {
|
|
190
|
+
message: {
|
|
191
|
+
model: string;
|
|
192
|
+
usage: {
|
|
193
|
+
input_tokens: number;
|
|
194
|
+
output_tokens: number;
|
|
195
|
+
cache_creation_input_tokens: number;
|
|
196
|
+
cache_read_input_tokens: number;
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
timestamp: string;
|
|
200
|
+
agentId?: string | undefined;
|
|
201
|
+
sessionId?: string | undefined;
|
|
202
|
+
}, {
|
|
203
|
+
message: {
|
|
204
|
+
usage: {
|
|
205
|
+
input_tokens?: number | undefined;
|
|
206
|
+
output_tokens?: number | undefined;
|
|
207
|
+
cache_creation_input_tokens?: number | undefined;
|
|
208
|
+
cache_read_input_tokens?: number | undefined;
|
|
209
|
+
};
|
|
210
|
+
model?: string | undefined;
|
|
211
|
+
};
|
|
212
|
+
timestamp: string;
|
|
213
|
+
agentId?: string | undefined;
|
|
214
|
+
sessionId?: string | undefined;
|
|
215
|
+
}>;
|
|
216
|
+
export declare function safeParseJson<S extends z.ZodTypeAny>(raw: string, schema: S): {
|
|
217
|
+
ok: true;
|
|
218
|
+
data: z.output<S>;
|
|
219
|
+
} | {
|
|
220
|
+
ok: false;
|
|
221
|
+
error: string;
|
|
222
|
+
};
|
|
223
|
+
export declare function safeParseJsonArray<S extends z.ZodTypeAny>(raw: string, itemSchema: S): {
|
|
224
|
+
ok: true;
|
|
225
|
+
data: Array<z.output<S>>;
|
|
226
|
+
} | {
|
|
227
|
+
ok: false;
|
|
228
|
+
error: string;
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* 解析 JSONL 的單一行 — 忽略不完整的行
|
|
232
|
+
*/
|
|
233
|
+
export declare function parseJournalLine(line: string): {
|
|
234
|
+
ok: true;
|
|
235
|
+
data: z.output<typeof JournalEntrySchema>;
|
|
236
|
+
} | {
|
|
237
|
+
ok: false;
|
|
238
|
+
};
|
|
239
|
+
//# sourceMappingURL=data-source-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-source-adapter.d.ts","sourceRoot":"","sources":["../../src/core/data-source-adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS3B,CAAA;AAEF,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAO3B,CAAA;AAEF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;EASzB,CAAA;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;EAO7B,CAAA;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAa7B,CAAA;AAIF,wBAAgB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EAClD,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,GACR;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAWhE;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACvD,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,CAAC,GACZ;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAEvE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,GACX;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAMzE"}
|