@a1hvdy/cc-openclaw 0.23.0 → 0.25.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.
Files changed (109) hide show
  1. package/dist/src/channels/telegram/injector.js +71 -0
  2. package/dist/src/channels/telegram/injector.js.map +1 -1
  3. package/dist/src/channels/telegram-mirror/burst-accumulator.d.ts +96 -0
  4. package/dist/src/channels/telegram-mirror/burst-accumulator.js +130 -0
  5. package/dist/src/channels/telegram-mirror/burst-accumulator.js.map +1 -0
  6. package/dist/src/channels/telegram-mirror/callback-mapping.d.ts +61 -0
  7. package/dist/src/channels/telegram-mirror/callback-mapping.js +99 -0
  8. package/dist/src/channels/telegram-mirror/callback-mapping.js.map +1 -0
  9. package/dist/src/channels/telegram-mirror/card-renderer.d.ts +74 -0
  10. package/dist/src/channels/telegram-mirror/card-renderer.js +131 -0
  11. package/dist/src/channels/telegram-mirror/card-renderer.js.map +1 -0
  12. package/dist/src/channels/telegram-mirror/commands.d.ts +142 -0
  13. package/dist/src/channels/telegram-mirror/commands.js +389 -0
  14. package/dist/src/channels/telegram-mirror/commands.js.map +1 -0
  15. package/dist/src/channels/telegram-mirror/compose-buffer.d.ts +71 -0
  16. package/dist/src/channels/telegram-mirror/compose-buffer.js +110 -0
  17. package/dist/src/channels/telegram-mirror/compose-buffer.js.map +1 -0
  18. package/dist/src/channels/telegram-mirror/cost-views.d.ts +58 -0
  19. package/dist/src/channels/telegram-mirror/cost-views.js +121 -0
  20. package/dist/src/channels/telegram-mirror/cost-views.js.map +1 -0
  21. package/dist/src/channels/telegram-mirror/failure/callback-data-overflow.d.ts +21 -0
  22. package/dist/src/channels/telegram-mirror/failure/callback-data-overflow.js +30 -0
  23. package/dist/src/channels/telegram-mirror/failure/callback-data-overflow.js.map +1 -0
  24. package/dist/src/channels/telegram-mirror/failure/gateway-down.d.ts +15 -0
  25. package/dist/src/channels/telegram-mirror/failure/gateway-down.js +27 -0
  26. package/dist/src/channels/telegram-mirror/failure/gateway-down.js.map +1 -0
  27. package/dist/src/channels/telegram-mirror/failure/in-flight-conflict.d.ts +15 -0
  28. package/dist/src/channels/telegram-mirror/failure/in-flight-conflict.js +27 -0
  29. package/dist/src/channels/telegram-mirror/failure/in-flight-conflict.js.map +1 -0
  30. package/dist/src/channels/telegram-mirror/failure/index.d.ts +23 -0
  31. package/dist/src/channels/telegram-mirror/failure/index.js +35 -0
  32. package/dist/src/channels/telegram-mirror/failure/index.js.map +1 -0
  33. package/dist/src/channels/telegram-mirror/failure/model-5xx.d.ts +16 -0
  34. package/dist/src/channels/telegram-mirror/failure/model-5xx.js +24 -0
  35. package/dist/src/channels/telegram-mirror/failure/model-5xx.js.map +1 -0
  36. package/dist/src/channels/telegram-mirror/failure/network-blip.d.ts +17 -0
  37. package/dist/src/channels/telegram-mirror/failure/network-blip.js +25 -0
  38. package/dist/src/channels/telegram-mirror/failure/network-blip.js.map +1 -0
  39. package/dist/src/channels/telegram-mirror/failure/pool-exhausted-fallback.d.ts +15 -0
  40. package/dist/src/channels/telegram-mirror/failure/pool-exhausted-fallback.js +24 -0
  41. package/dist/src/channels/telegram-mirror/failure/pool-exhausted-fallback.js.map +1 -0
  42. package/dist/src/channels/telegram-mirror/failure/rate-limit.d.ts +16 -0
  43. package/dist/src/channels/telegram-mirror/failure/rate-limit.js +26 -0
  44. package/dist/src/channels/telegram-mirror/failure/rate-limit.js.map +1 -0
  45. package/dist/src/channels/telegram-mirror/failure/returning-after-24h.d.ts +14 -0
  46. package/dist/src/channels/telegram-mirror/failure/returning-after-24h.js +33 -0
  47. package/dist/src/channels/telegram-mirror/failure/returning-after-24h.js.map +1 -0
  48. package/dist/src/channels/telegram-mirror/failure/types.d.ts +30 -0
  49. package/dist/src/channels/telegram-mirror/failure/types.js +9 -0
  50. package/dist/src/channels/telegram-mirror/failure/types.js.map +1 -0
  51. package/dist/src/channels/telegram-mirror/index.d.ts +30 -0
  52. package/dist/src/channels/telegram-mirror/index.js +41 -0
  53. package/dist/src/channels/telegram-mirror/index.js.map +1 -0
  54. package/dist/src/channels/telegram-mirror/plan-attachment.d.ts +120 -0
  55. package/dist/src/channels/telegram-mirror/plan-attachment.js +138 -0
  56. package/dist/src/channels/telegram-mirror/plan-attachment.js.map +1 -0
  57. package/dist/src/channels/telegram-mirror/quota-reader.d.ts +41 -0
  58. package/dist/src/channels/telegram-mirror/quota-reader.js +29 -0
  59. package/dist/src/channels/telegram-mirror/quota-reader.js.map +1 -0
  60. package/dist/src/channels/telegram-mirror/sessions-keyboard.d.ts +84 -0
  61. package/dist/src/channels/telegram-mirror/sessions-keyboard.js +113 -0
  62. package/dist/src/channels/telegram-mirror/sessions-keyboard.js.map +1 -0
  63. package/dist/src/channels/telegram-mirror/soak-log.d.ts +98 -0
  64. package/dist/src/channels/telegram-mirror/soak-log.js +136 -0
  65. package/dist/src/channels/telegram-mirror/soak-log.js.map +1 -0
  66. package/dist/src/channels/telegram-mirror/state-machine.d.ts +102 -0
  67. package/dist/src/channels/telegram-mirror/state-machine.js +117 -0
  68. package/dist/src/channels/telegram-mirror/state-machine.js.map +1 -0
  69. package/dist/src/channels/telegram-mirror/sync-commands.d.ts +63 -0
  70. package/dist/src/channels/telegram-mirror/sync-commands.js +51 -0
  71. package/dist/src/channels/telegram-mirror/sync-commands.js.map +1 -0
  72. package/dist/src/channels/telegram-mirror/threshold-watcher.d.ts +54 -0
  73. package/dist/src/channels/telegram-mirror/threshold-watcher.js +77 -0
  74. package/dist/src/channels/telegram-mirror/threshold-watcher.js.map +1 -0
  75. package/dist/src/command-router/cc-handler.js +13 -30
  76. package/dist/src/command-router/cc-handler.js.map +1 -1
  77. package/dist/src/engines/persistent-session.js +5 -0
  78. package/dist/src/engines/persistent-session.js.map +1 -1
  79. package/dist/src/index.js +22 -20
  80. package/dist/src/index.js.map +1 -1
  81. package/dist/src/lib/auto-recovery.js +1 -1
  82. package/dist/src/lib/auto-recovery.js.map +1 -1
  83. package/dist/src/lib/drift-detector.js +1 -1
  84. package/dist/src/lib/drift-detector.js.map +1 -1
  85. package/dist/src/lib/error-renderer.d.ts +23 -0
  86. package/dist/src/lib/error-renderer.js +106 -0
  87. package/dist/src/lib/error-renderer.js.map +1 -0
  88. package/dist/src/lib/perf/speculative-bubble.d.ts +27 -0
  89. package/dist/src/lib/perf/speculative-bubble.js +36 -0
  90. package/dist/src/lib/perf/speculative-bubble.js.map +1 -0
  91. package/dist/src/lib/session-registry.d.ts +66 -0
  92. package/dist/src/lib/session-registry.js +188 -0
  93. package/dist/src/lib/session-registry.js.map +1 -0
  94. package/dist/src/lib/telegram-bot-api.d.ts +100 -0
  95. package/dist/src/lib/telegram-bot-api.js +204 -0
  96. package/dist/src/lib/telegram-bot-api.js.map +1 -0
  97. package/dist/src/openai-compat/openai-compat.d.ts +2 -0
  98. package/dist/src/openai-compat/openai-compat.js +85 -0
  99. package/dist/src/openai-compat/openai-compat.js.map +1 -1
  100. package/dist/src/patches/sysprompt-strip.spec.d.ts +33 -0
  101. package/dist/src/patches/sysprompt-strip.spec.js +53 -0
  102. package/dist/src/patches/sysprompt-strip.spec.js.map +1 -0
  103. package/dist/src/session/session-manager.d.ts +4 -0
  104. package/dist/src/session/session-manager.js +99 -5
  105. package/dist/src/session/session-manager.js.map +1 -1
  106. package/package.json +1 -1
  107. package/dist/src/lib/perf/resident-cli-pool.d.ts +0 -39
  108. package/dist/src/lib/perf/resident-cli-pool.js +0 -60
  109. package/dist/src/lib/perf/resident-cli-pool.js.map +0 -1
