@songsid/agend 2.1.4-beta.8 → 2.1.4

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 (173) hide show
  1. package/README.md +1 -1
  2. package/README.zh-TW.md +1 -1
  3. package/dist/agent-cli-instructions.md +2 -2
  4. package/dist/agent-endpoint.js +1 -1
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/backend/antigravity.d.ts +12 -8
  7. package/dist/backend/antigravity.js +189 -42
  8. package/dist/backend/antigravity.js.map +1 -1
  9. package/dist/backend/claude-code.d.ts +87 -23
  10. package/dist/backend/claude-code.js +361 -46
  11. package/dist/backend/claude-code.js.map +1 -1
  12. package/dist/backend/codex.d.ts +18 -0
  13. package/dist/backend/codex.js +139 -5
  14. package/dist/backend/codex.js.map +1 -1
  15. package/dist/backend/gemini-cli.js +2 -2
  16. package/dist/backend/gemini-cli.js.map +1 -1
  17. package/dist/backend/grok.js +2 -2
  18. package/dist/backend/grok.js.map +1 -1
  19. package/dist/backend/kiro.d.ts +52 -2
  20. package/dist/backend/kiro.js +267 -12
  21. package/dist/backend/kiro.js.map +1 -1
  22. package/dist/backend/opencode.js +17 -4
  23. package/dist/backend/opencode.js.map +1 -1
  24. package/dist/backend/types.d.ts +122 -3
  25. package/dist/backend/types.js +5 -1
  26. package/dist/backend/types.js.map +1 -1
  27. package/dist/backend-outage.d.ts +61 -0
  28. package/dist/backend-outage.js +71 -0
  29. package/dist/backend-outage.js.map +1 -0
  30. package/dist/channel/adapters/discord.d.ts +44 -2
  31. package/dist/channel/adapters/discord.js +529 -89
  32. package/dist/channel/adapters/discord.js.map +1 -1
  33. package/dist/channel/adapters/telegram.d.ts +30 -0
  34. package/dist/channel/adapters/telegram.js +221 -19
  35. package/dist/channel/adapters/telegram.js.map +1 -1
  36. package/dist/channel/agy-mcp-launcher.d.ts +2 -0
  37. package/dist/channel/agy-mcp-launcher.js +28 -0
  38. package/dist/channel/agy-mcp-launcher.js.map +1 -0
  39. package/dist/channel/ipc-bridge.d.ts +2 -1
  40. package/dist/channel/ipc-bridge.js +33 -4
  41. package/dist/channel/ipc-bridge.js.map +1 -1
  42. package/dist/channel/markdown-chunk.d.ts +42 -0
  43. package/dist/channel/markdown-chunk.js +205 -0
  44. package/dist/channel/markdown-chunk.js.map +1 -0
  45. package/dist/channel/mcp-server.js +34 -5
  46. package/dist/channel/mcp-server.js.map +1 -1
  47. package/dist/channel/mcp-tools.js +5 -3
  48. package/dist/channel/mcp-tools.js.map +1 -1
  49. package/dist/channel/message-queue.js +64 -8
  50. package/dist/channel/message-queue.js.map +1 -1
  51. package/dist/channel/types.d.ts +31 -1
  52. package/dist/classic-channel-manager.d.ts +65 -0
  53. package/dist/classic-channel-manager.js +172 -3
  54. package/dist/classic-channel-manager.js.map +1 -1
  55. package/dist/cli.js +124 -83
  56. package/dist/cli.js.map +1 -1
  57. package/dist/config-validator.js +16 -0
  58. package/dist/config-validator.js.map +1 -1
  59. package/dist/config.d.ts +4 -0
  60. package/dist/config.js +12 -1
  61. package/dist/config.js.map +1 -1
  62. package/dist/cross-instance-envelope.d.ts +6 -0
  63. package/dist/cross-instance-envelope.js +30 -0
  64. package/dist/cross-instance-envelope.js.map +1 -0
  65. package/dist/daemon.d.ts +538 -11
  66. package/dist/daemon.js +2272 -288
  67. package/dist/daemon.js.map +1 -1
  68. package/dist/doctor.d.ts +41 -0
  69. package/dist/doctor.js +267 -0
  70. package/dist/doctor.js.map +1 -0
  71. package/dist/fleet-context.d.ts +63 -0
  72. package/dist/fleet-context.js.map +1 -1
  73. package/dist/fleet-manager.d.ts +424 -11
  74. package/dist/fleet-manager.js +2753 -444
  75. package/dist/fleet-manager.js.map +1 -1
  76. package/dist/fleet-yaml-slim.d.ts +10 -0
  77. package/dist/fleet-yaml-slim.js +59 -0
  78. package/dist/fleet-yaml-slim.js.map +1 -0
  79. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +1 -0
  80. package/dist/general-knowledge/skills/model-discovery/SKILL.md +63 -16
  81. package/dist/general-knowledge/skills/session-management/SKILL.md +171 -8
  82. package/dist/instance-lifecycle.d.ts +114 -1
  83. package/dist/instance-lifecycle.js +404 -14
  84. package/dist/instance-lifecycle.js.map +1 -1
  85. package/dist/instructions.js +42 -4
  86. package/dist/instructions.js.map +1 -1
  87. package/dist/locale.js +1024 -310
  88. package/dist/locale.js.map +1 -1
  89. package/dist/login-flows.d.ts +91 -0
  90. package/dist/login-flows.js +170 -0
  91. package/dist/login-flows.js.map +1 -0
  92. package/dist/login-manager.d.ts +63 -0
  93. package/dist/login-manager.js +134 -0
  94. package/dist/login-manager.js.map +1 -0
  95. package/dist/network-family.d.ts +18 -0
  96. package/dist/network-family.js +20 -0
  97. package/dist/network-family.js.map +1 -0
  98. package/dist/outbound-handlers.d.ts +16 -1
  99. package/dist/outbound-handlers.js +174 -40
  100. package/dist/outbound-handlers.js.map +1 -1
  101. package/dist/outbound-schemas.d.ts +10 -3
  102. package/dist/outbound-schemas.js +10 -0
  103. package/dist/outbound-schemas.js.map +1 -1
  104. package/dist/pane-input-residue.d.ts +52 -0
  105. package/dist/pane-input-residue.js +107 -0
  106. package/dist/pane-input-residue.js.map +1 -0
  107. package/dist/process-memory.d.ts +6 -0
  108. package/dist/process-memory.js +45 -0
  109. package/dist/process-memory.js.map +1 -1
  110. package/dist/reply-dedup.d.ts +7 -8
  111. package/dist/reply-dedup.js +0 -0
  112. package/dist/reply-dedup.js.map +1 -1
  113. package/dist/restart-progress.d.ts +2 -0
  114. package/dist/restart-progress.js +3 -0
  115. package/dist/restart-progress.js.map +1 -1
  116. package/dist/scheduler/db.d.ts +12 -0
  117. package/dist/scheduler/db.js +59 -0
  118. package/dist/scheduler/db.js.map +1 -1
  119. package/dist/service-installer.d.ts +11 -0
  120. package/dist/service-installer.js +84 -18
  121. package/dist/service-installer.js.map +1 -1
  122. package/dist/settings-api.js +1 -1
  123. package/dist/settings-api.js.map +1 -1
  124. package/dist/setup-wizard.js +2 -2
  125. package/dist/setup-wizard.js.map +1 -1
  126. package/dist/spawn-gate.d.ts +30 -0
  127. package/dist/spawn-gate.js +79 -0
  128. package/dist/spawn-gate.js.map +1 -0
  129. package/dist/steer-capability.d.ts +1 -0
  130. package/dist/steer-capability.js +6 -0
  131. package/dist/steer-capability.js.map +1 -0
  132. package/dist/storm-window.d.ts +83 -0
  133. package/dist/storm-window.js +251 -0
  134. package/dist/storm-window.js.map +1 -0
  135. package/dist/tips.d.ts +44 -0
  136. package/dist/tips.js +355 -0
  137. package/dist/tips.js.map +1 -0
  138. package/dist/tmux-manager.d.ts +16 -0
  139. package/dist/tmux-manager.js +110 -25
  140. package/dist/tmux-manager.js.map +1 -1
  141. package/dist/tool-progress.d.ts +40 -0
  142. package/dist/tool-progress.js +289 -0
  143. package/dist/tool-progress.js.map +1 -0
  144. package/dist/topic-commands.d.ts +45 -6
  145. package/dist/topic-commands.js +394 -66
  146. package/dist/topic-commands.js.map +1 -1
  147. package/dist/transcript-monitor.d.ts +15 -2
  148. package/dist/transcript-monitor.js +63 -17
  149. package/dist/transcript-monitor.js.map +1 -1
  150. package/dist/transcript-sources.d.ts +131 -0
  151. package/dist/transcript-sources.js +580 -0
  152. package/dist/transcript-sources.js.map +1 -0
  153. package/dist/types.d.ts +11 -0
  154. package/dist/ui/dashboard.html +55 -32
  155. package/dist/ui/settings.html +200 -60
  156. package/dist/ui/view.html +147 -30
  157. package/dist/usage/format-rich.d.ts +1 -1
  158. package/dist/usage/format-rich.js +28 -24
  159. package/dist/usage/format-rich.js.map +1 -1
  160. package/dist/usage/i18n-keys.d.ts +7 -0
  161. package/dist/usage/i18n-keys.js +34 -0
  162. package/dist/usage/i18n-keys.js.map +1 -0
  163. package/dist/usage/i18n.d.ts +5 -0
  164. package/dist/usage/i18n.js +27 -0
  165. package/dist/usage/i18n.js.map +1 -0
  166. package/dist/usage/providers.d.ts +21 -0
  167. package/dist/usage/providers.js +153 -75
  168. package/dist/usage/providers.js.map +1 -1
  169. package/dist/usage/usage-api.d.ts +11 -3
  170. package/dist/usage/usage-api.js +61 -24
  171. package/dist/usage/usage-api.js.map +1 -1
  172. package/dist/workflow-templates/default.md +2 -1
  173. package/package.json +2 -2
