maka-agent 0.2.0-dev.24.20260907 → 0.2.0-dev.26.20260909

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 (154) hide show
  1. package/README.md +38 -19
  2. package/README.zh-CN.md +34 -19
  3. package/dist/mcp-capability-provider.js +4 -1
  4. package/dist/pi-transcript-tools.js +6 -0
  5. package/dist/pi-tui-layout.js +56 -10
  6. package/dist/pi-tui-mcp-status.js +2 -2
  7. package/dist/pi-tui-pickers.js +121 -30
  8. package/dist/pi-tui-runner.js +156 -42
  9. package/dist/pi-tui-todo.js +221 -0
  10. package/dist/pi-tui-transcript-viewer.js +134 -7
  11. package/dist/runtime-host-cli-context.js +70 -13
  12. package/dist/runtime-host-handoff-surface.js +51 -26
  13. package/dist/runtime-host-local-handoff.js +11 -1
  14. package/dist/runtime-host-service-manager.js +0 -3
  15. package/dist/runtime-host-session-channel.js +13 -0
  16. package/dist/runtime-host-session-driver.js +42 -1
  17. package/dist/tui-autocomplete-layout.js +3 -1
  18. package/dist/tui-copy-catalog.js +85 -6
  19. package/dist/tui-editor-render.js +36 -0
  20. package/dist/tui-mcp-control.js +41 -6
  21. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  22. package/node_modules/@maka/core/dist/agent-run.js +1 -0
  23. package/node_modules/@maka/core/dist/attachments.js +3 -0
  24. package/node_modules/@maka/core/dist/canonical-json.js +27 -0
  25. package/node_modules/@maka/core/dist/capabilities.js +35 -0
  26. package/node_modules/@maka/core/dist/computer-use.js +0 -1
  27. package/node_modules/@maka/core/dist/display-redaction.js +18 -0
  28. package/node_modules/@maka/core/dist/llm-connections.js +13 -0
  29. package/node_modules/@maka/core/dist/model-call-attempt.js +2 -0
  30. package/node_modules/@maka/core/dist/model-metadata.js +4 -0
  31. package/node_modules/@maka/core/dist/redaction.js +35 -0
  32. package/node_modules/@maka/core/dist/run-composition.js +77 -2
  33. package/node_modules/@maka/core/dist/runtime-event.js +3 -0
  34. package/node_modules/@maka/core/dist/session.js +57 -6
  35. package/node_modules/@maka/core/dist/slash-command-catalog.js +1 -0
  36. package/node_modules/@maka/core/dist/tool-result-record-schema.js +2 -1
  37. package/node_modules/@maka/core/dist/workhub-action-result.js +68 -0
  38. package/node_modules/@maka/core/package.json +3 -3
  39. package/node_modules/@maka/eval/dist/experiment-directory.js +4 -13
  40. package/node_modules/@maka/eval/dist/toolchain-verification.js +1 -1
  41. package/node_modules/@maka/mcp/dist/form-elicitation.js +213 -0
  42. package/node_modules/@maka/mcp/dist/index.js +337 -71
  43. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +0 -13
  44. package/node_modules/@maka/runtime/dist/agent-run.js +90 -2
  45. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +16 -8
  46. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +57 -50
  47. package/node_modules/@maka/runtime/dist/archive-read-tool.js +2 -2
  48. package/node_modules/@maka/runtime/dist/context-budget-helpers.js +0 -16
  49. package/node_modules/@maka/runtime/dist/conversation-copy.js +153 -11
  50. package/node_modules/@maka/runtime/dist/github-copilot-oauth-enrollment.js +3 -3
  51. package/node_modules/@maka/runtime/dist/goal-continuation.js +5 -1
  52. package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +69 -1
  53. package/node_modules/@maka/runtime/dist/mcp-tools.js +7 -2
  54. package/node_modules/@maka/runtime/dist/model-fetcher.js +42 -2
  55. package/node_modules/@maka/runtime/dist/model-history.js +0 -112
  56. package/node_modules/@maka/runtime/dist/plugin-runtime.js +20 -10
  57. package/node_modules/@maka/runtime/dist/plugin-tool-service.js +205 -0
  58. package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +8 -1
  59. package/node_modules/@maka/runtime/dist/request-shape.js +23 -0
  60. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +1 -1
  61. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +24 -1
  62. package/node_modules/@maka/runtime/dist/runtime-kernel.js +127 -13
  63. package/node_modules/@maka/runtime/dist/session-manager.js +20 -36
  64. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +21 -0
  65. package/node_modules/@maka/runtime/dist/stream-graph-supervisor-tools.js +0 -5
  66. package/node_modules/@maka/runtime/dist/test-connection.js +0 -1
  67. package/node_modules/@maka/runtime/dist/tool-activation-identity.js +41 -0
  68. package/node_modules/@maka/runtime/dist/tool-availability.js +11 -1
  69. package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +63 -1
  70. package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +42 -12
  71. package/node_modules/@maka/runtime/dist/tool-result-archive.js +27 -4
  72. package/node_modules/@maka/runtime/package.json +1 -0
  73. package/node_modules/@maka/runtime-host/dist/client/connection.js +30 -5
  74. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +52 -13
  75. package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +2 -0
  76. package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +4 -2
  77. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +18 -1
  78. package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +19 -0
  79. package/node_modules/@maka/runtime-host/dist/control/endpoint.js +1 -3
  80. package/node_modules/@maka/runtime-host/dist/node-error.js +22 -0
  81. package/node_modules/@maka/runtime-host/dist/operator/managed-deployment.js +1 -3
  82. package/node_modules/@maka/runtime-host/dist/peer-mesh/limits.js +0 -1
  83. package/node_modules/@maka/runtime-host/dist/peer-mesh/model.js +1 -1
  84. package/node_modules/@maka/runtime-host/dist/peer-reachability/publisher.js +1 -3
  85. package/node_modules/@maka/runtime-host/dist/protocol/index.js +9 -1
  86. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +3 -2
  87. package/node_modules/@maka/runtime-host/dist/protocol/plan.js +1 -0
  88. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +27 -5
  89. package/node_modules/@maka/runtime-host/dist/protocol/project-catalog.js +2 -2
  90. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -0
  91. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +86 -157
  92. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +4 -0
  93. package/node_modules/@maka/runtime-host/dist/server/agent-graph-coordinator.js +0 -3
  94. package/node_modules/@maka/runtime-host/dist/server/canonical-session-projection.js +2 -1
  95. package/node_modules/@maka/runtime-host/dist/server/connection-session.js +28 -8
  96. package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +112 -56
  97. package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +32 -32
  98. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +85 -25
  99. package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +42 -14
  100. package/node_modules/@maka/runtime-host/dist/server/failure-diagnostic.js +24 -0
  101. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +7 -3
  102. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +3 -3
  103. package/node_modules/@maka/runtime-host/dist/server/host-session-availability.js +5 -2
  104. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +3 -2
  105. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +34 -4
  106. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +51 -23
  107. package/node_modules/@maka/runtime-host/dist/server/operation-dispatcher.js +2 -7
  108. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
  109. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +6 -0
  110. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +110 -14
  111. package/node_modules/@maka/runtime-host/dist/server/runtime-resource-coordinator.js +11 -8
  112. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +31 -21
  113. package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +1 -1
  114. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +20 -8
  115. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +5 -2
  116. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +4 -0
  117. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +15 -104
  118. package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +3 -0
  119. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +59 -82
  120. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +132 -139
  121. package/node_modules/@maka/runtime-host/dist/server/workhub-message-attachments.js +46 -0
  122. package/node_modules/@maka/storage/dist/agent-run-store.js +14 -1
  123. package/node_modules/@maka/storage/dist/artifact-store.js +40 -3
  124. package/node_modules/@maka/storage/dist/atomic-file-write.js +74 -0
  125. package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +0 -1
  126. package/node_modules/@maka/storage/dist/codex-session-adapter.js +284 -196
  127. package/node_modules/@maka/storage/dist/credential-store.js +12 -63
  128. package/node_modules/@maka/storage/dist/execution-stores.js +4 -1
  129. package/node_modules/@maka/storage/dist/git-exec.js +61 -0
  130. package/node_modules/@maka/storage/dist/git-worktree-child-executor.js +8 -35
  131. package/node_modules/@maka/storage/dist/goal-authority.js +2 -2
  132. package/node_modules/@maka/storage/dist/interaction-store.js +2 -2
  133. package/node_modules/@maka/storage/dist/mcp-config-store.js +10 -25
  134. package/node_modules/@maka/storage/dist/project-catalog.js +2 -23
  135. package/node_modules/@maka/storage/dist/runtime-event-invariants.js +2 -2
  136. package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +8 -3
  137. package/node_modules/@maka/storage/dist/scheduled-task-store.js +249 -283
  138. package/node_modules/@maka/storage/dist/session-bundle-manifest.js +5 -17
  139. package/node_modules/@maka/storage/dist/session-bundle-policy.js +2 -1
  140. package/node_modules/@maka/storage/dist/session-copy-cleanup.js +2 -1
  141. package/node_modules/@maka/storage/dist/session-store.js +14 -2
  142. package/node_modules/@maka/storage/dist/settings-store.js +8 -4
  143. package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +11 -0
  144. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +1 -2
  145. package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +12 -1
  146. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +67 -3
  147. package/node_modules/@maka/storage/dist/stable-storage.js +14 -1
  148. package/node_modules/@maka/storage/dist/storage-id.js +28 -0
  149. package/node_modules/@maka/storage/dist/tool-result-archive-evidence.js +31 -4
  150. package/node_modules/@maka/storage/dist/workspace-identity.js +2 -15
  151. package/node_modules/@maka/storage/dist/workspace-version-authority-internal.js +1 -8
  152. package/package.json +1 -1
  153. package/node_modules/@maka/core/dist/workhub-creation-intent.js +0 -990
  154. package/node_modules/@maka/core/dist/workhub-session-resolver.js +0 -50