@@ -0,0 +1,29 @@
1
+ /**
2
+ * src/channels/telegram-mirror/quota-reader.ts — v0.25.0 M4 stub.
3
+ *
4
+ * Minimal QuotaSnapshot interface and a zero-valued reader so /cost in M4
5
+ * has something to render. M11 replaces the stub with a wire-up to the
6
+ * production status-tee-reader.ts that the existing /cost handler in the
7
+ * legacy live card already consumes.
8
+ *
9
+ * Decision-link: M11 owns threshold-notification + detail-view wiring;
10
+ * keeping the surface here in M4 isolates the command-handler layer from
11
+ * the status-tee read path (so M11 can swap implementations without
12
+ * touching commands.ts).
13
+ */
14
+ /**
15
+ * Stub reader for M4 — returns a known-zero snapshot. M11 replaces this
16
+ * with a wire to status-tee-reader.ts. Tests can pass their own reader
17
+ * directly to the cost command for fixture-based assertions.
18
+ */
19
+ export const stubQuotaReader = {
20
+ read() {
21
+ return {
22
+ maxPercent: 0,
23
+ weeklyBurn: 0,
24
+ topSessions: [],
25
+ perDayRollup: [],
26
+ };
27
+ },
28
+ };
29
+ //# sourceMappingURL=quota-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota-reader.js","sourceRoot":"","sources":["../../../../src/channels/telegram-mirror/quota-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAwBH;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAgB;IAC1C,IAAI;QACF,OAAO;YACL,UAAU,EAAE,CAAC;YACb,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,EAAE;SACjB,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * src/channels/telegram-mirror/sessions-keyboard.ts — v0.25.0 M3.
3
+ *
4
+ * Builds the inline keyboard for /sessions. Pure function — takes a row
5
+ * list + page index + a CallbackMap, returns Telegram's inline_keyboard
6
+ * shape. Caller is responsible for sending/editing the surrounding
7
+ * Telegram message via the dispatch layer.
8
+ *
9
+ * Decision refs:
10
+ * • ADR-003 — slug ↔ sessionName comes from session-registry; the row
11
+ * list here is a denormalised SessionRow because the keyboard also
12
+ * needs live state + lastUsedAt, which the registry doesn't track
13
+ * (state lives in session-manager, time-since lives in the registry's
14
+ * lastUsedAt). The handler in M4 stitches them together.
15
+ * • ADR-004 — every callback_data slot is a CallbackMap id, not a raw
16
+ * JSON payload. Keeps every button under the 64-byte cap by design.
17
+ * • ADR-005 — page size is 8; 9th row is "▶ More" when more pages exist.
18
+ */
19
+ import type { CallbackMap } from './callback-mapping.js';
20
+ export declare const SESSIONS_PAGE_SIZE = 8;
21
+ export type SessionState = 'running' | 'stopped' | 'idle';
22
+ export interface SessionRow {
23
+ slug: string;
24
+ sessionName: string;
25
+ state: SessionState;
26
+ /** ISO timestamp — the registry's lastUsedAt. */
27
+ lastUsedAt: string;
28
+ }
29
+ export interface InlineButton {
30
+ text: string;
31
+ callback_data: string;
32
+ }
33
+ export interface SessionsKeyboardResult {
34
+ inline_keyboard: InlineButton[][];
35
+ /** Zero-indexed page rendered. */
36
+ page: number;
37
+ /** Total page count (≥ 1 even when rows is empty). */
38
+ pageCount: number;
39
+ /** True when there is at least one page after the rendered one. */
40
+ hasMore: boolean;
41
+ }
42
+ /**
43
+ * Compact relative-time string for a row's lastUsedAt.
44
+ * < 60s → "<1m"
45
+ * < 1h → "Nm"
46
+ * < 1d → "Nh"
47
+ * else → "Nd"
48
+ * Designed to fit in 4 chars so the row stays narrow on phone Telegram.
49
+ */
50
+ export declare function formatLastActivity(lastUsedAt: string, now?: number): string;
51
+ /**
52
+ * Format a row label. "cc-openclaw · 🟢 · 2m"
53
+ */
54
+ export declare function formatSessionRowLabel(row: SessionRow, now?: number): string;
55
+ export interface SessionsKeyboardOpts {
56
+ rows: SessionRow[];
57
+ /** Zero-indexed page; clamped into [0, pageCount-1]. */
58
+ page?: number;
59
+ callbackMap: CallbackMap;
60
+ /** Override clock for tests. */
61
+ now?: number;
62
+ }
63
+ /**
64
+ * Build the full /sessions inline keyboard. Layout:
65
+ *
66
+ * Row 1..N session rows (one button each) (N ≤ 8)
67
+ * Row N+1 "▶ More" — only when hasMore===true
68
+ * Row last "➕ New" "🗑 Stop" "✖ Close" (always)
69
+ *
70
+ * Every button's callback_data is a CallbackMap id pointing at a payload:
71
+ * session row → { action: 'switch', slug }
72
+ * ▶ More → { action: 'page', page: N+1 }
73
+ * ➕ New → { action: 'new' }
74
+ * 🗑 Stop → { action: 'stop' }
75
+ * ✖ Close → { action: 'close' }
76
+ */
77
+ export declare function buildSessionsKeyboard(opts: SessionsKeyboardOpts): SessionsKeyboardResult;
78
+ /**
79
+ * Helper: every button's callback_data must fit in 64 UTF-8 bytes per
80
+ * Telegram's API. We enforce this at build time as a defense-in-depth
81
+ * check — even an 8-char id is well under, but a future refactor that
82
+ * stuffs raw JSON would silently break Telegram without this assertion.
83
+ */
84
+ export declare function assertCallbackDataBudget(keyboard: SessionsKeyboardResult): void;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * src/channels/telegram-mirror/sessions-keyboard.ts — v0.25.0 M3.
3
+ *
4
+ * Builds the inline keyboard for /sessions. Pure function — takes a row
5
+ * list + page index + a CallbackMap, returns Telegram's inline_keyboard
6
+ * shape. Caller is responsible for sending/editing the surrounding
7
+ * Telegram message via the dispatch layer.
8
+ *
9
+ * Decision refs:
10
+ * • ADR-003 — slug ↔ sessionName comes from session-registry; the row
11
+ * list here is a denormalised SessionRow because the keyboard also
12
+ * needs live state + lastUsedAt, which the registry doesn't track
13
+ * (state lives in session-manager, time-since lives in the registry's
14
+ * lastUsedAt). The handler in M4 stitches them together.
15
+ * • ADR-004 — every callback_data slot is a CallbackMap id, not a raw
16
+ * JSON payload. Keeps every button under the 64-byte cap by design.
17
+ * • ADR-005 — page size is 8; 9th row is "▶ More" when more pages exist.
18
+ */
19
+ export const SESSIONS_PAGE_SIZE = 8;
20
+ const STATE_EMOJI = {
21
+ running: '🟢',
22
+ stopped: '⚫',
23
+ idle: '🟡',
24
+ };
25
+ /**
26
+ * Compact relative-time string for a row's lastUsedAt.
27
+ * < 60s → "<1m"
28
+ * < 1h → "Nm"
29
+ * < 1d → "Nh"
30
+ * else → "Nd"
31
+ * Designed to fit in 4 chars so the row stays narrow on phone Telegram.
32
+ */
33
+ export function formatLastActivity(lastUsedAt, now = Date.now()) {
34
+ const t = new Date(lastUsedAt).getTime();
35
+ if (!Number.isFinite(t))
36
+ return '?';
37
+ const ms = Math.max(0, now - t);
38
+ if (ms < 60_000)
39
+ return '<1m';
40
+ if (ms < 3_600_000)
41
+ return `${Math.floor(ms / 60_000)}m`;
42
+ if (ms < 86_400_000)
43
+ return `${Math.floor(ms / 3_600_000)}h`;
44
+ return `${Math.floor(ms / 86_400_000)}d`;
45
+ }
46
+ /**
47
+ * Format a row label. "cc-openclaw · 🟢 · 2m"
48
+ */
49
+ export function formatSessionRowLabel(row, now = Date.now()) {
50
+ return `${row.slug} · ${STATE_EMOJI[row.state]} · ${formatLastActivity(row.lastUsedAt, now)}`;
51
+ }
52
+ /**
53
+ * Build the full /sessions inline keyboard. Layout:
54
+ *
55
+ * Row 1..N session rows (one button each) (N ≤ 8)
56
+ * Row N+1 "▶ More" — only when hasMore===true
57
+ * Row last "➕ New" "🗑 Stop" "✖ Close" (always)
58
+ *
59
+ * Every button's callback_data is a CallbackMap id pointing at a payload:
60
+ * session row → { action: 'switch', slug }
61
+ * ▶ More → { action: 'page', page: N+1 }
62
+ * ➕ New → { action: 'new' }
63
+ * 🗑 Stop → { action: 'stop' }
64
+ * ✖ Close → { action: 'close' }
65
+ */
66
+ export function buildSessionsKeyboard(opts) {
67
+ const { rows, callbackMap, now = Date.now() } = opts;
68
+ const pageCount = Math.max(1, Math.ceil(rows.length / SESSIONS_PAGE_SIZE));
69
+ const page = Math.min(Math.max(0, opts.page ?? 0), pageCount - 1);
70
+ const start = page * SESSIONS_PAGE_SIZE;
71
+ const slice = rows.slice(start, start + SESSIONS_PAGE_SIZE);
72
+ const hasMore = page < pageCount - 1;
73
+ const inline_keyboard = [];
74
+ for (const row of slice) {
75
+ inline_keyboard.push([
76
+ {
77
+ text: formatSessionRowLabel(row, now),
78
+ callback_data: callbackMap.create({ action: 'switch', slug: row.slug }),
79
+ },
80
+ ]);
81
+ }
82
+ if (hasMore) {
83
+ inline_keyboard.push([
84
+ {
85
+ text: '▶ More',
86
+ callback_data: callbackMap.create({ action: 'page', page: page + 1 }),
87
+ },
88
+ ]);
89
+ }
90
+ inline_keyboard.push([
91
+ { text: '➕ New', callback_data: callbackMap.create({ action: 'new' }) },
92
+ { text: '🗑 Stop', callback_data: callbackMap.create({ action: 'stop' }) },
93
+ { text: '✖ Close', callback_data: callbackMap.create({ action: 'close' }) },
94
+ ]);
95
+ return { inline_keyboard, page, pageCount, hasMore };
96
+ }
97
+ /**
98
+ * Helper: every button's callback_data must fit in 64 UTF-8 bytes per
99
+ * Telegram's API. We enforce this at build time as a defense-in-depth
100
+ * check — even an 8-char id is well under, but a future refactor that
101
+ * stuffs raw JSON would silently break Telegram without this assertion.
102
+ */
103
+ export function assertCallbackDataBudget(keyboard) {
104
+ for (const row of keyboard.inline_keyboard) {
105
+ for (const btn of row) {
106
+ const bytes = Buffer.byteLength(btn.callback_data, 'utf8');
107
+ if (bytes > 64) {
108
+ throw new Error(`[cc-openclaw/telegram-mirror/sessions-keyboard] callback_data exceeds 64-byte cap (${bytes}b) on button "${btn.text}"`);
109
+ }
110
+ }
111
+ }
112
+ }
113
+ //# sourceMappingURL=sessions-keyboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions-keyboard.js","sourceRoot":"","sources":["../../../../src/channels/telegram-mirror/sessions-keyboard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * src/channels/telegram-mirror/soak-log.ts — v0.25.0 M14.
3
+ *
4
+ * Daily JSONL incident + activity log for the 2-week soak (M14 → M15
5
+ * cutover gate). Writes append-only rows to
6
+ * workspace/memory/mirror-soak-<YYYY-MM-DD>.jsonl
7
+ * per PRP §5 M14. The file rolls at UTC midnight (date-stamped path).
8
+ *
9
+ * Three row types per spec:
10
+ * • laptop-check — A1 self-reports opened-laptop-today via /soak-check.
11
+ * • failure-incident — fires once per M12 handler invocation in soak.
12
+ * • slash-invocation — one row per slash command dispatch.
13
+ *
14
+ * Idempotency: a single laptop-check per UTC day. recordLaptopCheck()
15
+ * returns false on the second call within a day so the /soak-check
16
+ * handler can surface "already recorded today" UX rather than logging
17
+ * duplicates.
18
+ *
19
+ * Decision-link: this file is the only filesystem-touching mirror module
20
+ * outside session-registry (M1). Both share the atomic-write-via-rename
21
+ * idiom; soak-log appends so it doesn't need the tmpfile dance, but the
22
+ * path resolution is the same shape.
23
+ */
24
+ export type SoakRow = SoakLaptopCheckRow | SoakFailureRow | SoakSlashRow;
25
+ export interface SoakLaptopCheckRow {
26
+ ts: string;
27
+ type: 'laptop-check';
28
+ openedLaptop: boolean;
29
+ }
30
+ export interface SoakFailureRow {
31
+ ts: string;
32
+ type: 'failure-incident';
33
+ mode: string;
34
+ chatId: string | number;
35
+ info?: unknown;
36
+ }
37
+ export interface SoakSlashRow {
38
+ ts: string;
39
+ type: 'slash-invocation';
40
+ cmd: string;
41
+ chatId: string | number;
42
+ }
43
+ export interface SoakLoggerOptions {
44
+ /** Override base dir for tests (defaults to ~/.openclaw/workspace/memory). */
45
+ dir?: string;
46
+ /** Override clock for tests. */
47
+ now?: () => Date;
48
+ }
49
+ export declare class SoakLogger {
50
+ private readonly dir;
51
+ private readonly now;
52
+ constructor(opts?: SoakLoggerOptions);
53
+ /**
54
+ * Absolute path of today's JSONL file. Honors the configured clock.
55
+ */
56
+ pathForToday(): string;
57
+ /**
58
+ * Append a row to today's file. Creates the parent directory if missing.
59
+ * Each row is `JSON.stringify(row) + '\n'`.
60
+ */
61
+ append(row: SoakRow): void;
62
+ /**
63
+ * Read all rows for today (parsing each line). Returns [] when the
64
+ * file is missing OR when every line is malformed.
65
+ */
66
+ readToday(): SoakRow[];
67
+ /**
68
+ * Returns true iff a laptop-check row has already been written today.
69
+ * Used by /soak-check's once-per-day gate.
70
+ */
71
+ hasLaptopCheckToday(): boolean;
72
+ /**
73
+ * Idempotent laptop-check writer. Returns true when the row was
74
+ * persisted, false when a check was already recorded today.
75
+ */
76
+ recordLaptopCheck(openedLaptop: boolean): boolean;
77
+ /**
78
+ * Failure-incident logger — called by the dispatcher when a M12
79
+ * handler fires. The info payload is opaque JSON; the schema is
80
+ * per-mode and lives in the handler's documentation.
81
+ */
82
+ recordFailure(mode: string, chatId: string | number, info?: unknown): void;
83
+ /**
84
+ * Slash-invocation logger — called once per dispatchCommand call by
85
+ * the dispatcher (M5+). Cheap to record and lets the soak surface
86
+ * which commands A1 actually uses on phone.
87
+ */
88
+ recordSlash(cmd: string, chatId: string | number): void;
89
+ /**
90
+ * Count rows by type for the day — used by M15's "5/7 no-laptop days"
91
+ * gate without having to ship a separate aggregator.
92
+ */
93
+ countByType(): {
94
+ 'laptop-check': number;
95
+ 'failure-incident': number;
96
+ 'slash-invocation': number;
97
+ };
98
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * src/channels/telegram-mirror/soak-log.ts — v0.25.0 M14.
3
+ *
4
+ * Daily JSONL incident + activity log for the 2-week soak (M14 → M15
5
+ * cutover gate). Writes append-only rows to
6
+ * workspace/memory/mirror-soak-<YYYY-MM-DD>.jsonl
7
+ * per PRP §5 M14. The file rolls at UTC midnight (date-stamped path).
8
+ *
9
+ * Three row types per spec:
10
+ * • laptop-check — A1 self-reports opened-laptop-today via /soak-check.
11
+ * • failure-incident — fires once per M12 handler invocation in soak.
12
+ * • slash-invocation — one row per slash command dispatch.
13
+ *
14
+ * Idempotency: a single laptop-check per UTC day. recordLaptopCheck()
15
+ * returns false on the second call within a day so the /soak-check
16
+ * handler can surface "already recorded today" UX rather than logging
17
+ * duplicates.
18
+ *
19
+ * Decision-link: this file is the only filesystem-touching mirror module
20
+ * outside session-registry (M1). Both share the atomic-write-via-rename
21
+ * idiom; soak-log appends so it doesn't need the tmpfile dance, but the
22
+ * path resolution is the same shape.
23
+ */
24
+ import { appendFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
25
+ import { homedir } from 'os';
26
+ import { join, dirname } from 'path';
27
+ function defaultDir() {
28
+ return join(homedir(), '.openclaw', 'workspace', 'memory');
29
+ }
30
+ function ymd(d) {
31
+ return d.toISOString().slice(0, 10); // YYYY-MM-DD (UTC)
32
+ }
33
+ export class SoakLogger {
34
+ dir;
35
+ now;
36
+ constructor(opts = {}) {
37
+ this.dir = opts.dir ?? defaultDir();
38
+ this.now = opts.now ?? (() => new Date());
39
+ }
40
+ /**
41
+ * Absolute path of today's JSONL file. Honors the configured clock.
42
+ */
43
+ pathForToday() {
44
+ return join(this.dir, `mirror-soak-${ymd(this.now())}.jsonl`);
45
+ }
46
+ /**
47
+ * Append a row to today's file. Creates the parent directory if missing.
48
+ * Each row is `JSON.stringify(row) + '\n'`.
49
+ */
50
+ append(row) {
51
+ const path = this.pathForToday();
52
+ mkdirSync(dirname(path), { recursive: true });
53
+ appendFileSync(path, JSON.stringify(row) + '\n');
54
+ }
55
+ /**
56
+ * Read all rows for today (parsing each line). Returns [] when the
57
+ * file is missing OR when every line is malformed.
58
+ */
59
+ readToday() {
60
+ const path = this.pathForToday();
61
+ if (!existsSync(path))
62
+ return [];
63
+ const raw = readFileSync(path, 'utf8');
64
+ const out = [];
65
+ for (const line of raw.split('\n')) {
66
+ if (!line.trim())
67
+ continue;
68
+ try {
69
+ out.push(JSON.parse(line));
70
+ }
71
+ catch {
72
+ /* skip malformed line — defensive */
73
+ }
74
+ }
75
+ return out;
76
+ }
77
+ /**
78
+ * Returns true iff a laptop-check row has already been written today.
79
+ * Used by /soak-check's once-per-day gate.
80
+ */
81
+ hasLaptopCheckToday() {
82
+ return this.readToday().some((r) => r.type === 'laptop-check');
83
+ }
84
+ /**
85
+ * Idempotent laptop-check writer. Returns true when the row was
86
+ * persisted, false when a check was already recorded today.
87
+ */
88
+ recordLaptopCheck(openedLaptop) {
89
+ if (this.hasLaptopCheckToday())
90
+ return false;
91
+ this.append({
92
+ ts: this.now().toISOString(),
93
+ type: 'laptop-check',
94
+ openedLaptop,
95
+ });
96
+ return true;
97
+ }
98
+ /**
99
+ * Failure-incident logger — called by the dispatcher when a M12
100
+ * handler fires. The info payload is opaque JSON; the schema is
101
+ * per-mode and lives in the handler's documentation.
102
+ */
103
+ recordFailure(mode, chatId, info) {
104
+ this.append({
105
+ ts: this.now().toISOString(),
106
+ type: 'failure-incident',
107
+ mode,
108
+ chatId,
109
+ info,
110
+ });
111
+ }
112
+ /**
113
+ * Slash-invocation logger — called once per dispatchCommand call by
114
+ * the dispatcher (M5+). Cheap to record and lets the soak surface
115
+ * which commands A1 actually uses on phone.
116
+ */
117
+ recordSlash(cmd, chatId) {
118
+ this.append({
119
+ ts: this.now().toISOString(),
120
+ type: 'slash-invocation',
121
+ cmd,
122
+ chatId,
123
+ });
124
+ }
125
+ /**
126
+ * Count rows by type for the day — used by M15's "5/7 no-laptop days"
127
+ * gate without having to ship a separate aggregator.
128
+ */
129
+ countByType() {
130
+ const out = { 'laptop-check': 0, 'failure-incident': 0, 'slash-invocation': 0 };
131
+ for (const r of this.readToday())
132
+ out[r.type] += 1;
133
+ return out;
134
+ }
135
+ }
136
+ //# sourceMappingURL=soak-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soak-log.js","sourceRoot":"","sources":["../../../../src/channels/telegram-mirror/soak-log.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * src/channels/telegram-mirror/state-machine.ts — v0.25.0 M2.
3
+ *
4
+ * Per-chat turn-lifecycle state machine for the Telegram Terminal Mirror.
5
+ * Consumes Claude stream events (tool_use / tool_result / text / turn_done)
6
+ * and produces a Turn object the card-renderer (sibling module) can format
7
+ * into Telegram message bodies.
8
+ *
9
+ * Naming note: this is NOT a 1:1 port of the legacy state-machine.ts —
10
+ * legacy is a message_sending cross-path-dedup hook. Per ADR-001 (mimic
11
+ * the terminal flow) the mirror's state-machine models what a user sees
12
+ * during a turn (running tools → completion), which is closer to a UI
13
+ * controller than legacy's API-dispatch hook. The name is reused for
14
+ * cross-reference clarity; the shape diverges intentionally.
15
+ *
16
+ * Pure module — no I/O, no Telegram-API calls. Wiring into register() and
17
+ * the actual gateway event bus lands in M3/M4 once /sessions provides a
18
+ * user-facing entry point. M2's scope: the abstraction + tests.
19
+ */
20
+ /**
21
+ * Stream event shape (mirrors the subset of legacy card-renderer.ts
22
+ * StreamEvent that the mirror actually consumes). Re-exported here so
23
+ * dependents don't need to import from the legacy directory.
24
+ */
25
+ export interface StreamEvent {
26
+ type: string;
27
+ id?: string;
28
+ tool_use_id?: string;
29
+ tool?: {
30
+ name?: string;
31
+ input?: Record<string, unknown>;
32
+ };
33
+ tool_name?: string;
34
+ input?: Record<string, unknown>;
35
+ content?: unknown;
36
+ output?: unknown;
37
+ is_error?: boolean;
38
+ error?: unknown;
39
+ result?: string;
40
+ }
41
+ export type TurnState = 'working' | 'done';
42
+ export interface ToolCallRecord {
43
+ /** Tool name (e.g. "Bash", "Read", "Edit"). */
44
+ name: string;
45
+ /** Tool input arguments (snapshot at tool_use time). */
46
+ input: Record<string, unknown>;
47
+ /** Telegram-readable tool-use id, used to match tool_result events. */
48
+ toolUseId: string;
49
+ /** Set when the matching tool_result event arrives. */
50
+ result?: unknown;
51
+ /** True if the tool_result event flagged is_error/error. */
52
+ isError?: boolean;
53
+ }
54
+ export interface Turn {
55
+ /** Telegram chat id this turn is rendering into. */
56
+ chatId: string;
57
+ /** Lifecycle state. */
58
+ state: TurnState;
59
+ /** Monotonic timestamp (Date.now()) when the turn started. */
60
+ startedAt: number;
61
+ /** Set when the turn transitions to 'done'. */
62
+ endedAt?: number;
63
+ /** Tool calls observed during the turn, in arrival order. */
64
+ toolCalls: ToolCallRecord[];
65
+ /** Accumulated assistant text (concatenation of text events). */
66
+ assistantText: string;
67
+ /** Accumulated extended-thinking text (M6). Rendered above assistant text. */
68
+ thinkingText: string;
69
+ }
70
+ export declare class TurnStateMachine {
71
+ private readonly turns;
72
+ /**
73
+ * Start (or restart) a turn for the given chat. If a turn already exists,
74
+ * its 'done' state is preserved as the prior turn until a new start
75
+ * overwrites it — the mirror does not keep history at this layer.
76
+ */
77
+ start(chatId: string, now?: number): Turn;
78
+ /**
79
+ * Apply a stream event. Returns the updated turn, or undefined if no
80
+ * turn is active for the chat. The state machine accepts:
81
+ * - tool_use → push a ToolCallRecord
82
+ * - tool_result → resolve the matching ToolCallRecord
83
+ * - text → append to assistantText
84
+ * Other event types are ignored at this layer; downstream milestones add
85
+ * thinking/insight handling (M6) and plan-mode handling (M9).
86
+ */
87
+ applyEvent(chatId: string, event: StreamEvent): Turn | undefined;
88
+ /**
89
+ * End the active turn for chatId. Transitions state to 'done' and stamps
90
+ * endedAt. Returns the finalized turn, or undefined if no turn was active.
91
+ */
92
+ end(chatId: string, now?: number): Turn | undefined;
93
+ /**
94
+ * Returns the active or most-recently-ended turn for chatId.
95
+ */
96
+ getTurn(chatId: string): Turn | undefined;
97
+ /**
98
+ * Drop the cached turn. Test-only convenience; production code starts a
99
+ * new turn instead.
100
+ */
101
+ clear(chatId: string): void;
102
+ }