@my-life-buddies/cli 0.13.3 → 0.14.1

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.
@@ -9,6 +9,175 @@ function createBuddyPreviewUrl(buddyId) {
9
9
  return `mlb://preview?${new URLSearchParams({ buddyId })}`;
10
10
  }
11
11
 
12
+ // apps/preview/src/runtime-diagnostics.ts
13
+ function parseInspectedData(text2) {
14
+ let at = 0;
15
+ const space = () => {
16
+ while (/\s/u.test(text2[at] ?? "") && at < text2.length) at++;
17
+ };
18
+ const fail = () => {
19
+ throw new Error("Unsupported log data");
20
+ };
21
+ function string() {
22
+ const quote = text2[at++];
23
+ let result = "";
24
+ while (at < text2.length) {
25
+ const char = text2[at++];
26
+ if (char === quote) return result;
27
+ if (char !== "\\") {
28
+ result += char;
29
+ continue;
30
+ }
31
+ const escaped = text2[at++];
32
+ const escapes = { n: "\n", r: "\r", t: " ", b: "\b", f: "\f", v: "\v", "0": "\0", "\\": "\\", "'": "'", '"': '"', "`": "`", "$": "$" };
33
+ if (escaped === "x" || escaped === "u") {
34
+ const length = escaped === "x" ? 2 : 4;
35
+ const hex = text2.slice(at, at + length);
36
+ if (!new RegExp(`^[0-9a-fA-F]{${length}}$`, "u").test(hex)) fail();
37
+ result += String.fromCharCode(parseInt(hex, 16));
38
+ at += length;
39
+ } else if (escaped !== void 0 && Object.hasOwn(escapes, escaped)) result += escapes[escaped];
40
+ else fail();
41
+ }
42
+ return fail();
43
+ }
44
+ function value(depth = 0) {
45
+ if (depth > 40) fail();
46
+ space();
47
+ const char = text2[at];
48
+ if (char === "'" || char === '"' || char === "`") {
49
+ let result = string();
50
+ space();
51
+ while (text2[at] === "+") {
52
+ at++;
53
+ space();
54
+ if (!["'", '"', "`"].includes(text2[at] ?? "")) fail();
55
+ result += string();
56
+ space();
57
+ }
58
+ return result;
59
+ }
60
+ if (char === "{" || char === "[") {
61
+ const array = char === "[";
62
+ const end = array ? "]" : "}";
63
+ const result = /* @__PURE__ */ Object.create(null);
64
+ const items = [];
65
+ at++;
66
+ space();
67
+ while (text2[at] !== end) {
68
+ if (at >= text2.length) fail();
69
+ if (array) items.push(value(depth + 1));
70
+ else {
71
+ const quoted = ["'", '"', "`"].includes(text2[at] ?? "");
72
+ const key = quoted ? string() : /^[\w$]+/u.exec(text2.slice(at))?.[0];
73
+ if (key === void 0) return fail();
74
+ if (!quoted) at += key.length;
75
+ space();
76
+ if (text2[at++] !== ":" || Object.hasOwn(result, key)) fail();
77
+ result[key] = value(depth + 1);
78
+ }
79
+ space();
80
+ if (text2[at] === end) break;
81
+ if (text2[at++] !== ",") fail();
82
+ space();
83
+ }
84
+ at++;
85
+ return array ? items : result;
86
+ }
87
+ const literal = /^(true|false|null|undefined|[-+]?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(?=[\s,\]}]|$)/iu.exec(text2.slice(at))?.[0];
88
+ if (!literal) return fail();
89
+ at += literal.length;
90
+ if (literal === "true") return true;
91
+ if (literal === "false") return false;
92
+ if (literal === "null" || literal === "undefined") return null;
93
+ const number = Number(literal);
94
+ return Number.isFinite(number) ? number : fail();
95
+ }
96
+ try {
97
+ if (text2.length > 98304) return void 0;
98
+ const result = value();
99
+ space();
100
+ return at === text2.length && result !== null && typeof result === "object" && !Array.isArray(result) ? result : void 0;
101
+ } catch {
102
+ return void 0;
103
+ }
104
+ }
105
+ var HEADER = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \[[^\]\r\n]+\] (INFO|WARN|ERROR)\s+(.+)$/u;
106
+ var MAX_RECORD = 98304;
107
+ var MAX_BYTES = 15e5;
108
+ var RuntimeDiagnostics = class {
109
+ #sequence = 0;
110
+ #records = [];
111
+ #active = /* @__PURE__ */ new Map();
112
+ #sizes = /* @__PURE__ */ new Map();
113
+ #bytes = 0;
114
+ #dropped = 0;
115
+ write(source, text2, observedAt) {
116
+ for (const line of text2.split(/\r?\n/u)) {
117
+ if (!line) continue;
118
+ const header = HEADER.exec(line);
119
+ const previousIndex = this.#records.findIndex((record3) => record3.id === this.#active.get(source));
120
+ const previous = this.#records[previousIndex];
121
+ const continuation = !header && previous && (/^\s/u.test(line) || line === "}");
122
+ let record2;
123
+ if (continuation) {
124
+ const raw = `${previous.raw}
125
+ ${line}`;
126
+ record2 = { ...previous, raw: raw.slice(0, MAX_RECORD), truncated: previous.truncated || raw.length > MAX_RECORD || line.endsWith(" \u2026[truncated]") };
127
+ } else {
128
+ const title = header ? header[3].replace(/ \{.*$/u, "") : line.slice(0, 180);
129
+ const time = header ? Date.parse(`${header[1].replace(" ", "T")}+08:00`) : NaN;
130
+ record2 = {
131
+ id: `runtime-${++this.#sequence}`,
132
+ timestamp: Number.isFinite(time) ? new Date(time).toISOString() : observedAt,
133
+ source,
134
+ level: header ? header[2].toLowerCase() : source === "agent.stderr" ? "error" : "info",
135
+ title,
136
+ kind: header && title === "\u6A21\u578B\u8BF7\u6C42\u4F53" ? "request" : header && title === "\u6A21\u578B\u54CD\u5E94" ? "response" : "log",
137
+ raw: line.slice(0, MAX_RECORD),
138
+ truncated: line.length > MAX_RECORD || line.endsWith(" \u2026[truncated]")
139
+ };
140
+ this.#active.set(source, record2.id);
141
+ }
142
+ if (!record2.truncated && line.trimEnd().endsWith("}")) {
143
+ const head = HEADER.exec(record2.raw.split("\n")[0]);
144
+ const offset = head ? record2.raw.indexOf(" {", record2.raw.indexOf(head[3])) : -1;
145
+ const data = offset < 0 ? void 0 : parseInspectedData(record2.raw.slice(offset + 1));
146
+ if (data) record2 = { ...record2, data, ...typeof data.convoKey === "string" ? { convoKey: data.convoKey } : {} };
147
+ }
148
+ if (continuation && previous.data) {
149
+ const { data: _data, convoKey: _convoKey, ...rawRecord } = record2;
150
+ record2 = rawRecord;
151
+ }
152
+ this.#bytes -= this.#sizes.get(record2.id) ?? 0;
153
+ const size = Buffer.byteLength(JSON.stringify(record2));
154
+ this.#sizes.set(record2.id, size);
155
+ this.#bytes += size;
156
+ if (continuation) this.#records[previousIndex] = record2;
157
+ else this.#records.push(record2);
158
+ while (this.#records.length > 200 || this.#bytes > MAX_BYTES) {
159
+ const removed = this.#records.shift();
160
+ this.#bytes -= this.#sizes.get(removed.id) ?? 0;
161
+ this.#sizes.delete(removed.id);
162
+ this.#dropped++;
163
+ }
164
+ }
165
+ }
166
+ boundary() {
167
+ this.#active.clear();
168
+ }
169
+ clear() {
170
+ this.#records = [];
171
+ this.#active.clear();
172
+ this.#sizes.clear();
173
+ this.#bytes = 0;
174
+ this.#dropped = 0;
175
+ }
176
+ snapshot() {
177
+ return { records: [...this.#records].reverse(), dropped: this.#dropped };
178
+ }
179
+ };
180
+
12
181
  // apps/preview/src/controller.ts
13
182
  var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
14
183
  function publicFailure(cause) {
@@ -31,8 +200,10 @@ var PreviewController = class {
31
200
  #failedOutputRuns = /* @__PURE__ */ new Set();
32
201
  #observedActiveRuns = /* @__PURE__ */ new Set();
33
202
  #events = [];
203
+ #diagnostics = new RuntimeDiagnostics();
34
204
  #runOrdinal = 0;
35
205
  #conversationRevision = 0;
206
+ #simulatorRevision = 0;
36
207
  #resetting = false;
37
208
  #sendingMessage = false;
38
209
  get resetting() {
@@ -78,7 +249,7 @@ var PreviewController = class {
78
249
  return () => this.#simulatorListeners.delete(listener);
79
250
  }
80
251
  simulatorSnapshot() {
81
- return Object.freeze({ ...this.#simulatorState, conversationRevision: this.#conversationRevision });
252
+ return Object.freeze({ ...this.#simulatorState, conversationRevision: this.#conversationRevision, stateRevision: this.#simulatorRevision });
82
253
  }
83
254
  start() {
84
255
  if (this.#startPromise) return this.#startPromise;
@@ -87,6 +258,7 @@ var PreviewController = class {
87
258
  return Promise.reject(Object.assign(new Error("DEV \u4F1A\u8BDD\u6B63\u5728\u505C\u6B62"), { code: "DEV_STOPPING" }));
88
259
  }
89
260
  const controller = new AbortController();
261
+ this.#diagnostics.boundary();
90
262
  this.#startController = controller;
91
263
  this.#record({ channel: "dev", type: "dev.start_requested", summary: "\u8BF7\u6C42\u542F\u52A8 DEV \u4F1A\u8BDD" });
92
264
  this.#setState({ phase: "starting" });
@@ -95,7 +267,8 @@ var PreviewController = class {
95
267
  signal: controller.signal,
96
268
  onLog: ({ source, text: text2 }) => {
97
269
  if (controller.signal.aborted) return;
98
- this.#record({ channel: "dev", type: source, summary: text2.slice(0, 8224) });
270
+ this.#diagnostics.write(source, text2, this.#now().toISOString());
271
+ this.#record({ channel: "dev", type: source, summary: text2.slice(0, 512) });
99
272
  this.#notifySimulator();
100
273
  }
101
274
  }).then(({ handle, ready, simulator }) => {
@@ -218,6 +391,7 @@ var PreviewController = class {
218
391
  this.#runs.clear();
219
392
  this.#observedActiveRuns.clear();
220
393
  this.#events.length = 0;
394
+ this.#diagnostics.clear();
221
395
  this.#runOrdinal = 0;
222
396
  this.#lastActiveRunId = void 0;
223
397
  this.#messages = Object.freeze([]);
@@ -235,7 +409,29 @@ var PreviewController = class {
235
409
  const started = this.#now();
236
410
  this.#record({ channel: "client", type: "message.send", summary: "\u53D1\u9001\u4E00\u6761\u79C1\u804A\u6D88\u606F" });
237
411
  const accepted = await this.#requiredSimulator().send(input, { signal });
238
- if (this.#runs.has(accepted.runId)) return accepted;
412
+ if (this.#runs.has(accepted.runId)) {
413
+ if (this.#simulatorState.activeRun?.runId === accepted.runId && !this.#simulatorState.messages.some((message2) => message2.messageId === accepted.messageId)) {
414
+ this.#record({
415
+ channel: "app_server",
416
+ type: "message.accepted",
417
+ summary: "\u8FFD\u52A0\u6D88\u606F\u5DF2\u63A5\u6536",
418
+ runId: accepted.runId,
419
+ durationMs: Math.max(0, this.#now().getTime() - started.getTime())
420
+ });
421
+ this.#simulatorState = Object.freeze({
422
+ ...this.#simulatorState,
423
+ messages: Object.freeze([...this.#simulatorState.messages, Object.freeze({
424
+ messageId: accepted.messageId,
425
+ role: "user",
426
+ text: input.text,
427
+ createdAt: this.#now().toISOString()
428
+ })])
429
+ });
430
+ this.#messages = this.#simulatorState.messages;
431
+ this.#notifySimulator();
432
+ }
433
+ return accepted;
434
+ }
239
435
  const acceptedAt = this.#now();
240
436
  const latency = Math.max(0, acceptedAt.getTime() - started.getTime());
241
437
  this.#upsertRun({
@@ -314,7 +510,8 @@ var PreviewController = class {
314
510
  generatedAt: this.#now().toISOString(),
315
511
  runs: Object.freeze([...this.#runs.values()].reverse()),
316
512
  events: Object.freeze([...this.#events].reverse()),
317
- messages: Object.freeze([...this.#messages])
513
+ messages: Object.freeze([...this.#messages]),
514
+ diagnostics: this.#diagnostics.snapshot()
318
515
  });
319
516
  }
320
517
  devicePairing() {
@@ -460,7 +657,7 @@ var PreviewController = class {
460
657
  void simulator.watchOutput(runId, {
461
658
  signal: controller.signal,
462
659
  onEvent: (event) => {
463
- if (this.#resetting || simulator !== this.#simulator) return;
660
+ if (controller.signal.aborted || this.#resetting || simulator !== this.#simulator) return;
464
661
  return this.#observeOutputEvent(event);
465
662
  }
466
663
  }).catch((cause) => {
@@ -479,6 +676,14 @@ var PreviewController = class {
479
676
  });
480
677
  }
481
678
  async #observeOutputEvent(event) {
679
+ if (event.type === "state") {
680
+ if (event.resetStreaming) {
681
+ const { streaming: _streaming, ...state } = this.#simulatorState;
682
+ this.#simulatorState = Object.freeze(state);
683
+ }
684
+ this.#observeSimulatorState(event.state);
685
+ return;
686
+ }
482
687
  if (event.type === "reset") {
483
688
  if (this.#simulatorState.streaming?.runId === event.runId) {
484
689
  const { streaming: _streaming, ...state } = this.#simulatorState;
@@ -560,6 +765,7 @@ var PreviewController = class {
560
765
  controller.abort(new Error("Agent local turn reached a terminal state"));
561
766
  }
562
767
  #notifySimulator() {
768
+ this.#simulatorRevision++;
563
769
  if (this.#simulatorListeners.size === 0) return;
564
770
  const value = Object.freeze({ state: this.simulatorSnapshot(), debug: this.debugSnapshot() });
565
771
  for (const listener of this.#simulatorListeners) {
@@ -640,6 +846,7 @@ var WEB_ASSETS = Object.freeze(/* @__PURE__ */ new Map([
640
846
  contentType: "text/javascript; charset=utf-8",
641
847
  body: readFileSync(new URL("../web/app.js", import.meta.url))
642
848
  }],
849
+ ["/assets/diagnostics.js", { contentType: "text/javascript; charset=utf-8", body: readFileSync(new URL("../web/diagnostics.js", import.meta.url)) }],
643
850
  ["/assets/developer-platform-icon.png", {
644
851
  contentType: "image/png",
645
852
  body: BRAND_ICON
@@ -1463,7 +1670,7 @@ var MlbSimulatorClient = class {
1463
1670
  this.#active = void 0;
1464
1671
  this.#streaming = void 0;
1465
1672
  } else if (this.#active.observedReplying) {
1466
- this.#latest = Object.freeze({ runId: this.#active.runId, status: "failed" });
1673
+ this.#latest = Object.freeze({ runId: this.#active.runId, status: "completed" });
1467
1674
  this.#active = void 0;
1468
1675
  this.#streaming = void 0;
1469
1676
  }
@@ -1490,11 +1697,11 @@ var MlbSimulatorClient = class {
1490
1697
  if (this.#lastSend.text !== input.text.trim()) throw httpError(409, { error: "\u540C\u4E00\u6761\u6D88\u606F\u4E0D\u80FD\u6539\u5199\u5185\u5BB9" });
1491
1698
  return this.#lastSend.receipt;
1492
1699
  }
1493
- if (this.#active || this.#sending) {
1494
- throw Object.assign(new Error("\u4E0A\u4E00\u8F6E\u56DE\u590D\u5C1A\u672A\u7ED3\u675F"), {
1495
- code: "TURN_IN_PROGRESS",
1700
+ if (this.#sending) {
1701
+ throw Object.assign(new Error("\u6D88\u606F\u6B63\u5728\u53D1\u9001"), {
1702
+ code: "SEND_IN_PROGRESS",
1496
1703
  status: 409,
1497
- publicMessage: "\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u5B8C\u6210\u540E\u518D\u53D1\u9001"
1704
+ publicMessage: "\u6D88\u606F\u6B63\u5728\u53D1\u9001\uFF0C\u8BF7\u7A0D\u5019"
1498
1705
  });
1499
1706
  }
1500
1707
  this.#sending = true;
@@ -1528,15 +1735,17 @@ var MlbSimulatorClient = class {
1528
1735
  }
1529
1736
  const receipt = object(value);
1530
1737
  const item = object(receipt.items[0]);
1531
- const runId = localReceipt("turn", idempotencyKey);
1738
+ const runId = this.#active?.runId ?? localReceipt("turn", idempotencyKey);
1532
1739
  const messageId = safeText(item.messageId, "receipt.items[0].messageId", 256);
1533
- this.#active = receipt.shouldReply ? Object.freeze({
1534
- runId,
1535
- messageTimestamp: item.timestamp,
1536
- observedReplying: false
1537
- }) : void 0;
1538
- this.#latest = Object.freeze({ runId, status: receipt.shouldReply ? "pending" : "completed" });
1539
- this.#streaming = void 0;
1740
+ if (!this.#active) {
1741
+ this.#active = receipt.shouldReply ? Object.freeze({
1742
+ runId,
1743
+ messageTimestamp: item.timestamp,
1744
+ observedReplying: false
1745
+ }) : void 0;
1746
+ this.#latest = Object.freeze({ runId, status: receipt.shouldReply ? "pending" : "completed" });
1747
+ this.#streaming = void 0;
1748
+ }
1540
1749
  const accepted = Object.freeze({ messageId, runId });
1541
1750
  this.#lastSend = { key: idempotencyKey, text: input.text.trim(), receipt: accepted };
1542
1751
  return accepted;
@@ -1604,6 +1813,7 @@ var MlbSimulatorClient = class {
1604
1813
  const url = endpoint(this.#baseUrl, "/app/ws");
1605
1814
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
1606
1815
  let sequence = 0;
1816
+ let assistantIds = new Set(this.#messages.filter((message2) => message2.role === "assistant").map((message2) => message2.messageId));
1607
1817
  const reset = async () => {
1608
1818
  sequence = 0;
1609
1819
  this.#streaming = void 0;
@@ -1629,11 +1839,21 @@ var MlbSimulatorClient = class {
1629
1839
  continue;
1630
1840
  }
1631
1841
  if (frame.event !== "chat" && frame.event !== "ready") continue;
1632
- const state = await this.state({ signal: options.signal });
1842
+ let state = await this.state({ signal: options.signal });
1843
+ const nextAssistantIds = new Set(state.messages.filter((message2) => message2.role === "assistant").map((message2) => message2.messageId));
1844
+ const committedReply = [...nextAssistantIds].some((id) => !assistantIds.has(id));
1845
+ assistantIds = nextAssistantIds;
1846
+ if (committedReply && state.activeRun) {
1847
+ sequence = 0;
1848
+ this.#streaming = void 0;
1849
+ const { streaming: _streaming, ...snapshot } = state;
1850
+ state = snapshot;
1851
+ }
1633
1852
  if (state.latestRun?.runId === safeRunId && (state.latestRun.status === "completed" || state.latestRun.status === "failed")) {
1634
1853
  await options.onEvent({ type: state.latestRun.status, runId: safeRunId, status: state.latestRun.status });
1635
1854
  return;
1636
1855
  }
1856
+ await options.onEvent({ type: "state", runId: safeRunId, state, resetStreaming: committedReply });
1637
1857
  }
1638
1858
  if (options.signal?.aborted) return;
1639
1859
  } catch (cause) {
@@ -1728,6 +1948,42 @@ async function connectPreviewBridge(options) {
1728
1948
  const initial = await attach();
1729
1949
  if (initial.reused) return { reused: true, completion: Promise.resolve(), async close() {
1730
1950
  } };
1951
+ const pendingEvents = /* @__PURE__ */ new Map();
1952
+ let eventSequence = 0;
1953
+ let publishing;
1954
+ let retryEvents;
1955
+ const flushEvents = () => {
1956
+ if (!initial.liveEvents || signal.aborted || publishing || retryEvents || !pendingEvents.size) return;
1957
+ publishing = (async () => {
1958
+ while (!signal.aborted && pendingEvents.size) {
1959
+ const events = [...pendingEvents.values()].sort((a, b) => a.sequence - b.sequence);
1960
+ try {
1961
+ await call("api/preview/events", connectionId, { events });
1962
+ for (const event of events) if (pendingEvents.get(event.type) === event) pendingEvents.delete(event.type);
1963
+ } catch {
1964
+ if (!signal.aborted) retryEvents = setTimeout(() => {
1965
+ retryEvents = void 0;
1966
+ flushEvents();
1967
+ }, 500);
1968
+ break;
1969
+ }
1970
+ }
1971
+ })().finally(() => {
1972
+ publishing = void 0;
1973
+ });
1974
+ };
1975
+ const queueEvent = (type, value) => {
1976
+ if (!initial.liveEvents || signal.aborted) return;
1977
+ pendingEvents.set(type, { sequence: eventSequence++, type, value });
1978
+ flushEvents();
1979
+ };
1980
+ const snapshotEvents = () => {
1981
+ queueEvent("dev.state", options.controller.state());
1982
+ queueEvent("simulator.state", { state: options.controller.simulatorSnapshot() });
1983
+ };
1984
+ const unsubscribeDev = options.controller.subscribe((state) => queueEvent("dev.state", state));
1985
+ const unsubscribeSimulator = options.controller.subscribeSimulator(({ state }) => queueEvent("simulator.state", { state }));
1986
+ snapshotEvents();
1731
1987
  const watchdog = setInterval(() => {
1732
1988
  if (Date.now() - lastContact < 1e4 || stopping || interrupted) return;
1733
1989
  interrupted = true;
@@ -1801,12 +2057,18 @@ async function connectPreviewBridge(options) {
1801
2057
  if (status === 410) {
1802
2058
  await options.controller.stop();
1803
2059
  if ((await attach()).reused) throw new Error("\u5F53\u524D\u5DE5\u7A0B\u5DF2\u6709\u5176\u4ED6 CLI \u8FDE\u63A5\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C preview \u6253\u5F00\u540E\u53F0");
2060
+ snapshotEvents();
1804
2061
  } else if (status && status >= 400 && status < 500 && status !== 429) throw cause;
1805
2062
  }
1806
2063
  await delay(replies.size ? 20 : 500, void 0, { signal }).catch(() => void 0);
1807
2064
  }
1808
2065
  } finally {
1809
2066
  clearInterval(watchdog);
2067
+ unsubscribeDev();
2068
+ unsubscribeSimulator();
2069
+ if (retryEvents) clearTimeout(retryEvents);
2070
+ shutdown.abort();
2071
+ await publishing;
1810
2072
  }
1811
2073
  })();
1812
2074
  void completion.catch(() => void 0);