@@ -16,8 +16,9 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { Key, matchesKey, truncateToWidth, visibleWidth, } from '@earendil-works/pi-tui';
20
- import { ansi } from './tui-ansi.js';
19
+ import { Key, Input, isKeyRelease, isKeyRepeat, matchesKey, truncateToWidth, visibleWidth, } from '@earendil-works/pi-tui';
20
+ import { ansi, stripAnsi } from './tui-ansi.js';
21
+ import { TUI_COPY_RESOURCES } from './tui-copy-catalog.js';
21
22
  const VIEWER_CHROME_ROWS = 2;
22
23
  /**
23
24
  * Full-screen, read-only navigation over the CLI transcript projection.
@@ -28,19 +29,100 @@ const VIEWER_CHROME_ROWS = 2;
28
29
  */
29
30
  export class TranscriptViewerOverlay {
30
31
  input;
32
+ focused = false;
31
33
  top = 0;
32
34
  documentRows = 0;
33
35
  bodyRows = 0;
34
36
  followsEnd = true;
37
+ document = { lines: [], anchors: [] };
38
+ expanded = true;
39
+ anchor;
40
+ search = new Input();
41
+ searching = false;
42
+ query = '';
43
+ matches = [];
44
+ searchOrigin;
45
+ matchedLine;
35
46
  constructor(input) {
36
47
  this.input = input;
37
48
  }
38
49
  invalidate() { }
39
50
  handleInput(data) {
51
+ if (isKeyRelease(data))
52
+ return;
53
+ if (matchesKey(data, Key.ctrl('o'))) {
54
+ if (!isKeyRepeat(data))
55
+ this.input.onClose();
56
+ return;
57
+ }
58
+ if (this.searching) {
59
+ if (matchesKey(data, Key.escape)) {
60
+ this.searching = false;
61
+ this.query = '';
62
+ this.matches = [];
63
+ if (this.searchOrigin)
64
+ Object.assign(this, this.searchOrigin);
65
+ this.input.onChange();
66
+ return;
67
+ }
68
+ if (matchesKey(data, Key.enter)) {
69
+ this.searching = false;
70
+ this.input.onChange();
71
+ return;
72
+ }
73
+ if (matchesKey(data, Key.up) || matchesKey(data, Key.down)) {
74
+ this.nextMatch(matchesKey(data, Key.up) ? -1 : 1);
75
+ return;
76
+ }
77
+ this.search.handleInput(data);
78
+ const query = this.search.getValue().trim().toLocaleLowerCase();
79
+ if (query !== this.query) {
80
+ this.query = query;
81
+ this.findMatches();
82
+ const target = this.matches.find((line) => line >= (this.searchOrigin?.top ?? 0)) ?? this.matches[0];
83
+ if (target !== undefined)
84
+ this.goToMatch(target);
85
+ }
86
+ this.input.onChange();
87
+ return;
88
+ }
89
+ if (matchesKey(data, Key.escape) && this.query) {
90
+ this.query = '';
91
+ this.matches = [];
92
+ if (this.searchOrigin)
93
+ Object.assign(this, this.searchOrigin);
94
+ this.input.onChange();
95
+ return;
96
+ }
40
97
  if (matchesKey(data, Key.escape) || matchesKey(data, 'q')) {
41
98
  this.input.onClose();
42
99
  return;
43
100
  }
101
+ if (matchesKey(data, '/')) {
102
+ this.searchOrigin = {
103
+ top: this.top,
104
+ followsEnd: this.followsEnd,
105
+ anchor: this.anchor ? { ...this.anchor } : undefined,
106
+ };
107
+ this.search.setValue(this.query);
108
+ this.searching = true;
109
+ this.input.onChange();
110
+ return;
111
+ }
112
+ if (this.query && (matchesKey(data, 'n') || matchesKey(data, Key.shift('n')))) {
113
+ this.nextMatch(matchesKey(data, Key.shift('n')) ? -1 : 1);
114
+ return;
115
+ }
116
+ if (matchesKey(data, Key.ctrl('e'))) {
117
+ if (!isKeyRepeat(data)) {
118
+ this.expanded = !this.expanded;
119
+ // A detail that disappears on collapse returns to its own heading.
120
+ if (!this.expanded && this.anchor)
121
+ this.anchor.offset = 0;
122
+ this.input.onChange();
123
+ }
124
+ return;
125
+ }
44
126
  if (matchesKey(data, Key.up)) {
45
127
  this.scrollBy(-1);
46
128
  return;
@@ -60,12 +142,14 @@ export class TranscriptViewerOverlay {
60
142
  if (matchesKey(data, Key.home)) {
61
143
  this.followsEnd = false;
62
144
  this.top = 0;
145
+ this.captureAnchor();
63
146
  this.input.onChange();
64
147
  return;
65
148
  }
66
149
  if (matchesKey(data, Key.end)) {
67
150
  this.followsEnd = true;
68
151
  this.top = this.maxTop();
152
+ this.captureAnchor();
69
153
  this.input.onChange();
70
154
  }
71
155
  }
@@ -76,31 +160,74 @@ export class TranscriptViewerOverlay {
76
160
  // navigation chrome only. The footer appears once header + body + footer fit.
77
161
  const showFooter = viewportRows > 2;
78
162
  this.bodyRows = Math.max(0, viewportRows - (showFooter ? VIEWER_CHROME_ROWS : 1));
79
- const document = [...this.input.renderTranscript(safeWidth)];
163
+ this.document = this.input.renderTranscript(safeWidth, this.expanded);
164
+ const document = this.document.lines;
80
165
  this.documentRows = document.length;
166
+ if (!this.followsEnd && this.anchor) {
167
+ const index = this.document.anchors.findIndex((entry) => entry.id === this.anchor.id);
168
+ if (index >= 0) {
169
+ const entry = this.document.anchors[index];
170
+ const end = this.document.anchors[index + 1]?.line ?? document.length;
171
+ this.top = entry.line + Math.min(this.anchor.offset, Math.max(0, end - entry.line - 1));
172
+ }
173
+ }
81
174
  const maxTop = this.maxTop();
82
175
  this.top = this.followsEnd ? maxTop : clamp(this.top, 0, maxTop);
83
- this.followsEnd = this.top === maxTop;
176
+ this.captureAnchor();
177
+ this.findMatches();
84
178
  const visible = document.slice(this.top, this.top + this.bodyRows);
85
179
  const start = visible.length === 0 ? 0 : this.top + 1;
86
180
  const end = visible.length === 0 ? 0 : this.top + visible.length;
87
- const header = padLine(`${ansi.bold('TRANSCRIPT')} ${ansi.dim(`${start}-${end} of ${document.length}`)}`, safeWidth);
181
+ const copy = TUI_COPY_RESOURCES['transcript-reader'][this.input.locale ?? 'en'];
182
+ const header = padLine(`${ansi.bold(copy.title)} ${ansi.dim(`${start}-${end}/${document.length} · ${copy.scope}`)}`, safeWidth);
183
+ const matchingLines = new Set(this.matches);
88
184
  const body = [
89
- ...visible.map((line) => padLine(line, safeWidth)),
185
+ ...visible.map((line, index) => padLine(matchingLines.has(this.top + index) ? ansi.reverse(stripAnsi(line)) : line, safeWidth)),
90
186
  ...Array.from({ length: Math.max(0, this.bodyRows - visible.length) }, () => ' '.repeat(safeWidth)),
91
187
  ];
92
188
  if (!showFooter)
93
189
  return [header, ...body];
94
- const footer = padLine(ansi.dim('↑/↓ scroll · PgUp/PgDn page · Home/End jump · q/Esc close'), safeWidth);
190
+ this.search.focused = this.focused && this.searching;
191
+ const footer = padLine(this.searching
192
+ ? `/ ${this.search.render(Math.max(1, safeWidth - 3))[0] ?? ''}`
193
+ : ansi.dim(this.query
194
+ ? `${this.matches.length} ${copy.matches} · n/N · Esc ${copy.back}`
195
+ : copy.hint), safeWidth);
95
196
  return [header, ...body, footer];
96
197
  }
97
198
  scrollBy(delta) {
199
+ this.matchedLine = undefined;
98
200
  const maxTop = this.maxTop();
99
201
  this.top = clamp(this.top + delta, 0, maxTop);
100
202
  // Follow the tail whenever the clamped position is the end, including the
101
203
  // no-op case where a short transcript cannot move at all: a stray Up key
102
204
  // must not pin the viewer at the head once the transcript grows.
103
205
  this.followsEnd = this.top === maxTop;
206
+ this.captureAnchor();
207
+ this.input.onChange();
208
+ }
209
+ captureAnchor() {
210
+ const entry = [...this.document.anchors].reverse().find((entry) => entry.line <= this.top);
211
+ this.anchor = entry ? { id: entry.id, offset: this.top - entry.line } : undefined;
212
+ }
213
+ findMatches() {
214
+ this.matches = this.query
215
+ ? this.document.lines.flatMap((line, index) => stripAnsi(line).toLocaleLowerCase().includes(this.query) ? [index] : [])
216
+ : [];
217
+ }
218
+ goToMatch(line) {
219
+ this.matchedLine = line;
220
+ this.top = clamp(line, 0, this.maxTop());
221
+ this.followsEnd = false;
222
+ this.captureAnchor();
223
+ }
224
+ nextMatch(direction) {
225
+ const current = this.matchedLine ?? this.top;
226
+ const line = direction > 0
227
+ ? (this.matches.find((line) => line > current) ?? this.matches[0])
228
+ : ([...this.matches].reverse().find((line) => line < current) ?? this.matches.at(-1));
229
+ if (line !== undefined)
230
+ this.goToMatch(line);
104
231
  this.input.onChange();
105
232
  }
106
233
  maxTop() {
@@ -20,8 +20,8 @@ import { activateLocalManagedRuntimeHost } from './runtime-host-local-managed-ac
20
20
  import { randomUUID } from 'node:crypto';
21
21
  import { join } from 'node:path';
22
22
  import { NO_REAL_CONNECTION_CODE } from '@maka/core/connection-error-copy';
23
- import { connectOrSpawnRuntimeHost, connectRuntimeHost, connectRuntimeHostProfile, createClientRuntimeHostProfileCatalog, createRuntimeHostPeerClientFromEnvironment, createRuntimeHostReconnectingConnection, loadOrCreateRuntimeHostClientInstanceId, LOCAL_RUNTIME_HOST_PROFILE, readRuntimeHostConnectionCatalog, RuntimeHostPermanentReconnectError, RuntimeHostRemoteCompatibilityError, runHostHandoff, runtimeHostStartupError, } from '@maka/runtime-host/client';
24
- import { INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, RUNTIME_HOST_PROTOCOL_VERSION, } from '@maka/runtime-host/protocol';
23
+ import { connectOrSpawnRuntimeHost, forceTerminateObservedRegisteredRuntimeHost, connectRuntimeHost, connectRuntimeHostProfile, createClientRuntimeHostProfileCatalog, createRuntimeHostPeerClientFromEnvironment, createRuntimeHostReconnectingConnection, loadOrCreateRuntimeHostClientInstanceId, LOCAL_RUNTIME_HOST_PROFILE, readRuntimeHostConnectionCatalog, RuntimeHostPermanentReconnectError, RuntimeHostRemoteCompatibilityError, runHostHandoff, runtimeHostStartupError, } from '@maka/runtime-host/client';
24
+ import { INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, RUNTIME_HOST_PROTOCOL_VERSION, RUNTIME_HOST_COMPATIBILITY_EPOCH, } from '@maka/runtime-host/protocol';
25
25
  import { readLocalHostDeploymentRecord, resolveRuntimeHostManagedDeploymentAuthority, } from '@maka/runtime-host/operator';
26
26
  import { resolveMakaClientDataRoot } from '@maka/storage/workspace-root';
27
27
  import { isTemporaryNpxInstallation, resolveRuntimeHostNpmGlobalInstallation, } from './runtime-host-cli-installation.js';
@@ -70,6 +70,7 @@ export async function connectRuntimeHostCliConnection(input, overrides = {}) {
70
70
  createPeerClient: createRuntimeHostPeerClientFromEnvironment,
71
71
  resolveInstallation: resolveRuntimeHostNpmGlobalInstallation,
72
72
  isTemporaryNpxInstallation,
73
+ terminateObservedHost: forceTerminateObservedRegisteredRuntimeHost,
73
74
  restartDeployment: restartRuntimeHostNpmGlobalDeployment,
74
75
  sourceRetirementAvailable: runtimeHostNpmGlobalSourceRetirementAvailable,
75
76
  ...overrides,
@@ -157,6 +158,7 @@ export async function connectRuntimeHostCliConnection(input, overrides = {}) {
157
158
  : await deps.readDeploymentRecord(connected.registration.rootId);
158
159
  let replacement;
159
160
  let installation;
161
+ let installationFailure;
160
162
  // On-demand managed Hosts are also ephemeral processes. Their durable
161
163
  // operator authority, not the process lifetime label, decides who may
162
164
  // replace them (including when the Host was already running).
@@ -164,8 +166,8 @@ export async function connectRuntimeHostCliConnection(input, overrides = {}) {
164
166
  try {
165
167
  installation = await deps.resolveInstallation();
166
168
  }
167
- catch {
168
- /* No persistent installation authority. */
169
+ catch (error) {
170
+ installationFailure = error instanceof Error ? error.message : String(error);
169
171
  }
170
172
  const owner = record?.state.kind === 'handoff' ? record.state.from : record?.state.owner;
171
173
  if (installation &&
@@ -173,15 +175,16 @@ export async function connectRuntimeHostCliConnection(input, overrides = {}) {
173
175
  (owner.kind === installation.owner.kind &&
174
176
  owner.installationId === installation.owner.installationId))) {
175
177
  const expectedInstallation = installation;
176
- const canInterrupt = record
177
- ? await deps
178
- .sourceRetirementAvailable({
179
- rootId: connected.registration.rootId,
180
- owner: installation.owner,
181
- source: record.state.selected,
182
- })
183
- .catch(() => false)
184
- : false;
178
+ const canInterrupt = connected.processIdentity !== undefined ||
179
+ (record
180
+ ? await deps
181
+ .sourceRetirementAvailable({
182
+ rootId: connected.registration.rootId,
183
+ owner: installation.owner,
184
+ source: record.state.selected,
185
+ })
186
+ .catch(() => false)
187
+ : false);
185
188
  replacement = {
186
189
  kind: record?.state.kind === 'handoff' ? 'repair' : 'replace',
187
190
  canReplaceIdle: true,
@@ -190,6 +193,9 @@ export async function connectRuntimeHostCliConnection(input, overrides = {}) {
190
193
  const result = await deps.restartDeployment({
191
194
  rootPath: input.rootPath,
192
195
  registration: connected.registration,
196
+ ...(connected.processIdentity
197
+ ? { processIdentity: connected.processIdentity }
198
+ : {}),
193
199
  activeWorkPolicy,
194
200
  expectedInstallation,
195
201
  ...(attemptSignal ? { signal: attemptSignal } : {}),
@@ -210,11 +216,45 @@ export async function connectRuntimeHostCliConnection(input, overrides = {}) {
210
216
  };
211
217
  }
212
218
  }
219
+ // The same observed-process recovery used by Desktop is available to
220
+ // persistent local invocations without a deployment owner. This is an
221
+ // explicit interruption, never an idle inference or an installation claim.
222
+ const processIdentity = connected.processIdentity;
223
+ if (!replacement &&
224
+ !managed &&
225
+ !record &&
226
+ !invocationOwned &&
227
+ connected.registration.lifecycleMode === 'ephemeral' &&
228
+ processIdentity) {
229
+ const registration = connected.registration;
230
+ replacement = {
231
+ kind: 'replace',
232
+ canReplaceIdle: false,
233
+ canInterrupt: true,
234
+ execute: async (policy, progress, consent, attemptSignal) => {
235
+ if (policy !== 'interrupt_active_work' || consent !== 'explicit') {
236
+ return { kind: 'active_work' };
237
+ }
238
+ attemptSignal?.throwIfAborted();
239
+ if ((await deps.resolveManagedAuthority(registration.rootId)) ||
240
+ (await deps.readDeploymentRecord(registration.rootId)))
241
+ return { kind: 'changed' };
242
+ attemptSignal?.throwIfAborted();
243
+ progress('retiring');
244
+ const stopped = await deps.terminateObservedHost({ rootPath: input.rootPath, registration }, { processIdentity, isCurrent: () => !signal?.aborted && !attemptSignal?.aborted });
245
+ if (!stopped)
246
+ return { kind: 'changed' };
247
+ progress('verifying');
248
+ return { kind: 'completed' };
249
+ },
250
+ };
251
+ }
213
252
  return {
214
253
  kind: 'blocked',
215
254
  blocker: {
216
255
  identity: JSON.stringify([
217
256
  connected.registration,
257
+ processIdentity,
218
258
  managed?.record,
219
259
  record,
220
260
  installation,
@@ -226,6 +266,23 @@ export async function connectRuntimeHostCliConnection(input, overrides = {}) {
226
266
  hostEpoch: connected.registration.hostEpoch,
227
267
  },
228
268
  reason: record?.state.kind === 'handoff' ? 'repair' : 'upgrade',
269
+ ...(!replacement
270
+ ? {
271
+ recoveryBlocker: managed || connected.registration.lifecycleMode === 'service'
272
+ ? 'managed'
273
+ : record
274
+ ? 'owner'
275
+ : invocationOwned
276
+ ? 'installation'
277
+ : 'identity',
278
+ diagnostic: [
279
+ `Host compatibility: ${connected.registration.compatibilityEpoch}; client: ${RUNTIME_HOST_COMPATIBILITY_EPOCH}.`,
280
+ installationFailure,
281
+ ]
282
+ .filter(Boolean)
283
+ .join('\n'),
284
+ }
285
+ : {}),
229
286
  ...(connected.handshake?.activity ? { activity: connected.handshake.activity } : {}),
230
287
  mayExitNaturally: !managed &&
231
288
  connected.registration.lifecycleMode === 'ephemeral' &&
@@ -16,37 +16,25 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { createInterface } from 'node:readline';
19
+ import { clearLine, createInterface, cursorTo } from 'node:readline';
20
20
  import { stripVTControlCharacters } from 'node:util';
21
21
  import { formatHostHandoff, } from '@maka/runtime-host/client';
22
- /** One readline lifetime renders the shared decision; it never selects replacement policy. */
22
+ /** The surface owns terminal input; replacement policy stays in the shared handoff. */
23
23
  export function createCliHostHandoffSurface(locale, input = process.stdin, output = process.stderr) {
24
24
  return (submit) => {
25
- const readline = createInterface({ input, output, terminal: output.isTTY === true });
25
+ let readline;
26
26
  let current;
27
27
  let closed = false;
28
28
  let ended = false;
29
+ const releaseReader = () => {
30
+ const previous = readline;
31
+ readline = undefined;
32
+ previous?.close();
33
+ };
29
34
  const cancel = () => {
30
35
  if (current)
31
36
  submit(current.revision, 'cancel');
32
37
  };
33
- readline.on('SIGINT', cancel);
34
- readline.on('close', () => {
35
- ended = true;
36
- if (!closed)
37
- cancel();
38
- });
39
- readline.on('line', (line) => {
40
- if (!current)
41
- return;
42
- const answer = line.trim().toLowerCase();
43
- const action = answer === 'r' ? 'retry' : answer === 'stop' ? 'interrupt' : 'cancel';
44
- if (!current.actions.includes(action)) {
45
- readline.prompt();
46
- return;
47
- }
48
- submit(current.revision, action);
49
- });
50
38
  return {
51
39
  update(view) {
52
40
  if (closed || current?.revision === view.revision)
@@ -56,9 +44,13 @@ export function createCliHostHandoffSurface(locale, input = process.stdin, outpu
56
44
  cancel();
57
45
  return;
58
46
  }
59
- // Discard partially typed consent when the observed target/consequences change.
60
- if (output.isTTY)
61
- readline.write(null, { ctrl: true, name: 'u' });
47
+ // A new observation gets a new input buffer. Ctrl+U leaves text after
48
+ // the cursor intact and is ignored by readline in TERM=dumb.
49
+ releaseReader();
50
+ if (output.isTTY) {
51
+ clearLine(output, 0);
52
+ cursorTo(output, 0);
53
+ }
62
54
  const copy = formatHostHandoff(view, locale);
63
55
  const text = [copy.title, copy.description, copy.detail, view.diagnostic]
64
56
  .filter(Boolean)
@@ -69,12 +61,45 @@ export function createCliHostHandoffSurface(locale, input = process.stdin, outpu
69
61
  if (view.actions.length === 0)
70
62
  return;
71
63
  const options = copy.actions.map(({ action, label }) => `${action === 'interrupt' ? 'stop' : action === 'retry' ? 'r' : 'Enter'}: ${label}`);
72
- readline.setPrompt(options.join(' · ') + ' > ');
73
- readline.prompt();
64
+ // Create the reader only after the explanation is visible, with the
65
+ // final action prompt already installed. Never draw readline's default >.
66
+ const reader = createInterface({
67
+ input,
68
+ output,
69
+ terminal: output.isTTY === true,
70
+ prompt: options.join(' · ') + ' > ',
71
+ });
72
+ readline = reader;
73
+ reader.on('SIGINT', cancel);
74
+ reader.on('close', () => {
75
+ if (readline !== reader)
76
+ return;
77
+ ended = true;
78
+ if (!closed)
79
+ cancel();
80
+ });
81
+ reader.on('line', (line) => {
82
+ if (readline !== reader || current?.revision !== view.revision)
83
+ return;
84
+ const answer = line.trim().toLowerCase();
85
+ const action = answer === 'r'
86
+ ? 'retry'
87
+ : answer === 'stop'
88
+ ? 'interrupt'
89
+ : answer === ''
90
+ ? 'cancel'
91
+ : undefined;
92
+ if (!action || !view.actions.includes(action)) {
93
+ reader.prompt();
94
+ return;
95
+ }
96
+ submit(view.revision, action);
97
+ });
98
+ reader.prompt();
74
99
  },
75
100
  close() {
76
101
  closed = true;
77
- readline.close();
102
+ releaseReader();
78
103
  },
79
104
  };
80
105
  };
@@ -23,7 +23,7 @@ import { dirname, join, posix, resolve, win32 } from 'node:path';
23
23
  import { isDeepStrictEqual } from 'node:util';
24
24
  import { claimLocalHostProcessDeployment, handoffLocalHostProcessDeployment, readLocalHostDeploymentRecord, resolveRuntimeHostManagedDeploymentAuthority, } from '@maka/runtime-host/operator';
25
25
  import { compareProductReleaseVersions } from '@maka/runtime-host/operator/update-package-evidence';
26
- import { connectExistingRuntimeHost } from '@maka/runtime-host/client';
26
+ import { connectExistingRuntimeHost, forceTerminateObservedRegisteredRuntimeHost, } from '@maka/runtime-host/client';
27
27
  import { INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, RUNTIME_HOST_PROTOCOL_VERSION, } from '@maka/runtime-host/protocol';
28
28
  import { resolveRuntimeHostNpmGlobalInstallation, } from './runtime-host-cli-installation.js';
29
29
  import { openRuntimeHostPackageDeployment, prepareRuntimeHostPackageDeployment, resolveRuntimeHostPackageCliPath, } from './runtime-host-package-deployment.js';
@@ -73,6 +73,7 @@ export async function restartRuntimeHostNpmGlobalDeployment(input, authorityOpti
73
73
  connectExisting: connectExistingRuntimeHost,
74
74
  activateTarget: launchRuntimeHostTargetActivator,
75
75
  retireSource: launchRuntimeHostLocalSourceRetirement,
76
+ terminateObservedHost: forceTerminateObservedRegisteredRuntimeHost,
76
77
  ...overrides,
77
78
  };
78
79
  if (await deps.resolveManagedAuthority(input.registration.rootId)) {
@@ -205,6 +206,15 @@ export async function restartRuntimeHostNpmGlobalDeployment(input, authorityOpti
205
206
  }
206
207
  return { kind: 'target_present' };
207
208
  }
209
+ // Older source packages cannot negotiate retirement. Explicit consent may
210
+ // use Desktop's exact-process recovery, inside this existing deployment
211
+ // transaction and only after staging the verified successor.
212
+ if (activeWorkPolicy === 'interrupt_active_work' && input.processIdentity) {
213
+ input.signal?.throwIfAborted();
214
+ const stopped = await deps.terminateObservedHost({ rootPath: input.rootPath, registration: input.registration }, { processIdentity: input.processIdentity, isCurrent: () => !input.signal?.aborted });
215
+ if (!stopped)
216
+ throw new Error('The observed Runtime Host changed before it could be stopped safely');
217
+ }
208
218
  const activated = await activateExactTarget(rootId, stagedTarget, inheritableAuthorityLeaseFd, registration.hostEpoch);
209
219
  if (activated === 'active_work')
210
220
  return { kind: 'active_work' };
@@ -670,9 +670,6 @@ async function normalizeStateRoot(requestedRoot) {
670
670
  throw new RuntimeHostServiceManagerError('invalid_config', `Invalid Runtime Host State Root: ${requestedRoot}`, { cause: error });
671
671
  }
672
672
  }
673
- export async function resolveRuntimeHostManagedStateRoot(requestedRoot) {
674
- return normalizeStateRoot(requestedRoot);
675
- }
676
673
  async function normalizeProjectDirectoryRoots(roots) {
677
674
  let canonicalRoots;
678
675
  try {
@@ -37,6 +37,7 @@ export class RuntimeHostSessionChannel {
37
37
  #now;
38
38
  #onTurnStarted;
39
39
  #onRuntimeResourceChanged;
40
+ #onSessionDomainChanged;
40
41
  #onInteractionPending;
41
42
  #onInteractionResolved;
42
43
  #onTranscriptSettlement;
@@ -71,6 +72,7 @@ export class RuntimeHostSessionChannel {
71
72
  this.#now = options.now;
72
73
  this.#onTurnStarted = options.onTurnStarted;
73
74
  this.#onRuntimeResourceChanged = options.onRuntimeResourceChanged;
75
+ this.#onSessionDomainChanged = options.onSessionDomainChanged;
74
76
  this.#onInteractionPending = options.onInteractionPending;
75
77
  this.#onInteractionResolved = options.onInteractionResolved;
76
78
  this.#onTranscriptSettlement = options.onTranscriptSettlement;
@@ -79,6 +81,16 @@ export class RuntimeHostSessionChannel {
79
81
  this.#onSnapshotChanged = options.onSnapshotChanged;
80
82
  this.#onFailed = options.onFailed;
81
83
  this.#onRecovered = options.onRecovered;
84
+ this.#subscribeSessionDomainChanges(subscription);
85
+ }
86
+ #subscribeSessionDomainChanges(subscription) {
87
+ if (!this.#onSessionDomainChanged)
88
+ return;
89
+ subscription.subscribeSessionDomainChanges((frame) => {
90
+ if (!this.#closing && this.#subscription === subscription) {
91
+ this.#onSessionDomainChanged?.(frame);
92
+ }
93
+ });
82
94
  }
83
95
  static async open(options) {
84
96
  const subscription = await options.connection.openSessionSubscription({
@@ -343,6 +355,7 @@ export class RuntimeHostSessionChannel {
343
355
  return;
344
356
  }
345
357
  this.#subscription = replacement;
358
+ this.#subscribeSessionDomainChanges(replacement);
346
359
  this.#ready = false;
347
360
  this.#pendingFrames.length = 0;
348
361
  void this.#pump(replacement);
@@ -20,6 +20,7 @@ import { randomUUID } from 'node:crypto';
20
20
  import { join } from 'node:path';
21
21
  import { DEFAULT_SESSION_NAME } from '@maka/core/session-name';
22
22
  import { decodeStoredMessage as decodePersistedStoredMessage, deriveTurnRecords, userFacingText, } from '@maka/core/session';
23
+ import { projectSessionTodoItemsForDisplay } from '@maka/core/session-todo';
23
24
  import { markPersisted } from '@maka/core/persisted-value';
24
25
  import { isSideConversationSession } from '@maka/core/side-conversation';
25
26
  import { createSessionCopyCleanupAuthority, } from '@maka/storage/session-copy-cleanup';
@@ -88,6 +89,7 @@ class RuntimeHostMakaSessionDriverImpl {
88
89
  #transcriptRefreshSequence = 0;
89
90
  #startedTurnListeners = new Set();
90
91
  #goalListeners = new Set();
92
+ #todoChangeListeners = new Set();
91
93
  #pendingInteractionListeners = new Set();
92
94
  #claimedTurnIds = new Set();
93
95
  #shellRunListeners = new Set();
@@ -381,6 +383,22 @@ class RuntimeHostMakaSessionDriverImpl {
381
383
  const sessionId = await this.#ensureSession();
382
384
  return this.#request('turn.message.query', { sessionId, messageIds });
383
385
  }
386
+ async queryTodo(sessionId) {
387
+ const currentSessionId = this.#requireSession('query Todo');
388
+ if (sessionId !== currentSessionId) {
389
+ throw new Error(`Cannot query Todo for a non-current Session: ${sessionId}`);
390
+ }
391
+ const sessionGeneration = this.#sessionGeneration;
392
+ const result = await this.#request('session.todo.query', { sessionId });
393
+ this.#assertCurrentSession(sessionId, sessionGeneration);
394
+ if (result.sessionId !== sessionId) {
395
+ throw new Error(`Runtime Host returned Todo for an unexpected Session: ${result.sessionId}`);
396
+ }
397
+ return {
398
+ sessionId,
399
+ items: projectSessionTodoItemsForDisplay(result.items),
400
+ };
401
+ }
384
402
  async retractQueued() {
385
403
  if (!this.#sessionId)
386
404
  return { text: '', messageIds: [] };
@@ -856,6 +874,10 @@ class RuntimeHostMakaSessionDriverImpl {
856
874
  this.#goalListeners.add(listener);
857
875
  return () => this.#goalListeners.delete(listener);
858
876
  }
877
+ subscribeTodoChanges(listener) {
878
+ this.#todoChangeListeners.add(listener);
879
+ return () => this.#todoChangeListeners.delete(listener);
880
+ }
859
881
  async controlGoal(action) {
860
882
  const sessionId = this.#sessionId;
861
883
  if (!sessionId)
@@ -1246,6 +1268,7 @@ class RuntimeHostMakaSessionDriverImpl {
1246
1268
  now: this.#now,
1247
1269
  onTurnStarted: (turn) => this.#publishStartedTurn(turn, sessionGeneration),
1248
1270
  onRuntimeResourceChanged: (sourceSessionId, ref) => this.#publishRuntimeResource(sourceSessionId, ref),
1271
+ onSessionDomainChanged: (frame) => this.#publishSessionDomainChanged(frame, sessionId, sessionGeneration),
1249
1272
  onInteractionPending: (pending) => {
1250
1273
  for (const listener of this.#pendingInteractionListeners)
1251
1274
  listener(pending);
@@ -1261,9 +1284,27 @@ class RuntimeHostMakaSessionDriverImpl {
1261
1284
  for (const listener of this.#goalListeners)
1262
1285
  listener(goal);
1263
1286
  },
1264
- onRecovered: () => this.#refreshRuntimeResources(sessionId),
1287
+ onRecovered: () => {
1288
+ this.#refreshRuntimeResources(sessionId);
1289
+ this.#publishTodoChanged(sessionId, sessionGeneration);
1290
+ },
1265
1291
  });
1266
1292
  }
1293
+ #publishSessionDomainChanged(frame, sessionId, sessionGeneration) {
1294
+ if (frame.domain !== 'todo' ||
1295
+ frame.sessionId !== sessionId ||
1296
+ this.#sessionId !== sessionId ||
1297
+ this.#sessionGeneration !== sessionGeneration) {
1298
+ return;
1299
+ }
1300
+ this.#publishTodoChanged(sessionId, sessionGeneration);
1301
+ }
1302
+ #publishTodoChanged(sessionId, sessionGeneration) {
1303
+ if (this.#sessionId !== sessionId || this.#sessionGeneration !== sessionGeneration)
1304
+ return;
1305
+ for (const listener of this.#todoChangeListeners)
1306
+ listener(sessionId);
1307
+ }
1267
1308
  #publishRuntimeResource(sourceSessionId, ref) {
1268
1309
  void this.#request('runtime.resource.query', {
1269
1310
  kind: 'get',
@@ -17,6 +17,7 @@
17
17
  * under the License.
18
18
  */
19
19
  import { selectListTheme, stripAnsi } from './tui-ansi.js';
20
+ import { stripUnfocusedCursorStyle } from './tui-editor-render.js';
20
21
  export function arrangeAutocompleteAboveEditor(input) {
21
22
  if (!input.autocompleteShowing) {
22
23
  return { lines: input.lines, autocompleteSlotRows: 0, editorRows: input.lines.length };
@@ -72,7 +73,8 @@ export class MakaAutocompleteAboveEditorComponent {
72
73
  this.editor.handleInput(data);
73
74
  }
74
75
  render(width) {
75
- const lines = this.editor.render(width);
76
+ const renderedLines = this.editor.render(width);
77
+ const lines = stripUnfocusedCursorStyle(renderedLines, this.editor.focused);
76
78
  const result = arrangeAutocompleteAboveEditor({
77
79
  lines,
78
80
  autocompleteShowing: this.editor.isShowingAutocomplete(),