@itookit/dsht 0.3.8 → 0.5.2

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 (166) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +33 -12
  3. package/README.zh.md +35 -14
  4. package/dist/catalog/controller.d.ts +26 -6
  5. package/dist/catalog/controller.js +73 -45
  6. package/dist/catalog/index.d.ts +1 -0
  7. package/dist/cli/dsht.js +206 -18
  8. package/dist/cli/startup.d.ts +40 -0
  9. package/dist/cli/startup.js +314 -0
  10. package/dist/cli/trace-summary.d.ts +78 -0
  11. package/dist/cli/trace-summary.js +241 -0
  12. package/dist/cli/verifier.d.ts +64 -0
  13. package/dist/cli/verifier.js +265 -0
  14. package/dist/contracts.d.ts +359 -0
  15. package/dist/contracts.js +1 -0
  16. package/dist/controller/commands.d.ts +47 -0
  17. package/dist/controller/commands.js +322 -0
  18. package/dist/controller/connection-streams.d.ts +22 -0
  19. package/dist/controller/connection-streams.js +105 -0
  20. package/dist/controller/connection.d.ts +24 -31
  21. package/dist/controller/connection.js +48 -111
  22. package/dist/controller/controller.d.ts +412 -178
  23. package/dist/controller/controller.js +713 -167
  24. package/dist/controller/foreground.d.ts +44 -0
  25. package/dist/controller/foreground.js +79 -0
  26. package/dist/controller/index.d.ts +8 -1
  27. package/dist/controller/index.js +5 -0
  28. package/dist/controller/loop-contract.d.ts +136 -0
  29. package/dist/controller/loop-contract.js +308 -0
  30. package/dist/controller/loop-coordinator.d.ts +48 -0
  31. package/dist/controller/loop-coordinator.js +647 -0
  32. package/dist/controller/loop-prompts-schema.d.ts +56 -0
  33. package/dist/controller/loop-prompts-schema.js +144 -0
  34. package/dist/controller/loop-prompts.d.ts +55 -0
  35. package/dist/controller/loop-prompts.generated.d.ts +104 -0
  36. package/dist/controller/loop-prompts.generated.js +185 -0
  37. package/dist/controller/loop-prompts.js +104 -0
  38. package/dist/controller/loop-protocols.d.ts +39 -0
  39. package/dist/controller/loop-protocols.js +115 -0
  40. package/dist/controller/loop.d.ts +275 -0
  41. package/dist/controller/loop.js +378 -0
  42. package/dist/controller/prompts.d.ts +54 -0
  43. package/dist/controller/prompts.js +162 -0
  44. package/dist/controller/trace-log.d.ts +45 -0
  45. package/dist/controller/trace-log.js +144 -0
  46. package/dist/controller/verifier.d.ts +130 -0
  47. package/dist/controller/verifier.js +75 -0
  48. package/dist/cost/controller.d.ts +1 -1
  49. package/dist/cost/controller.js +12 -5
  50. package/dist/cost/index.d.ts +1 -1
  51. package/dist/cost/index.js +1 -1
  52. package/dist/cost/ledger.d.ts +0 -1
  53. package/dist/cost/ledger.js +0 -1
  54. package/dist/cost/scanner.js +1 -0
  55. package/dist/json.d.ts +18 -0
  56. package/dist/json.js +19 -0
  57. package/dist/references.d.ts +25 -0
  58. package/dist/references.js +26 -0
  59. package/dist/session/connection-view.d.ts +2 -11
  60. package/dist/session/controller.d.ts +94 -105
  61. package/dist/session/controller.js +262 -536
  62. package/dist/session/history-reader.d.ts +32 -0
  63. package/dist/session/history-reader.js +170 -0
  64. package/dist/session/history.d.ts +6 -18
  65. package/dist/session/history.js +1 -24
  66. package/dist/session/index.d.ts +9 -4
  67. package/dist/session/index.js +7 -3
  68. package/dist/session/info.d.ts +20 -82
  69. package/dist/session/info.js +52 -25
  70. package/dist/session/interactions.d.ts +26 -0
  71. package/dist/session/interactions.js +75 -0
  72. package/dist/session/markdown.js +1 -1
  73. package/dist/session/math.js +1 -1
  74. package/dist/session/mutation-gate.d.ts +51 -0
  75. package/dist/session/mutation-gate.js +73 -0
  76. package/dist/session/navigation.d.ts +2 -89
  77. package/dist/session/navigation.js +2 -129
  78. package/dist/session/navigator.d.ts +47 -0
  79. package/dist/session/navigator.js +158 -0
  80. package/dist/session/peek.d.ts +38 -0
  81. package/dist/session/peek.js +103 -0
  82. package/dist/session/prompt-backfill.d.ts +23 -0
  83. package/dist/session/prompt-backfill.js +88 -0
  84. package/dist/session/references.d.ts +2 -20
  85. package/dist/session/references.js +1 -26
  86. package/dist/session/runtime.d.ts +26 -0
  87. package/dist/session/runtime.js +28 -0
  88. package/dist/session/state.d.ts +20 -0
  89. package/dist/session/state.js +1 -0
  90. package/dist/session/telemetry.d.ts +25 -17
  91. package/dist/session/telemetry.js +66 -60
  92. package/dist/session/transcript.d.ts +5 -7
  93. package/dist/session/transcript.js +2 -15
  94. package/dist/session/types.d.ts +25 -0
  95. package/dist/session/types.js +0 -1
  96. package/dist/session-title.d.ts +9 -0
  97. package/dist/session-title.js +21 -0
  98. package/dist/shell/controller.d.ts +31 -1
  99. package/dist/shell/controller.js +34 -2
  100. package/dist/shell/index.d.ts +3 -3
  101. package/dist/shell/index.js +2 -2
  102. package/dist/shell/runner.d.ts +10 -0
  103. package/dist/shell/runner.js +48 -9
  104. package/dist/slash/index.d.ts +10 -0
  105. package/dist/slash/index.js +7 -0
  106. package/dist/slash/parse.d.ts +42 -0
  107. package/dist/slash/parse.js +259 -0
  108. package/dist/slash/pipeline.d.ts +140 -0
  109. package/dist/slash/pipeline.js +115 -0
  110. package/dist/slash/registry.d.ts +88 -0
  111. package/dist/slash/registry.js +177 -0
  112. package/dist/slash/types.d.ts +126 -0
  113. package/dist/slash/types.js +1 -0
  114. package/dist/state.d.ts +16 -18
  115. package/dist/state.js +4 -3
  116. package/dist/text.d.ts +28 -0
  117. package/dist/text.js +55 -0
  118. package/dist/transport/client.d.ts +4 -3
  119. package/dist/transport/client.js +71 -25
  120. package/dist/transport/events.d.ts +104 -0
  121. package/dist/transport/events.js +149 -0
  122. package/dist/transport/wire.d.ts +9 -17
  123. package/dist/transport/wire.js +2 -27
  124. package/dist/ui/app.js +750 -550
  125. package/dist/ui/chat/header.js +1 -1
  126. package/dist/ui/chat/history-view.d.ts +1 -1
  127. package/dist/ui/chat/loop-status.d.ts +11 -0
  128. package/dist/ui/chat/loop-status.js +28 -0
  129. package/dist/ui/chat/navigation-model.d.ts +86 -0
  130. package/dist/ui/chat/navigation-model.js +107 -0
  131. package/dist/ui/chat/shell-view.d.ts +17 -2
  132. package/dist/ui/chat/shell-view.js +45 -3
  133. package/dist/ui/chat/status.d.ts +47 -3
  134. package/dist/ui/chat/status.js +65 -50
  135. package/dist/ui/chat/use-history-view.d.ts +69 -0
  136. package/dist/ui/chat/use-history-view.js +123 -0
  137. package/dist/ui/chat/viewport.d.ts +1 -1
  138. package/dist/ui/dialogs/cost.d.ts +21 -4
  139. package/dist/ui/dialogs/cost.js +7 -12
  140. package/dist/ui/dialogs/index.d.ts +22 -5
  141. package/dist/ui/dialogs/index.js +19 -3
  142. package/dist/ui/dialogs/loop.d.ts +43 -0
  143. package/dist/ui/dialogs/loop.js +224 -0
  144. package/dist/ui/dialogs/peek.d.ts +25 -0
  145. package/dist/ui/dialogs/peek.js +35 -0
  146. package/dist/ui/dialogs/picker.d.ts +2 -0
  147. package/dist/ui/dialogs/picker.js +4 -2
  148. package/dist/ui/dialogs/use-panels.d.ts +53 -0
  149. package/dist/ui/dialogs/use-panels.js +51 -0
  150. package/dist/ui/input/mouse.d.ts +12 -2
  151. package/dist/ui/input/mouse.js +20 -7
  152. package/dist/ui/input/references.d.ts +1 -1
  153. package/dist/ui/input/use-composer.d.ts +35 -0
  154. package/dist/ui/input/use-composer.js +109 -0
  155. package/dist/ui/input/use-deferred-lines.d.ts +16 -0
  156. package/dist/ui/input/use-deferred-lines.js +54 -0
  157. package/dist/ui/input/use-history-recall.d.ts +20 -0
  158. package/dist/ui/input/use-history-recall.js +47 -0
  159. package/dist/ui/status/model.d.ts +7 -0
  160. package/dist/ui/status/model.js +5 -0
  161. package/dist/ui/theme/index.d.ts +1 -1
  162. package/package.json +6 -4
  163. package/dist/ui/commands/parse.d.ts +0 -104
  164. package/dist/ui/commands/parse.js +0 -135
  165. package/dist/ui/commands/registry.d.ts +0 -33
  166. package/dist/ui/commands/registry.js +0 -73