@@ -0,0 +1,79 @@
1
+ import { freemem } from "node:os";
2
+ /** Persistent fleet-wide concurrency/workdir gate shared by startup and recovery. */
3
+ export class SpawnGate {
4
+ options;
5
+ queue = [];
6
+ active = 0;
7
+ activeDirectories = new Set();
8
+ activeInstances = new Set();
9
+ lastStartedAt = 0;
10
+ timer = null;
11
+ stopped = false;
12
+ random;
13
+ lowMemoryBytes;
14
+ constructor(options) {
15
+ this.options = options;
16
+ this.random = options.random ?? Math.random;
17
+ this.lowMemoryBytes = options.lowMemoryBytes ?? 300 * 1024 * 1024;
18
+ options.storm.on("recovery_due", () => this.pump());
19
+ options.storm.on("closed", () => this.pump());
20
+ }
21
+ run(task, operation) {
22
+ if (this.stopped)
23
+ return Promise.reject(new Error("Spawn gate is shutting down"));
24
+ // startInstancesWithConcurrency/restartSingleInstance gate the whole
25
+ // lifecycle operation, whose Daemon.trySpawn reaches this same choke point.
26
+ // Treat that nested acquisition as re-entrant for the same instance.
27
+ if (this.activeInstances.has(task.instanceName))
28
+ return operation();
29
+ return new Promise((resolve, reject) => {
30
+ this.queue.push({ task, run: operation, resolve, reject });
31
+ this.pump();
32
+ });
33
+ }
34
+ shutdown() {
35
+ this.stopped = true;
36
+ if (this.timer)
37
+ clearTimeout(this.timer);
38
+ this.timer = null;
39
+ const err = new Error("Spawn gate shut down before task started");
40
+ for (const item of this.queue.splice(0))
41
+ item.reject(err);
42
+ }
43
+ pump() {
44
+ if (this.stopped || this.timer || this.options.storm.isSpawnBlocked())
45
+ return;
46
+ const configured = Math.max(1, Math.min(20, this.options.concurrency()));
47
+ const limit = this.options.storm.isActive() ? Math.min(4, configured) : configured;
48
+ while (this.active < limit) {
49
+ const index = this.queue.findIndex(item => !this.activeDirectories.has(item.task.workingDirectory));
50
+ if (index < 0)
51
+ return;
52
+ if (this.active > 0 && freemem() < this.lowMemoryBytes) {
53
+ this.timer = setTimeout(() => { this.timer = null; this.pump(); }, 5_000);
54
+ this.timer.unref?.();
55
+ return;
56
+ }
57
+ const stagger = Math.max(0, Math.min(30_000, this.options.staggerMs()));
58
+ const jitter = this.options.storm.isActive() ? Math.floor(this.random() * 500) : 0;
59
+ const wait = Math.max(0, this.lastStartedAt + stagger + jitter - Date.now());
60
+ if (wait > 0) {
61
+ this.timer = setTimeout(() => { this.timer = null; this.pump(); }, wait);
62
+ this.timer.unref?.();
63
+ return;
64
+ }
65
+ const [item] = this.queue.splice(index, 1);
66
+ this.active++;
67
+ this.activeDirectories.add(item.task.workingDirectory);
68
+ this.activeInstances.add(item.task.instanceName);
69
+ this.lastStartedAt = Date.now();
70
+ void item.run().then(item.resolve, item.reject).finally(() => {
71
+ this.active--;
72
+ this.activeDirectories.delete(item.task.workingDirectory);
73
+ this.activeInstances.delete(item.task.instanceName);
74
+ this.pump();
75
+ });
76
+ }
77
+ }
78
+ }
79
+ //# sourceMappingURL=spawn-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn-gate.js","sourceRoot":"","sources":["../src/spawn-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function backendSupportsSteer(backend: string): boolean;
@@ -0,0 +1,6 @@
1
+ /** Backends whose TUI has been live-verified to accept mid-turn input. */
2
+ const STEER_SUPPORTED_BACKENDS = new Set(["claude-code", "codex", "grok", "mock"]);
3
+ export function backendSupportsSteer(backend) {
4
+ return STEER_SUPPORTED_BACKENDS.has(backend);
5
+ }
6
+ //# sourceMappingURL=steer-capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steer-capability.js","sourceRoot":"","sources":["../src/steer-capability.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEnF,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,OAAO,wBAAwB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,83 @@
1
+ import { EventEmitter } from "node:events";
2
+ export type StormPhase = "closed" | "backing_off" | "recovering";
3
+ export interface StormSnapshot {
4
+ phase: StormPhase;
5
+ generation: number;
6
+ crashLevel: number;
7
+ crashCount: number;
8
+ backoffMs: number;
9
+ retryAt: number | null;
10
+ affected: string[];
11
+ recovered: string[];
12
+ suppressed: Record<string, number>;
13
+ serverPid: number | null;
14
+ }
15
+ export interface StormWindowOptions {
16
+ now?: () => number;
17
+ setTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;
18
+ clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
19
+ backoffsMs?: readonly number[];
20
+ stableResetMs?: number;
21
+ recoveryTimeoutMs?: number;
22
+ }
23
+ /**
24
+ * Fleet-wide tmux-server storm state.
25
+ *
26
+ * A server death is counted on an observed alive -> dead transition, not once
27
+ * per daemon report. Health ticks are deliberately out of phase, so a time
28
+ * window cannot distinguish thirty reports of one death from a real second
29
+ * crash sixteen seconds later.
30
+ */
31
+ export declare class StormWindow extends EventEmitter {
32
+ private readonly now;
33
+ private readonly setTimer;
34
+ private readonly clearTimer;
35
+ private readonly backoffsMs;
36
+ private readonly stableResetMs;
37
+ private readonly recoveryTimeoutMs;
38
+ private phase;
39
+ private generation;
40
+ private crashLevel;
41
+ private crashCount;
42
+ private backoffMs;
43
+ private retryAt;
44
+ private serverDown;
45
+ private serverPid;
46
+ private affected;
47
+ private recovered;
48
+ private suppressed;
49
+ private backoffTimer;
50
+ private stableTimer;
51
+ private recoveryTimer;
52
+ private spawnWaiters;
53
+ private deliveryWaiters;
54
+ private stopped;
55
+ constructor(options?: StormWindowOptions);
56
+ snapshot(): StormSnapshot;
57
+ isActive(): boolean;
58
+ isStopped(): boolean;
59
+ isSpawnBlocked(): boolean;
60
+ isDeliveryHeld(name?: string): boolean;
61
+ needsRecovery(name: string): boolean;
62
+ /** Record a confirmed tmux-server absence. Returns true for a new transition. */
63
+ recordServerDead(reporter: string, affected: Iterable<string>): boolean;
64
+ /**
65
+ * Mark the server alive. A changed PID re-arms the generation latch. If a
66
+ * storm is already open, an unexpected PID swap is itself evidence that a
67
+ * crash/restart transition occurred between probes.
68
+ */
69
+ observeServerAlive(pid: number | null): boolean;
70
+ addAffected(name: string): void;
71
+ markRecovered(name: string): void;
72
+ shouldSuppress(kind: string): boolean;
73
+ waitForSpawnAllowed(): Promise<void>;
74
+ waitForDeliveryAllowed(name: string): Promise<void>;
75
+ shutdown(): void;
76
+ private recordDistinctCrash;
77
+ private beginRecovery;
78
+ private closeWindow;
79
+ private release;
80
+ private releaseDelivery;
81
+ private releaseAllDelivery;
82
+ private clearAllTimers;
83
+ }
@@ -0,0 +1,251 @@
1
+ import { EventEmitter } from "node:events";
2
+ const STORM_INCIDENT_KINDS = new Set([
3
+ "crash_respawn",
4
+ "mcp_died",
5
+ "mcp_auto_restart",
6
+ "snapshot_failed",
7
+ "hang",
8
+ "health_check_error",
9
+ ]);
10
+ /**
11
+ * Fleet-wide tmux-server storm state.
12
+ *
13
+ * A server death is counted on an observed alive -> dead transition, not once
14
+ * per daemon report. Health ticks are deliberately out of phase, so a time
15
+ * window cannot distinguish thirty reports of one death from a real second
16
+ * crash sixteen seconds later.
17
+ */
18
+ export class StormWindow extends EventEmitter {
19
+ now;
20
+ setTimer;
21
+ clearTimer;
22
+ backoffsMs;
23
+ stableResetMs;
24
+ recoveryTimeoutMs;
25
+ phase = "closed";
26
+ generation = 0;
27
+ crashLevel = 0;
28
+ crashCount = 0;
29
+ backoffMs = 0;
30
+ retryAt = null;
31
+ serverDown = false;
32
+ serverPid = null;
33
+ affected = new Set();
34
+ recovered = new Set();
35
+ suppressed = new Map();
36
+ backoffTimer = null;
37
+ stableTimer = null;
38
+ recoveryTimer = null;
39
+ spawnWaiters = new Set();
40
+ deliveryWaiters = new Map();
41
+ stopped = false;
42
+ constructor(options = {}) {
43
+ super();
44
+ this.now = options.now ?? Date.now;
45
+ this.setTimer = options.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
46
+ this.clearTimer = options.clearTimer ?? clearTimeout;
47
+ this.backoffsMs = options.backoffsMs ?? [30_000, 2 * 60_000, 10 * 60_000];
48
+ this.stableResetMs = options.stableResetMs ?? 10 * 60_000;
49
+ this.recoveryTimeoutMs = options.recoveryTimeoutMs ?? 10 * 60_000;
50
+ }
51
+ snapshot() {
52
+ return {
53
+ phase: this.phase,
54
+ generation: this.generation,
55
+ crashLevel: this.crashLevel,
56
+ crashCount: this.crashCount,
57
+ backoffMs: this.backoffMs,
58
+ retryAt: this.retryAt,
59
+ affected: [...this.affected],
60
+ recovered: [...this.recovered],
61
+ suppressed: Object.fromEntries(this.suppressed),
62
+ serverPid: this.serverPid,
63
+ };
64
+ }
65
+ isActive() { return this.phase !== "closed"; }
66
+ isStopped() { return this.stopped; }
67
+ isSpawnBlocked() { return !this.stopped && this.phase === "backing_off"; }
68
+ isDeliveryHeld(name) {
69
+ if (this.stopped || this.phase === "closed")
70
+ return false;
71
+ if (this.phase === "backing_off" || name == null)
72
+ return true;
73
+ return this.needsRecovery(name);
74
+ }
75
+ needsRecovery(name) {
76
+ return this.isActive() && this.affected.has(name) && !this.recovered.has(name);
77
+ }
78
+ /** Record a confirmed tmux-server absence. Returns true for a new transition. */
79
+ recordServerDead(reporter, affected) {
80
+ for (const name of affected)
81
+ this.affected.add(name);
82
+ this.affected.add(reporter);
83
+ this.recovered.delete(reporter);
84
+ if (this.serverDown)
85
+ return false;
86
+ this.serverDown = true;
87
+ this.serverPid = null;
88
+ this.recordDistinctCrash();
89
+ return true;
90
+ }
91
+ /**
92
+ * Mark the server alive. A changed PID re-arms the generation latch. If a
93
+ * storm is already open, an unexpected PID swap is itself evidence that a
94
+ * crash/restart transition occurred between probes.
95
+ */
96
+ observeServerAlive(pid) {
97
+ const changed = pid != null && this.serverPid != null && pid !== this.serverPid;
98
+ const wasDown = this.serverDown;
99
+ this.serverDown = false;
100
+ if (pid != null)
101
+ this.serverPid = pid;
102
+ if (changed && !wasDown)
103
+ this.recordDistinctCrash();
104
+ return changed && !wasDown;
105
+ }
106
+ addAffected(name) {
107
+ if (!this.isActive())
108
+ return;
109
+ this.affected.add(name);
110
+ this.recovered.delete(name);
111
+ }
112
+ markRecovered(name) {
113
+ if (!this.isActive() || !this.affected.has(name))
114
+ return;
115
+ this.recovered.add(name);
116
+ this.releaseDelivery(name);
117
+ this.emit("progress", this.snapshot());
118
+ if (this.phase === "recovering" && this.recovered.size >= this.affected.size) {
119
+ this.closeWindow("recovered");
120
+ }
121
+ }
122
+ shouldSuppress(kind) {
123
+ if (!this.isActive() || !STORM_INCIDENT_KINDS.has(kind))
124
+ return false;
125
+ this.suppressed.set(kind, (this.suppressed.get(kind) ?? 0) + 1);
126
+ return true;
127
+ }
128
+ waitForSpawnAllowed() {
129
+ if (!this.isSpawnBlocked())
130
+ return Promise.resolve();
131
+ return new Promise(resolve => this.spawnWaiters.add(resolve));
132
+ }
133
+ waitForDeliveryAllowed(name) {
134
+ if (!this.isDeliveryHeld(name))
135
+ return Promise.resolve();
136
+ return new Promise(resolve => {
137
+ let waiters = this.deliveryWaiters.get(name);
138
+ if (!waiters) {
139
+ waiters = new Set();
140
+ this.deliveryWaiters.set(name, waiters);
141
+ }
142
+ waiters.add(resolve);
143
+ });
144
+ }
145
+ shutdown() {
146
+ this.stopped = true;
147
+ this.clearAllTimers();
148
+ this.release(this.spawnWaiters);
149
+ this.releaseAllDelivery();
150
+ this.phase = "closed";
151
+ }
152
+ recordDistinctCrash() {
153
+ const now = this.now();
154
+ const wasClosed = this.phase === "closed";
155
+ this.generation++;
156
+ this.crashCount++;
157
+ this.crashLevel = Math.min(this.crashLevel + 1, this.backoffsMs.length);
158
+ this.backoffMs = this.backoffsMs[Math.max(0, this.crashLevel - 1)] ?? this.backoffsMs.at(-1);
159
+ this.retryAt = now + this.backoffMs;
160
+ this.phase = "backing_off";
161
+ this.recovered.clear();
162
+ if (this.backoffTimer)
163
+ this.clearTimer(this.backoffTimer);
164
+ if (this.recoveryTimer) {
165
+ this.clearTimer(this.recoveryTimer);
166
+ this.recoveryTimer = null;
167
+ }
168
+ this.backoffTimer = this.setTimer(() => this.beginRecovery(), this.backoffMs);
169
+ this.backoffTimer?.unref?.();
170
+ // A storm is not stable merely because the current backoff itself lasts ten
171
+ // minutes. The reset clock begins only after recovery closes the window.
172
+ if (this.stableTimer) {
173
+ this.clearTimer(this.stableTimer);
174
+ this.stableTimer = null;
175
+ }
176
+ this.emit(wasClosed ? "opened" : "extended", this.snapshot());
177
+ }
178
+ beginRecovery() {
179
+ this.backoffTimer = null;
180
+ if (this.stopped || this.phase !== "backing_off")
181
+ return;
182
+ this.phase = "recovering";
183
+ this.retryAt = null;
184
+ this.release(this.spawnWaiters);
185
+ // A delivery may have parked during backoff for an instance which was not
186
+ // running (and therefore not in `affected`). Once the server is available,
187
+ // that instance can follow its normal wake path immediately; do not make it
188
+ // wait for unrelated recovery stragglers to close the whole window.
189
+ for (const name of [...this.deliveryWaiters.keys()]) {
190
+ if (!this.isDeliveryHeld(name))
191
+ this.releaseDelivery(name);
192
+ }
193
+ this.emit("recovery_due", this.snapshot());
194
+ this.recoveryTimer = this.setTimer(() => this.closeWindow("timeout"), this.recoveryTimeoutMs);
195
+ this.recoveryTimer?.unref?.();
196
+ }
197
+ closeWindow(reason) {
198
+ if (this.phase === "closed")
199
+ return;
200
+ if (this.backoffTimer) {
201
+ this.clearTimer(this.backoffTimer);
202
+ this.backoffTimer = null;
203
+ }
204
+ if (this.recoveryTimer) {
205
+ this.clearTimer(this.recoveryTimer);
206
+ this.recoveryTimer = null;
207
+ }
208
+ this.phase = "closed";
209
+ this.retryAt = null;
210
+ const snapshot = this.snapshot();
211
+ this.release(this.spawnWaiters);
212
+ this.releaseAllDelivery();
213
+ this.emit("closed", snapshot, reason);
214
+ this.affected.clear();
215
+ this.recovered.clear();
216
+ this.suppressed.clear();
217
+ this.stableTimer = this.setTimer(() => {
218
+ if (this.phase !== "closed")
219
+ return;
220
+ this.crashLevel = 0;
221
+ this.crashCount = 0;
222
+ this.stableTimer = null;
223
+ this.emit("stable", this.snapshot());
224
+ }, this.stableResetMs);
225
+ this.stableTimer?.unref?.();
226
+ }
227
+ release(waiters) {
228
+ for (const resolve of waiters)
229
+ resolve();
230
+ waiters.clear();
231
+ }
232
+ releaseDelivery(name) {
233
+ const waiters = this.deliveryWaiters.get(name);
234
+ if (!waiters)
235
+ return;
236
+ this.release(waiters);
237
+ this.deliveryWaiters.delete(name);
238
+ }
239
+ releaseAllDelivery() {
240
+ for (const name of [...this.deliveryWaiters.keys()])
241
+ this.releaseDelivery(name);
242
+ }
243
+ clearAllTimers() {
244
+ for (const timer of [this.backoffTimer, this.stableTimer, this.recoveryTimer]) {
245
+ if (timer)
246
+ this.clearTimer(timer);
247
+ }
248
+ this.backoffTimer = this.stableTimer = this.recoveryTimer = null;
249
+ }
250
+ }
251
+ //# sourceMappingURL=storm-window.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storm-window.js","sourceRoot":"","sources":["../src/storm-window.ts"],"names":[],"mappings":"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"}
package/dist/tips.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ import type { DailySummaryConfig } from "./types.js";
2
+ export type TipLevel = "beginner" | "intermediate" | "advanced";
3
+ /**
4
+ * Usage tips surfaced to users, graded by experience level.
5
+ *
6
+ * Beginner copy follows the user's own vocabulary: short, direct conclusions,
7
+ * `term = consequence` comparisons, and practical pitfalls. It introduces an
8
+ * AgEnD term only to explain what changes for the person using the chat.
9
+ * Intermediate and Advanced retain the deeper conceptual and operational
10
+ * material for later rollout stages.
11
+ *
12
+ * Any command, tool, or config key named here is verified against the command
13
+ * registry, outbound tool schemas, and `types.ts` rather than written from
14
+ * memory. When a capability is backend-specific (`/btw` is Claude Code only),
15
+ * the tip says so rather than implying it works everywhere.
16
+ *
17
+ * The Chinese text is written natively, not translated from the English — the
18
+ * two convey the same fact, and neither reads as a translation of the other.
19
+ */
20
+ export interface Tip {
21
+ id: string;
22
+ level: TipLevel;
23
+ text_en: string;
24
+ text_zh: string;
25
+ /** Backend-specific tips are shown only when one of these backends is active. */
26
+ tags?: string[];
27
+ }
28
+ export declare const ADVANCED_TIP_UNLOCK_THRESHOLD = 60;
29
+ /** Temporary rollout gate: keep authored higher tiers without selecting them yet. */
30
+ export declare const TIPS_MAX_VISIBLE_LEVEL: TipLevel;
31
+ export declare const TIPS: Tip[];
32
+ export declare function canUnlockAdvancedTips(dismissedIds: ReadonlySet<string>): boolean;
33
+ export declare function visibleTipLevels(advancedUnlocked?: boolean): ReadonlySet<TipLevel>;
34
+ export declare function selectTip(dismissedIds: ReadonlySet<string>, random?: () => number, advancedUnlocked?: boolean, activeBackends?: ReadonlySet<string>): Tip | null;
35
+ /** Fleet-internal daily trigger. It is intentionally not persisted in fleet.yaml. */
36
+ export declare class DailyTipScheduler {
37
+ private readonly config;
38
+ private readonly timezone;
39
+ private readonly onTip;
40
+ private job;
41
+ constructor(config: DailySummaryConfig, timezone: string, onTip: () => void | Promise<void>);
42
+ start(): void;
43
+ stop(): void;
44
+ }