agent-handoff 0.2.0 → 0.4.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/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { createRequire } from "module";
5
- import { Command as Command7 } from "commander";
5
+ import { Command as Command9 } from "commander";
6
6
 
7
7
  // src/cli/commands/init.ts
8
8
  import { Command } from "commander";
@@ -377,14 +377,15 @@ async function copyToClipboard(text) {
377
377
  import fs4 from "fs/promises";
378
378
  import path4 from "path";
379
379
  async function writeEvent(options) {
380
- const { workspacePath, step, type, summary, workItemId, links } = options;
380
+ const { workspacePath, step, type, summary, workItemId, links, data } = options;
381
381
  const event = {
382
382
  ts: (/* @__PURE__ */ new Date()).toISOString(),
383
383
  step,
384
384
  type,
385
385
  summary,
386
386
  ...workItemId && { workItemId },
387
- ...links && links.length > 0 && { links }
387
+ ...links && links.length > 0 && { links },
388
+ ...data && { data }
388
389
  };
389
390
  try {
390
391
  const eventsPath = path4.join(workspacePath, "events.jsonl");
@@ -571,7 +572,7 @@ async function validateArtifactFile(filePath) {
571
572
  try {
572
573
  const content = await fs5.readFile(filePath, "utf-8");
573
574
  return validateArtifact(content);
574
- } catch (error) {
575
+ } catch {
575
576
  return {
576
577
  valid: false,
577
578
  errors: [
@@ -799,7 +800,8 @@ function getDefaultSummary(eventType, stepId) {
799
800
  "accept.passed": "\u9A8C\u6536\u901A\u8FC7",
800
801
  "accept.failed": "\u9A8C\u6536\u5931\u8D25",
801
802
  "issue.raised": "\u53D1\u73B0\u95EE\u9898",
802
- "handoff.sent": "\u5DF2\u4EA4\u63A5\u7ED9\u4E0B\u4E00\u6B65"
803
+ "handoff.sent": "\u5DF2\u4EA4\u63A5\u7ED9\u4E0B\u4E00\u6B65",
804
+ "automation.session": "\u81EA\u52A8\u5316\u4F1A\u8BDD\u5DF2\u8BB0\u5F55"
803
805
  };
804
806
  return summaries[eventType] || `\u4E8B\u4EF6: ${eventType}`;
805
807
  }
@@ -818,6 +820,14 @@ var DEFAULT_CONFIG = {
818
820
  logStepStarted: true,
819
821
  logStepDone: true
820
822
  },
823
+ automation: {
824
+ enabled: false,
825
+ provider: "nutjs",
826
+ screenshot: false,
827
+ timeout: 3e4,
828
+ retries: 3,
829
+ confidence: 0.8
830
+ },
821
831
  clipboard: {
822
832
  autoCopy: false
823
833
  },
@@ -989,7 +999,7 @@ var configCommand = new Command6("config").description("\u67E5\u770B\u6216\u7BA1
989
999
  } else if (action === "init") {
990
1000
  await initConfig(options);
991
1001
  } else if (action === "list") {
992
- await listConfig(options);
1002
+ await listConfig();
993
1003
  } else {
994
1004
  console.error(`Unknown action: ${action}`);
995
1005
  console.log("Available actions: show, init, list");
@@ -1023,7 +1033,7 @@ async function initConfig(options) {
1023
1033
  console.log("Default configuration:");
1024
1034
  console.log(JSON.stringify(DEFAULT_CONFIG, null, 2));
1025
1035
  }
1026
- async function listConfig(options) {
1036
+ async function listConfig() {
1027
1037
  console.log("Configuration options:");
1028
1038
  console.log("");
1029
1039
  console.log(" defaultWorkspace - \u9ED8\u8BA4 workspace \u8DEF\u5F84");
@@ -1044,10 +1054,733 @@ async function listConfig(options) {
1044
1054
  console.log(` ~/.agenthandoffrc (global)`);
1045
1055
  }
1046
1056
 
1057
+ // src/cli/commands/report.ts
1058
+ import { Command as Command7 } from "commander";
1059
+ import fs9 from "fs/promises";
1060
+ import path10 from "path";
1061
+
1062
+ // src/adapters/trae/operation-reporter.ts
1063
+ import fs8 from "fs/promises";
1064
+ var OperationReporter = class {
1065
+ session;
1066
+ constructor(session) {
1067
+ this.session = session;
1068
+ }
1069
+ generate(options) {
1070
+ switch (options.format) {
1071
+ case "json":
1072
+ return this.generateJson();
1073
+ case "markdown":
1074
+ return this.generateMarkdown(options.includeScreenshots);
1075
+ case "html":
1076
+ return this.generateHtml(options.includeScreenshots);
1077
+ default:
1078
+ return this.generateMarkdown(options.includeScreenshots);
1079
+ }
1080
+ }
1081
+ generateJson() {
1082
+ return JSON.stringify(this.session, null, 2);
1083
+ }
1084
+ generateMarkdown(includeScreenshots) {
1085
+ const lines = [
1086
+ "# Automation Session Report",
1087
+ "",
1088
+ `**Session ID:** ${this.session.id}`,
1089
+ `**Started At:** ${this.session.startedAt}`,
1090
+ `**Workspace:** ${this.session.workspacePath}`,
1091
+ `**Step ID:** ${this.session.stepId}`,
1092
+ ...this.session.status ? [`**Status:** ${this.session.status}`] : [],
1093
+ ...this.session.error ? [`**Error:** ${this.session.error}`] : [],
1094
+ "",
1095
+ `## Operations (${this.session.operations.length})`,
1096
+ ""
1097
+ ];
1098
+ this.session.operations.forEach((op, index) => {
1099
+ lines.push(`### ${index + 1}. ${op.type}`);
1100
+ if (op.target) {
1101
+ lines.push(`- **Target:** ${op.target}`);
1102
+ }
1103
+ if (op.value) {
1104
+ lines.push(
1105
+ `- **Value:** ${op.value.substring(0, 100)}${op.value.length > 100 ? "..." : ""}`
1106
+ );
1107
+ }
1108
+ lines.push(`- **Timestamp:** ${new Date(op.timestamp).toISOString()}`);
1109
+ lines.push("");
1110
+ });
1111
+ if (includeScreenshots && this.session.screenshots.length > 0) {
1112
+ lines.push(`## Screenshots (${this.session.screenshots.length})`);
1113
+ lines.push("");
1114
+ this.session.screenshots.forEach((screenshot, index) => {
1115
+ lines.push(`### Screenshot ${index + 1}`);
1116
+ lines.push(`![Screenshot ${index + 1}](${screenshot})`);
1117
+ lines.push("");
1118
+ });
1119
+ }
1120
+ return lines.join("\n");
1121
+ }
1122
+ generateHtml(includeScreenshots) {
1123
+ const operationsHtml = this.session.operations.map(
1124
+ (op, index) => `
1125
+ <div class="operation">
1126
+ <h3>${index + 1}. ${op.type}</h3>
1127
+ ${op.target ? `<p><strong>Target:</strong> ${op.target}</p>` : ""}
1128
+ ${op.value ? `<p><strong>Value:</strong> ${op.value.substring(0, 100)}${op.value.length > 100 ? "..." : ""}</p>` : ""}
1129
+ <p><strong>Timestamp:</strong> ${new Date(op.timestamp).toISOString()}</p>
1130
+ </div>
1131
+ `
1132
+ ).join("");
1133
+ const screenshotsHtml = includeScreenshots && this.session.screenshots.length > 0 ? `
1134
+ <h2>Screenshots (${this.session.screenshots.length})</h2>
1135
+ ${this.session.screenshots.map(
1136
+ (s, i) => `
1137
+ <div class="screenshot">
1138
+ <h3>Screenshot ${i + 1}</h3>
1139
+ <img src="${s}" alt="Screenshot ${i + 1}" />
1140
+ </div>
1141
+ `
1142
+ ).join("")}
1143
+ ` : "";
1144
+ const statusLine = this.session.status ? `<p><strong>Status:</strong> ${this.session.status}</p>` : "";
1145
+ const errorLine = this.session.error ? `<p><strong>Error:</strong> ${this.session.error}</p>` : "";
1146
+ return `
1147
+ <!DOCTYPE html>
1148
+ <html lang="zh-CN">
1149
+ <head>
1150
+ <meta charset="UTF-8">
1151
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1152
+ <title>Automation Session Report</title>
1153
+ <style>
1154
+ body {
1155
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1156
+ max-width: 800px;
1157
+ margin: 0 auto;
1158
+ padding: 20px;
1159
+ background: #f5f5f5;
1160
+ }
1161
+ .header {
1162
+ background: white;
1163
+ padding: 20px;
1164
+ border-radius: 8px;
1165
+ margin-bottom: 20px;
1166
+ }
1167
+ .operation {
1168
+ background: white;
1169
+ padding: 15px;
1170
+ border-radius: 8px;
1171
+ margin-bottom: 10px;
1172
+ }
1173
+ .screenshot img {
1174
+ max-width: 100%;
1175
+ border-radius: 8px;
1176
+ }
1177
+ h1 { color: #333; }
1178
+ h2 { color: #555; border-bottom: 1px solid #eee; padding-bottom: 10px; }
1179
+ h3 { color: #666; margin: 0 0 10px 0; }
1180
+ p { margin: 5px 0; color: #666; }
1181
+ </style>
1182
+ </head>
1183
+ <body>
1184
+ <div class="header">
1185
+ <h1>Automation Session Report</h1>
1186
+ <p><strong>Session ID:</strong> ${this.session.id}</p>
1187
+ <p><strong>Started At:</strong> ${this.session.startedAt}</p>
1188
+ <p><strong>Workspace:</strong> ${this.session.workspacePath}</p>
1189
+ <p><strong>Step ID:</strong> ${this.session.stepId}</p>
1190
+ ${statusLine}
1191
+ ${errorLine}
1192
+ </div>
1193
+
1194
+ <h2>Operations (${this.session.operations.length})</h2>
1195
+ ${operationsHtml}
1196
+
1197
+ ${screenshotsHtml}
1198
+ </body>
1199
+ </html>
1200
+ `.trim();
1201
+ }
1202
+ async saveToFile(outputPath, options) {
1203
+ const content = this.generate(options);
1204
+ await fs8.writeFile(outputPath, content, "utf-8");
1205
+ return outputPath;
1206
+ }
1207
+ };
1208
+
1209
+ // src/cli/commands/report.ts
1210
+ var reportCommand = new Command7("report").description("\u751F\u6210\u81EA\u52A8\u5316\u64CD\u4F5C\u62A5\u544A").argument("[workspace]", "workspace \u8DEF\u5F84", ".").option("-f, --format <format>", "\u62A5\u544A\u683C\u5F0F: json, markdown, html", "markdown").option("-s, --session <id>", "\u6307\u5B9A session ID").option("--screenshots", "\u5305\u542B\u622A\u56FE", false).option("-o, --output <path>", "\u8F93\u51FA\u8DEF\u5F84").action(
1211
+ async (workspace, options) => {
1212
+ const workspacePath = path10.resolve(workspace);
1213
+ const logDir = path10.join(workspacePath, "operations");
1214
+ try {
1215
+ let sessionFile;
1216
+ if (options.session) {
1217
+ sessionFile = path10.join(logDir, `operations-${options.session}.jsonl`);
1218
+ } else {
1219
+ const files = await fs9.readdir(logDir);
1220
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).sort().reverse();
1221
+ if (jsonlFiles.length === 0) {
1222
+ console.error("Error: no operation logs found");
1223
+ process.exit(1);
1224
+ }
1225
+ sessionFile = path10.join(logDir, jsonlFiles[0]);
1226
+ }
1227
+ const content = await fs9.readFile(sessionFile, "utf-8");
1228
+ const lines = content.split("\n").filter((l) => l.trim());
1229
+ const session = JSON.parse(lines[lines.length - 1]);
1230
+ const reporter = new OperationReporter(session);
1231
+ const report = reporter.generate({
1232
+ format: options.format,
1233
+ includeScreenshots: options.screenshots
1234
+ });
1235
+ if (options.output) {
1236
+ await fs9.writeFile(path10.resolve(options.output), report, "utf-8");
1237
+ console.log(`\u2705 Report saved to: ${options.output}`);
1238
+ } else {
1239
+ console.log(report);
1240
+ }
1241
+ } catch (error) {
1242
+ console.error(`Error: ${error}`);
1243
+ process.exit(1);
1244
+ }
1245
+ }
1246
+ );
1247
+
1248
+ // src/cli/commands/export.ts
1249
+ import { Command as Command8 } from "commander";
1250
+ import path13 from "path";
1251
+
1252
+ // src/export/web/exporter.ts
1253
+ import fs11 from "fs/promises";
1254
+ import path12 from "path";
1255
+
1256
+ // src/core/events-reader.ts
1257
+ import fs10 from "fs/promises";
1258
+ import path11 from "path";
1259
+ async function readEventsJsonl(options) {
1260
+ const eventsPath = path11.join(options.workspacePath, "events.jsonl");
1261
+ try {
1262
+ const content = await fs10.readFile(eventsPath, "utf-8");
1263
+ const lines = content.split("\n").filter((line) => line.trim());
1264
+ const events = [];
1265
+ let invalidLines = 0;
1266
+ for (const line of lines) {
1267
+ try {
1268
+ const event = JSON.parse(line);
1269
+ events.push(event);
1270
+ } catch {
1271
+ invalidLines += 1;
1272
+ }
1273
+ }
1274
+ events.sort((a, b) => {
1275
+ const ats = a.ts ?? "";
1276
+ const bts = b.ts ?? "";
1277
+ if (ats < bts) return -1;
1278
+ if (ats > bts) return 1;
1279
+ return 0;
1280
+ });
1281
+ const limited = typeof options.limit === "number" && options.limit > 0 ? events.slice(-options.limit) : events;
1282
+ return { events: limited, invalidLines };
1283
+ } catch {
1284
+ return { events: [], invalidLines: 0 };
1285
+ }
1286
+ }
1287
+ function toTimelineItems(events) {
1288
+ return events.map((event) => ({
1289
+ ts: event.ts,
1290
+ stepId: event.step.id,
1291
+ stepIndex: event.step.index,
1292
+ type: event.type,
1293
+ summary: event.summary,
1294
+ ...event.workItemId && { workItemId: event.workItemId },
1295
+ ...event.links && event.links.length > 0 && { links: event.links },
1296
+ ...event.data && { data: event.data }
1297
+ }));
1298
+ }
1299
+
1300
+ // src/export/web/artifact-indexer.ts
1301
+ import crypto from "crypto";
1302
+ function sanitizeSegments(segments) {
1303
+ const result = [];
1304
+ for (const seg of segments) {
1305
+ const trimmed = seg.trim();
1306
+ if (!trimmed || trimmed === ".") continue;
1307
+ if (trimmed === "..") {
1308
+ result.push("__");
1309
+ continue;
1310
+ }
1311
+ result.push(trimmed);
1312
+ }
1313
+ return result;
1314
+ }
1315
+ function toSafeFilename(original) {
1316
+ const rawSegments = original.split(/[\\/]+/g);
1317
+ const safeSegments = sanitizeSegments(rawSegments);
1318
+ const base = safeSegments.join("-") || "artifact";
1319
+ const normalized = base.replace(/[^a-zA-Z0-9._-]+/g, "_");
1320
+ const suffix = ".html";
1321
+ const maxLen = 180;
1322
+ if (normalized.length + suffix.length <= maxLen) {
1323
+ return normalized + suffix;
1324
+ }
1325
+ const hash = crypto.createHash("sha1").update(original).digest("hex").slice(0, 10);
1326
+ const headLen = Math.max(16, maxLen - suffix.length - 1 - hash.length);
1327
+ const head = normalized.slice(0, headLen);
1328
+ return `${head}-${hash}${suffix}`;
1329
+ }
1330
+ function buildArtifactLinkMap(links) {
1331
+ const seen = /* @__PURE__ */ new Set();
1332
+ const mappings = [];
1333
+ for (const link of links) {
1334
+ const original = String(link);
1335
+ if (seen.has(original)) continue;
1336
+ seen.add(original);
1337
+ const filename = toSafeFilename(original);
1338
+ const outputHtmlPath = `artifacts/${filename}`;
1339
+ mappings.push({ original, outputHtmlPath, title: original });
1340
+ }
1341
+ return mappings;
1342
+ }
1343
+
1344
+ // src/export/web/artifact-renderer.ts
1345
+ function escapeHtml(input) {
1346
+ return input.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1347
+ }
1348
+ function renderArtifactPage(options) {
1349
+ const title = escapeHtml(options.title);
1350
+ const originalPath = escapeHtml(options.originalPath);
1351
+ const content = escapeHtml(options.content);
1352
+ return [
1353
+ "<!doctype html>",
1354
+ '<html lang="zh-CN">',
1355
+ "<head>",
1356
+ '<meta charset="utf-8" />',
1357
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />',
1358
+ `<title>${title}</title>`,
1359
+ "<style>",
1360
+ 'body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial; color: #111827; background: #ffffff; }',
1361
+ ".container { max-width: 1040px; margin: 0 auto; padding: 24px; }",
1362
+ "h1 { font-size: 18px; margin: 0 0 8px; }",
1363
+ '.path { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 12px; color: #374151; }',
1364
+ "pre { margin-top: 16px; background: #0b1020; color: #e5e7eb; padding: 12px; border-radius: 10px; overflow: auto; font-size: 12px; line-height: 1.5; }",
1365
+ "</style>",
1366
+ "</head>",
1367
+ "<body>",
1368
+ '<div class="container">',
1369
+ `<h1>${title}</h1>`,
1370
+ `<div class="path">${originalPath}</div>`,
1371
+ `<pre>${content}</pre>`,
1372
+ "</div>",
1373
+ "</body>",
1374
+ "</html>",
1375
+ ""
1376
+ ].join("\n");
1377
+ }
1378
+
1379
+ // src/export/web/timeline-renderer.ts
1380
+ function escapeHtml2(input) {
1381
+ return input.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1382
+ }
1383
+ function renderLinks(links, linkHrefMap) {
1384
+ const items = links.map((link) => {
1385
+ const href = linkHrefMap && linkHrefMap[link] ? linkHrefMap[link] : link;
1386
+ const safeLabel = escapeHtml2(link);
1387
+ const safeHref = escapeHtml2(href);
1388
+ return `<a href="${safeHref}" data-link="${safeLabel}" target="_blank" rel="noopener noreferrer">${safeLabel}</a>`;
1389
+ }).join("");
1390
+ return `<div class="links">${items}</div>`;
1391
+ }
1392
+ function renderData(data) {
1393
+ const json = escapeHtml2(JSON.stringify(data, null, 2));
1394
+ return `<details><summary>data</summary><pre>${json}</pre></details>`;
1395
+ }
1396
+ function renderWebTimeline(options) {
1397
+ const assets = {};
1398
+ if (options.includeAssets) {
1399
+ assets["assets/viewer.css"] = getDefaultCss();
1400
+ assets["assets/viewer.js"] = getDefaultJs();
1401
+ }
1402
+ const title = escapeHtml2(options.title);
1403
+ const workspaceName = escapeHtml2(options.workspaceName);
1404
+ const generatedAt = escapeHtml2(options.generatedAt);
1405
+ const eventsJson = escapeHtml2(JSON.stringify(options.items));
1406
+ const linkMapJson = escapeHtml2(JSON.stringify(options.linkHrefMap || {}));
1407
+ const headAssets = options.includeAssets ? `<link rel="stylesheet" href="assets/viewer.css" />` : "";
1408
+ const bodyAssets = options.includeAssets ? `<script src="assets/viewer.js"></script>` : "";
1409
+ const list = options.items.map((item) => {
1410
+ const ts = escapeHtml2(item.ts);
1411
+ const stepId = escapeHtml2(item.stepId);
1412
+ const type = escapeHtml2(item.type);
1413
+ const summary = escapeHtml2(item.summary);
1414
+ const workItem = item.workItemId ? `<span class="badge">${escapeHtml2(item.workItemId)}</span>` : "";
1415
+ const links = item.links && item.links.length > 0 ? renderLinks(item.links, options.linkHrefMap) : "";
1416
+ const data = item.data ? renderData(item.data) : "";
1417
+ return [
1418
+ `<div class="item" data-step="${stepId}" data-type="${type}">`,
1419
+ `<div class="row">`,
1420
+ `<span class="ts">${ts}</span>`,
1421
+ `<span class="badge">${stepId}</span>`,
1422
+ `<span class="type">${type}</span>`,
1423
+ workItem,
1424
+ `</div>`,
1425
+ `<div class="summary">${summary}</div>`,
1426
+ links,
1427
+ data,
1428
+ `</div>`
1429
+ ].join("");
1430
+ }).join("");
1431
+ const html = [
1432
+ "<!doctype html>",
1433
+ '<html lang="zh-CN">',
1434
+ "<head>",
1435
+ '<meta charset="utf-8" />',
1436
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />',
1437
+ `<title>${title}</title>`,
1438
+ headAssets,
1439
+ "</head>",
1440
+ "<body>",
1441
+ '<div class="container">',
1442
+ "<header>",
1443
+ `<h1>${workspaceName} Timeline</h1>`,
1444
+ `<div class="meta">Generated at ${generatedAt}</div>`,
1445
+ "</header>",
1446
+ '<section class="filters" id="timeline-filters">',
1447
+ '<div class="filters-row">',
1448
+ '<input id="filter-q" type="search" placeholder="\u641C\u7D22 summary / data..." />',
1449
+ '<select id="filter-step"></select>',
1450
+ '<select id="filter-type"></select>',
1451
+ '<select id="filter-workItem"></select>',
1452
+ '<button id="filter-clear" type="button">\u6E05\u7A7A\u7B5B\u9009</button>',
1453
+ "</div>",
1454
+ '<div class="filters-meta" id="filters-meta"></div>',
1455
+ "</section>",
1456
+ `<script id="__EVENTS__" type="application/json">${eventsJson}</script>`,
1457
+ `<script id="__LINK_HREF_MAP__" type="application/json">${linkMapJson}</script>`,
1458
+ `<div class="list" id="timeline-list">${list}</div>`,
1459
+ "</div>",
1460
+ bodyAssets,
1461
+ "</body>",
1462
+ "</html>"
1463
+ ].filter((x) => x !== "").join("\n");
1464
+ return { html, assets };
1465
+ }
1466
+ function getDefaultCss() {
1467
+ return [
1468
+ "* { box-sizing: border-box; }",
1469
+ 'body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; color: #111827; background: #ffffff; }',
1470
+ ".container { max-width: 1040px; margin: 0 auto; padding: 24px; }",
1471
+ "header { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; border-bottom: 1px solid #e5e7eb; padding-bottom: 12px; margin-bottom: 16px; }",
1472
+ "h1 { font-size: 20px; margin: 0; }",
1473
+ ".meta { font-size: 12px; color: #6b7280; }",
1474
+ ".filters { margin: 12px 0 16px; padding: 12px; border: 1px solid #e5e7eb; border-radius: 10px; background: #fafafa; }",
1475
+ ".filters-row { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr auto; gap: 10px; align-items: center; }",
1476
+ ".filters-row input, .filters-row select { width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid #d1d5db; font-size: 13px; background: #fff; }",
1477
+ ".filters-row button { padding: 8px 12px; border-radius: 8px; border: 1px solid #d1d5db; background: #fff; cursor: pointer; }",
1478
+ ".filters-row button:hover { background: #f3f4f6; }",
1479
+ ".filters-meta { margin-top: 8px; font-size: 12px; color: #6b7280; }",
1480
+ ".list { display: flex; flex-direction: column; gap: 10px; }",
1481
+ ".item { border: 1px solid #e5e7eb; border-radius: 10px; padding: 12px; }",
1482
+ ".row { display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: center; }",
1483
+ '.ts { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 12px; color: #374151; }',
1484
+ ".badge { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 999px; font-size: 12px; background: #eff6ff; color: #1d4ed8; border: 1px solid #dbeafe; }",
1485
+ '.type { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 12px; color: #111827; background: #f3f4f6; padding: 2px 8px; border-radius: 8px; }',
1486
+ ".summary { margin-top: 8px; white-space: pre-wrap; line-height: 1.45; }",
1487
+ ".links { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 8px; }",
1488
+ ".links a { font-size: 12px; color: #2563eb; text-decoration: none; border-bottom: 1px dashed #93c5fd; }",
1489
+ ".links a:hover { border-bottom-style: solid; }",
1490
+ "details { margin-top: 10px; }",
1491
+ "pre { background: #0b1020; color: #e5e7eb; padding: 10px; border-radius: 10px; overflow: auto; font-size: 12px; line-height: 1.5; }",
1492
+ ""
1493
+ ].join("\n");
1494
+ }
1495
+ function getDefaultJs() {
1496
+ return [
1497
+ "(() => {",
1498
+ " const g = globalThis;",
1499
+ " const doc = g.document;",
1500
+ ' if (!doc || typeof doc.getElementById !== "function") return;',
1501
+ ' const el = doc.getElementById("__EVENTS__");',
1502
+ " if (!el) return;",
1503
+ ' const qInput = doc.getElementById("filter-q");',
1504
+ ' const stepSelect = doc.getElementById("filter-step");',
1505
+ ' const typeSelect = doc.getElementById("filter-type");',
1506
+ ' const workItemSelect = doc.getElementById("filter-workItem");',
1507
+ ' const clearBtn = doc.getElementById("filter-clear");',
1508
+ ' const listEl = doc.getElementById("timeline-list");',
1509
+ ' const metaEl = doc.getElementById("filters-meta");',
1510
+ " const HTMLInput = g.HTMLInputElement;",
1511
+ " const HTMLSelect = g.HTMLSelectElement;",
1512
+ " const HTMLButton = g.HTMLButtonElement;",
1513
+ " const HTMLElementCtor = g.HTMLElement;",
1514
+ " if (!HTMLInput || !HTMLSelect || !HTMLButton || !HTMLElementCtor) return;",
1515
+ " if (",
1516
+ " !(qInput instanceof HTMLInput) ||",
1517
+ " !(stepSelect instanceof HTMLSelect) ||",
1518
+ " !(typeSelect instanceof HTMLSelect) ||",
1519
+ " !(workItemSelect instanceof HTMLSelect) ||",
1520
+ " !(clearBtn instanceof HTMLButton) ||",
1521
+ " !(listEl instanceof HTMLElementCtor)",
1522
+ " ) {",
1523
+ " return;",
1524
+ " }",
1525
+ " let events;",
1526
+ " try {",
1527
+ ' events = JSON.parse(el.textContent || "[]");',
1528
+ " } catch {",
1529
+ " return;",
1530
+ " }",
1531
+ ' const linkMapEl = doc.getElementById("__LINK_HREF_MAP__");',
1532
+ " let linkHrefMap = {};",
1533
+ ' if (linkMapEl && typeof linkMapEl.textContent === "string" && linkMapEl.textContent.trim()) {',
1534
+ " try {",
1535
+ " linkHrefMap = JSON.parse(linkMapEl.textContent) || {};",
1536
+ " } catch {",
1537
+ " linkHrefMap = {};",
1538
+ " }",
1539
+ " }",
1540
+ ' const safeText = (v) => (v == null ? "" : String(v));',
1541
+ " const normalize = (v) => safeText(v).toLowerCase();",
1542
+ " const getUniqueSorted = (arr) =>",
1543
+ ' Array.from(new Set(arr.filter((x) => x != null && String(x).trim() !== "").map(String))).sort();',
1544
+ " const steps = getUniqueSorted(events.map((e) => e.stepId));",
1545
+ " const types = getUniqueSorted(events.map((e) => e.type));",
1546
+ " const workItems = getUniqueSorted(events.map((e) => e.workItemId));",
1547
+ " const setOptions = (select, values, labelAll) => {",
1548
+ ' select.innerHTML = "";',
1549
+ ' const optAll = doc.createElement("option");',
1550
+ ' optAll.value = "";',
1551
+ " optAll.textContent = labelAll;",
1552
+ " select.appendChild(optAll);",
1553
+ " for (const v of values) {",
1554
+ ' const opt = doc.createElement("option");',
1555
+ " opt.value = v;",
1556
+ " opt.textContent = v;",
1557
+ " select.appendChild(opt);",
1558
+ " }",
1559
+ " };",
1560
+ ' setOptions(stepSelect, steps, "\u5168\u90E8 step");',
1561
+ ' setOptions(typeSelect, types, "\u5168\u90E8 type");',
1562
+ ' setOptions(workItemSelect, workItems, "\u5168\u90E8 workItem");',
1563
+ ' const getQuery = () => new URLSearchParams(g.location ? g.location.search : "");',
1564
+ " const applyFromUrl = () => {",
1565
+ " const qs = getQuery();",
1566
+ ' qInput.value = safeText(qs.get("q") || "");',
1567
+ ' stepSelect.value = safeText(qs.get("step") || "");',
1568
+ ' typeSelect.value = safeText(qs.get("type") || "");',
1569
+ ' workItemSelect.value = safeText(qs.get("workItem") || "");',
1570
+ " };",
1571
+ " const writeUrl = () => {",
1572
+ " if (!g.history || !g.location) return;",
1573
+ " const qs = new URLSearchParams();",
1574
+ ' if (qInput.value.trim()) qs.set("q", qInput.value.trim());',
1575
+ ' if (stepSelect.value) qs.set("step", stepSelect.value);',
1576
+ ' if (typeSelect.value) qs.set("type", typeSelect.value);',
1577
+ ' if (workItemSelect.value) qs.set("workItem", workItemSelect.value);',
1578
+ " const qsStr = qs.toString();",
1579
+ " const next = qsStr ? `${g.location.pathname}?${qsStr}` : g.location.pathname;",
1580
+ ' g.history.replaceState(null, "", next);',
1581
+ " };",
1582
+ " const escapeHtml = (input) =>",
1583
+ " safeText(input)",
1584
+ ' .replaceAll("&", "&amp;")',
1585
+ ' .replaceAll("<", "&lt;")',
1586
+ ' .replaceAll(">", "&gt;")',
1587
+ ' .replaceAll("\\"", "&quot;")',
1588
+ ` .replaceAll("\\'", "&#39;");`,
1589
+ " const renderLinks = (links) => {",
1590
+ ' if (!Array.isArray(links) || links.length === 0) return "";',
1591
+ " const html = links",
1592
+ " .map((l) => {",
1593
+ " const href = linkHrefMap && linkHrefMap[l] ? linkHrefMap[l] : l;",
1594
+ " const safeHref = escapeHtml(href);",
1595
+ " const label = escapeHtml(l);",
1596
+ ' return `<a href="${safeHref}" target="_blank" rel="noopener noreferrer">${label}</a>`;',
1597
+ " })",
1598
+ ' .join("");',
1599
+ ' return `<div class="links">${html}</div>`;',
1600
+ " };",
1601
+ " const renderData = (data) => {",
1602
+ ' if (!data) return "";',
1603
+ ' let json = "";',
1604
+ " try {",
1605
+ " json = JSON.stringify(data, null, 2);",
1606
+ " } catch {",
1607
+ " json = safeText(data);",
1608
+ " }",
1609
+ " return `<details><summary>data</summary><pre>${escapeHtml(json)}</pre></details>`;",
1610
+ " };",
1611
+ " const renderList = (items) => {",
1612
+ " listEl.innerHTML = items",
1613
+ " .map((item) => {",
1614
+ " const ts = escapeHtml(item.ts);",
1615
+ " const stepId = escapeHtml(item.stepId);",
1616
+ " const type = escapeHtml(item.type);",
1617
+ " const summary = escapeHtml(item.summary);",
1618
+ ' const workItem = item.workItemId ? `<span class="badge">${escapeHtml(item.workItemId)}</span>` : "";',
1619
+ " return [",
1620
+ ' `<div class="item" data-step="${stepId}" data-type="${type}">`,',
1621
+ ' `<div class="row">`,',
1622
+ ' `<span class="ts">${ts}</span>`,',
1623
+ ' `<span class="badge">${stepId}</span>`,',
1624
+ ' `<span class="type">${type}</span>`,',
1625
+ " workItem,",
1626
+ " `</div>`,",
1627
+ ' `<div class="summary">${summary}</div>`,',
1628
+ " renderLinks(item.links),",
1629
+ " renderData(item.data),",
1630
+ " `</div>`,",
1631
+ ' ].join("");',
1632
+ " })",
1633
+ ' .join("");',
1634
+ " };",
1635
+ " const applyFilters = () => {",
1636
+ " const q = normalize(qInput.value.trim());",
1637
+ " const step = safeText(stepSelect.value);",
1638
+ " const type = safeText(typeSelect.value);",
1639
+ " const workItem = safeText(workItemSelect.value);",
1640
+ " const filtered = events.filter((e) => {",
1641
+ " if (step && safeText(e.stepId) !== step) return false;",
1642
+ " if (type && safeText(e.type) !== type) return false;",
1643
+ " if (workItem && safeText(e.workItemId) !== workItem) return false;",
1644
+ " if (!q) return true;",
1645
+ " const hay1 = normalize(e.summary);",
1646
+ ' let hay2 = "";',
1647
+ " try {",
1648
+ " hay2 = normalize(JSON.stringify(e.data || {}));",
1649
+ " } catch {",
1650
+ ' hay2 = normalize(String(e.data || ""));',
1651
+ " }",
1652
+ " return hay1.includes(q) || hay2.includes(q);",
1653
+ " });",
1654
+ " renderList(filtered);",
1655
+ " if (metaEl) {",
1656
+ " metaEl.textContent = `\u663E\u793A ${filtered.length} / ${events.length}`;",
1657
+ " }",
1658
+ " writeUrl();",
1659
+ " };",
1660
+ " applyFromUrl();",
1661
+ " applyFilters();",
1662
+ ' qInput.addEventListener("input", applyFilters);',
1663
+ ' stepSelect.addEventListener("change", applyFilters);',
1664
+ ' typeSelect.addEventListener("change", applyFilters);',
1665
+ ' workItemSelect.addEventListener("change", applyFilters);',
1666
+ ' clearBtn.addEventListener("click", () => {',
1667
+ ' qInput.value = "";',
1668
+ ' stepSelect.value = "";',
1669
+ ' typeSelect.value = "";',
1670
+ ' workItemSelect.value = "";',
1671
+ " applyFilters();",
1672
+ " });",
1673
+ "})();",
1674
+ ""
1675
+ ].join("\n");
1676
+ }
1677
+
1678
+ // src/export/web/exporter.ts
1679
+ async function ensureDir(dir) {
1680
+ await fs11.mkdir(dir, { recursive: true });
1681
+ }
1682
+ async function exportWebTimeline(options) {
1683
+ const { events, invalidLines } = await readEventsJsonl({
1684
+ workspacePath: options.workspacePath,
1685
+ limit: options.limit
1686
+ });
1687
+ const items = toTimelineItems(events);
1688
+ const linkSet = /* @__PURE__ */ new Set();
1689
+ for (const item of items) {
1690
+ for (const link of item.links || []) {
1691
+ linkSet.add(link);
1692
+ }
1693
+ }
1694
+ const mappings = buildArtifactLinkMap([...linkSet]);
1695
+ const linkHrefMap = {};
1696
+ for (const m of mappings) {
1697
+ linkHrefMap[m.original] = m.outputHtmlPath;
1698
+ }
1699
+ const out = path12.resolve(options.outputDir);
1700
+ await ensureDir(out);
1701
+ await ensureDir(path12.join(out, "assets"));
1702
+ await ensureDir(path12.join(out, "artifacts"));
1703
+ for (const m of mappings) {
1704
+ const artifactSourcePath = path12.join(options.workspacePath, m.original);
1705
+ let content;
1706
+ try {
1707
+ content = await fs11.readFile(artifactSourcePath, "utf-8");
1708
+ } catch {
1709
+ content = `\u6587\u4EF6\u4E0D\u5B58\u5728\u6216\u65E0\u6CD5\u8BFB\u53D6: ${m.original}`;
1710
+ }
1711
+ const html = renderArtifactPage({
1712
+ title: m.title,
1713
+ originalPath: m.original,
1714
+ content
1715
+ });
1716
+ const fullOutPath = path12.join(out, m.outputHtmlPath);
1717
+ await ensureDir(path12.dirname(fullOutPath));
1718
+ await fs11.writeFile(fullOutPath, html, "utf-8");
1719
+ }
1720
+ const timeline = renderWebTimeline({
1721
+ title: "AgentHandoff Timeline",
1722
+ workspaceName: path12.basename(path12.resolve(options.workspacePath)),
1723
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1724
+ items,
1725
+ includeAssets: true,
1726
+ linkHrefMap
1727
+ });
1728
+ await fs11.writeFile(path12.join(out, "index.html"), timeline.html, "utf-8");
1729
+ for (const [rel, content] of Object.entries(timeline.assets)) {
1730
+ const full = path12.join(out, rel);
1731
+ await ensureDir(path12.dirname(full));
1732
+ await fs11.writeFile(full, content, "utf-8");
1733
+ }
1734
+ return {
1735
+ outputDir: out,
1736
+ indexPath: path12.join(out, "index.html"),
1737
+ eventsCount: items.length,
1738
+ invalidLines,
1739
+ artifactsCount: mappings.length
1740
+ };
1741
+ }
1742
+
1743
+ // src/cli/commands/export.ts
1744
+ var exportCommand = new Command8("export").description("\u5BFC\u51FA\u9759\u6001\u4EA7\u7269\uFF08\u4F8B\u5982 Web Timeline\uFF09").argument("[workspace]", "workspace \u8DEF\u5F84", ".").requiredOption("--format <format>", "\u5BFC\u51FA\u683C\u5F0F: web", "web").option("-o, --output <dir>", "\u5BFC\u51FA\u76EE\u5F55\uFF08\u9ED8\u8BA4 <workspace>/timeline\uFF09").option("--limit <n>", "\u53EA\u5BFC\u51FA\u6700\u8FD1 n \u6761\u4E8B\u4EF6", (v) => parseInt(v, 10)).action(
1745
+ async (workspace, options) => {
1746
+ const workspacePath = path13.resolve(workspace);
1747
+ try {
1748
+ const info = await loadWorkspace(workspacePath);
1749
+ if (!info.exists) {
1750
+ console.error(`Error: workspace not found: ${workspacePath}`);
1751
+ process.exit(1);
1752
+ }
1753
+ if (!info.hasWorkflow) {
1754
+ console.error(`Error: workflow.yaml not found in ${workspacePath}`);
1755
+ process.exit(1);
1756
+ }
1757
+ if (options.format !== "web") {
1758
+ console.error(`Error: unsupported format: ${options.format}`);
1759
+ process.exit(1);
1760
+ }
1761
+ const outputDir = options.output ? path13.resolve(options.output) : path13.join(workspacePath, "timeline");
1762
+ const result = await exportWebTimeline({
1763
+ workspacePath,
1764
+ outputDir,
1765
+ limit: options.limit
1766
+ });
1767
+ console.log(`\u2705 Exported to: ${result.outputDir}`);
1768
+ console.log(` index: ${result.indexPath}`);
1769
+ console.log(` events: ${result.eventsCount} (invalid lines: ${result.invalidLines})`);
1770
+ console.log(` artifacts: ${result.artifactsCount}`);
1771
+ console.log("");
1772
+ console.log(`\u6253\u5F00\u65B9\u5F0F\uFF08macOS\uFF09\uFF1Aopen ${path13.join(result.outputDir, "index.html")}`);
1773
+ } catch (error) {
1774
+ console.error(`Error: ${error}`);
1775
+ process.exit(1);
1776
+ }
1777
+ }
1778
+ );
1779
+
1047
1780
  // src/index.ts
1048
1781
  var require2 = createRequire(import.meta.url);
1049
1782
  var { version } = require2("../package.json");
1050
- var program = new Command7();
1783
+ var program = new Command9();
1051
1784
  program.name("agent-handoff").description("\u8F7B\u91CF\u7EA7\u591A Agent \u534F\u4F5C\u63A5\u529B\u5DE5\u5177").version(version);
1052
1785
  program.addCommand(initCommand);
1053
1786
  program.addCommand(statusCommand);
@@ -1055,5 +1788,7 @@ program.addCommand(nextCommand);
1055
1788
  program.addCommand(validateCommand);
1056
1789
  program.addCommand(advanceCommand);
1057
1790
  program.addCommand(configCommand);
1791
+ program.addCommand(reportCommand);
1792
+ program.addCommand(exportCommand);
1058
1793
  program.parse();
1059
1794
  //# sourceMappingURL=index.js.map