@@ -1,22 +1,20 @@
1
1
  /** Owns the physical connection, its generations, and the host event subscriptions. */
2
2
  import { setTimeout as delay } from 'node:timers/promises';
3
3
  import { AuthenticationRequired } from "../transport/auth.js";
4
- import { HttpError, RemoteError } from "../transport/client.js";
5
- import { array, errorText, object, string } from "../transport/wire.js";
6
- import { Telemetry } from "../session/telemetry.js";
7
- /** Projection keys this client consumes; the host retains every other capability. */
8
- const RETAINED_PROJECTIONS = new Set(['title', 'modelSelection', 'contextPressure', 'tokenUsage', 'sessionStats', 'agentPreset']);
9
- /** Host events that invalidate the model catalog. */
10
- const CATALOG_EVENTS = ['llm/adapters-updated', 'settings/document-updated', 'credentials/reference-updated'];
11
- /** Owns reconnects, subscriptions and the telemetry generation. User commands remain single-attempt operations. */
4
+ import { HttpError } from "../transport/client.js";
5
+ import { errorText } from "../transport/wire.js";
6
+ import { ConnectionStreams } from "./connection-streams.js";
7
+ /** Owns the physical connection: reconnects, subscriptions and generation lifecycle.
8
+ *
9
+ * Host running state, projections and interactions belong to the session domain; this class only
10
+ * decodes wire frames into `HostEvent` and hands them to the application, which routes them on.
11
+ * User commands remain single-attempt operations.
12
+ */
12
13
  export class ConnectionController {
13
14
  store;
14
15
  options;
15
16
  listener;
16
- telemetry = new Telemetry(RETAINED_PROJECTIONS);
17
17
  clientId = '';
18
- runningUpdates = new Map();
19
- observedRunningAt = new Map();
20
18
  current;
21
19
  abort = new AbortController();
22
20
  runTask;
@@ -47,119 +45,72 @@ export class ConnectionController {
47
45
  online() { return this.store.state.online; }
48
46
  /** @returns The client lifetime signal. */
49
47
  signal() { return this.abort.signal; }
50
- /** @returns The projection store of the current generation. */
51
- telemetryView() { return this.telemetry; }
52
- /** @returns Cached host running state, or undefined when never reported. */
53
- runningFor(sessionId) { return this.runningUpdates.get(sessionId); }
54
- /** @returns When this client first observed the session, for the elapsed-time fallback. */
55
- observedAt(sessionId) { return this.observedRunningAt.get(sessionId); }
56
- /** Record an observation start for a session this client just opened. */
57
- observe(sessionId) { if (!this.observedRunningAt.has(sessionId))
58
- this.observedRunningAt.set(sessionId, Date.now()); }
59
48
  /** Fail the current generation, so the controller reopens a snapshot. */
60
49
  fail(error) { this.generationFailed?.(error); }
61
50
  /** Answer one retained host waterfall through the event-result endpoint. */
62
- async reply(frame, outcome) {
63
- await this.require().call('$events/result', { clientId: this.clientId, eventId: string(frame.eventId), outcome });
51
+ async reply(eventId, outcome) {
52
+ await this.require().call('$events/result', { clientId: this.clientId, eventId, outcome });
64
53
  }
65
54
  /** Run one generation, then retry with bounded jittered backoff until stopped. */
66
55
  async run() {
67
56
  let attempt = 0;
68
57
  while (!this.abort.signal.aborted) {
69
- this.runningUpdates.clear();
70
- this.observedRunningAt.clear();
71
- this.telemetry = new Telemetry(RETAINED_PROJECTIONS);
72
58
  this.listener.begin();
59
+ if (this.abort.signal.aborted)
60
+ break;
73
61
  const client = this.options.makeClient();
74
62
  this.current = client;
63
+ const generation = new AbortController();
64
+ const signal = AbortSignal.any([this.abort.signal, generation.signal]);
65
+ let disconnect;
66
+ const disconnected = new Promise(resolve => { disconnect = resolve; });
67
+ const fail = (error) => {
68
+ if (generation.signal.aborted)
69
+ return;
70
+ generation.abort(error);
71
+ disconnect(error);
72
+ // End HTTP as well as streams, including requests the ready callback is awaiting.
73
+ void client.close();
74
+ };
75
+ this.generationFailed = fail;
76
+ const streams = new ConnectionStreams(client, {
77
+ identified: id => { this.clientId = id; }, event: event => this.listener.event(event),
78
+ changed: () => this.store.update({}), degraded: controlError => this.store.update({ controlError }), fail,
79
+ }, signal);
75
80
  try {
81
+ signal.throwIfAborted();
76
82
  await this.options.authenticate(client);
83
+ signal.throwIfAborted();
77
84
  await client.connect();
78
- let fail;
79
- const disconnected = new Promise(resolve => { fail = resolve; });
80
- this.generationFailed = fail;
81
- const ready = new Promise((resolve, reject) => {
82
- const timer = setTimeout(() => reject(new Error('Host ready timed out')), client.timeoutMs);
83
- client.subscribe('$events', {}, {
84
- item: value => {
85
- const frame = object(value);
86
- if (frame.type === 'ready') {
87
- this.clientId = string(frame.clientId);
88
- clearTimeout(timer);
89
- resolve();
90
- }
91
- else if (frame.type === 'waterfall') {
92
- if (this.listener.waterfall(frame))
93
- return;
94
- void client.call('$events/result', { clientId: this.clientId,
95
- eventId: string(frame.eventId), outcome: { kind: 'next' } }).catch(error => fail(new Error(errorText(error))));
96
- }
97
- else if (frame.type === 'cancel') {
98
- this.listener.cancelled(string(frame.eventId));
99
- }
100
- else if (frame.type === 'emit' && frame.event === 'api-session/status') {
101
- this.acceptStatus(array(frame.args));
102
- }
103
- else if (frame.type === 'emit' && CATALOG_EVENTS.includes(String(frame.event))) {
104
- this.listener.invalidated();
105
- }
106
- else if (frame.type === 'emit' && frame.event === 'api-session/error') {
107
- const args = array(frame.args);
108
- this.listener.error(args[0], args[1]);
109
- }
110
- },
111
- end: error => { clearTimeout(timer); const reason = error ?? new Error('Event stream ended'); reject(reason); fail(reason); },
112
- });
113
- });
114
- await ready;
115
- await new Promise((resolve, reject) => {
116
- const timer = setTimeout(() => reject(new Error('Session control baseline timed out')), client.timeoutMs);
117
- client.subscribe('session/control', {}, {
118
- item: value => {
119
- try {
120
- this.telemetry.accept(value);
121
- this.store.update({});
122
- clearTimeout(timer);
123
- resolve();
124
- }
125
- catch (error) {
126
- clearTimeout(timer);
127
- reject(error);
128
- fail(new Error(errorText(error)));
129
- }
130
- },
131
- end: error => {
132
- clearTimeout(timer);
133
- if (error instanceof RemoteError && error.code === 'gateway/method-unavailable') {
134
- this.store.update({ controlError: 'Live metrics unavailable on this host' });
135
- resolve();
136
- }
137
- else {
138
- const reason = error ?? new Error('Session control stream ended');
139
- reject(reason);
140
- fail(reason);
141
- }
142
- },
143
- });
144
- });
85
+ signal.throwIfAborted();
86
+ await streams.start();
87
+ signal.throwIfAborted();
145
88
  await this.listener.ready();
89
+ signal.throwIfAborted();
146
90
  attempt = 0;
147
91
  const error = await disconnected;
148
92
  if (!this.abort.signal.aborted)
149
93
  throw error;
150
94
  }
151
95
  catch (error) {
152
- if (error instanceof AuthenticationRequired || error instanceof HttpError && [401, 403].includes(error.status)) {
153
- this.store.update({ error: `${errorText(error)}. Set DSH_TOKEN and restart to log in.`, status: 'Login required' });
96
+ if (this.abort.signal.aborted)
97
+ break;
98
+ const reason = generation.signal.aborted ? generation.signal.reason : error;
99
+ if (reason instanceof AuthenticationRequired || reason instanceof HttpError && [401, 403].includes(reason.status)) {
100
+ this.store.update({ lastFailure: `${errorText(reason)}. Set DSH_TOKEN and restart to log in.`, status: 'Login required' });
154
101
  return;
155
102
  }
156
- if (!this.abort.signal.aborted)
157
- this.store.update({ error: errorText(error), status: 'Reconnecting…' });
103
+ this.store.update({ lastFailure: errorText(reason), status: 'Reconnecting…' });
158
104
  }
159
105
  finally {
160
106
  this.generationFailed = undefined;
161
- this.store.update({ online: false, pending: [] });
107
+ generation.abort();
108
+ streams.close();
109
+ this.clientId = '';
110
+ this.store.update({ online: false });
162
111
  await client.close();
112
+ if (this.current === client)
113
+ this.current = undefined;
163
114
  await this.listener.ended();
164
115
  }
165
116
  if (!this.abort.signal.aborted) {
@@ -173,18 +124,4 @@ export class ConnectionController {
173
124
  }
174
125
  }
175
126
  }
176
- /** Apply one `api-session/status` notification to the running maps and the session view. */
177
- acceptStatus(args) {
178
- const sessionId = string(args[0]);
179
- if (typeof args[1] !== 'boolean')
180
- throw new Error('Invalid session running state');
181
- if (args[1] && !this.runningUpdates.get(sessionId))
182
- this.observedRunningAt.set(sessionId, Date.now());
183
- if (!args[1])
184
- this.observedRunningAt.delete(sessionId);
185
- this.runningUpdates.set(sessionId, args[1]);
186
- this.listener.status(sessionId, args[1]);
187
- if (!args[1])
188
- this.listener.idle();
189
- }
190
127
  }