maka-agent 0.2.0-dev.27.20260910 → 0.2.0-dev.28.20260911

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 (76) hide show
  1. package/LICENSE +106 -0
  2. package/dist/cli-core.js +7 -0
  3. package/dist/runtime-host-run-command.js +15 -7
  4. package/dist/session-import-command.js +69 -0
  5. package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
  6. package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
  7. package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
  8. package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
  9. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  10. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +9 -4
  11. package/node_modules/@maka/core/dist/diagnostic-log.js +18 -7
  12. package/node_modules/@maka/core/dist/text-sanitize.js +8 -2
  13. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +8 -8
  14. package/node_modules/@maka/runtime/dist/archive-read-tool.js +3 -1
  15. package/node_modules/@maka/runtime/dist/bots/bot-test.js +2 -1
  16. package/node_modules/@maka/runtime/dist/bots/feishu-bridge.js +24 -20
  17. package/node_modules/@maka/runtime/dist/bots/slack-bridge.js +5 -3
  18. package/node_modules/@maka/runtime/dist/bots/wechat-bridge.js +87 -29
  19. package/node_modules/@maka/runtime/dist/bots/wecom-bridge.js +40 -36
  20. package/node_modules/@maka/runtime/dist/computer-use-tools.js +4 -1
  21. package/node_modules/@maka/runtime/dist/deep-research-tools.js +11 -5
  22. package/node_modules/@maka/runtime/dist/edit-replace.js +2 -2
  23. package/node_modules/@maka/runtime/dist/filesystem-worker/operations.js +5 -6
  24. package/node_modules/@maka/runtime/dist/memory-extraction-evidence.js +7 -1
  25. package/node_modules/@maka/runtime/dist/memory-extraction.js +12 -7
  26. package/node_modules/@maka/runtime/dist/openai-responses-websocket.js +2 -5
  27. package/node_modules/@maka/runtime/dist/plugin-composition-loader.js +11 -2
  28. package/node_modules/@maka/runtime/dist/request-customization-fetch.js +14 -7
  29. package/node_modules/@maka/runtime/dist/session-export.js +1 -0
  30. package/node_modules/@maka/runtime/dist/session-import.js +100 -0
  31. package/node_modules/@maka/runtime/dist/session-manager.js +6 -1
  32. package/node_modules/@maka/runtime/dist/shell-run-manager.js +9 -6
  33. package/node_modules/@maka/runtime/dist/shell-run-tool-result.js +13 -6
  34. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +43 -10
  35. package/node_modules/@maka/runtime/dist/stream-graph-handoff.js +6 -1
  36. package/node_modules/@maka/runtime/dist/subscription-model-fetch.js +1 -4
  37. package/node_modules/@maka/runtime/dist/system-prompt/workspace-instructions.js +11 -6
  38. package/node_modules/@maka/runtime/dist/tavily-search.js +8 -2
  39. package/node_modules/@maka/runtime/dist/text-line-window.js +49 -0
  40. package/node_modules/@maka/runtime/dist/tool-output.js +35 -12
  41. package/node_modules/@maka/runtime/dist/unified-diff.js +40 -10
  42. package/node_modules/@maka/runtime/dist/web-fetch-tool.js +4 -2
  43. package/node_modules/@maka/runtime/dist/workers/filesystem-worker.js +82 -33
  44. package/node_modules/@maka/runtime/dist/workspace-executor.js +2 -6
  45. package/node_modules/@maka/runtime/package.json +2 -1
  46. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +6 -6
  47. package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +7 -4
  48. package/node_modules/@maka/runtime-host/dist/protocol/artifact.js +2 -2
  49. package/node_modules/@maka/runtime-host/dist/protocol/index.js +2 -1
  50. package/node_modules/@maka/runtime-host/dist/protocol/session-transcript.js +3 -2
  51. package/node_modules/@maka/runtime-host/dist/protocol/skill-catalog.js +2 -1
  52. package/node_modules/@maka/runtime-host/dist/server/artifact-coordinator.js +2 -0
  53. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +29 -4
  54. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +6 -1
  55. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +35 -4
  56. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +24 -8
  57. package/node_modules/@maka/runtime-host/dist/server/root-admission-owner.js +17 -8
  58. package/node_modules/@maka/runtime-host/dist/server/runtime-resource-projection.js +3 -9
  59. package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +20 -21
  60. package/node_modules/@maka/runtime-host/dist/server/session-transcript-pager.js +24 -2
  61. package/node_modules/@maka/runtime-host/dist/server/skill-catalog-coordinator.js +18 -2
  62. package/node_modules/@maka/runtime-host/dist/transport/framed-byte-stream-transport.js +4 -1
  63. package/node_modules/@maka/runtime-host/dist/transport/local-ipc-framing.js +4 -2
  64. package/node_modules/@maka/runtime-host/dist/transport/peer-native.js +9 -8
  65. package/node_modules/@maka/runtime-host/dist/transport/resumable-peer-stream.js +14 -9
  66. package/node_modules/@maka/storage/dist/context-offload-snapshot.js +19 -4
  67. package/node_modules/@maka/storage/dist/context-value-mutation-gate.js +59 -0
  68. package/node_modules/@maka/storage/dist/session-bundle-policy.js +579 -7
  69. package/node_modules/@maka/storage/dist/session-message-projection.js +8 -4
  70. package/node_modules/@maka/storage/dist/sqlite-context-offload-store.js +18 -4
  71. package/node_modules/@maka/storage/dist/stable-storage.js +2 -1
  72. package/node_modules/@modelcontextprotocol/client/dist/src-D_zzAWoS.mjs +22 -5
  73. package/node_modules/@modelcontextprotocol/client/dist/src-NAgB4Mp8.cjs +22 -5
  74. package/node_modules/zod/v4/core/memoizer.cjs +12 -16
  75. package/node_modules/zod/v4/core/memoizer.js +12 -16
  76. package/package.json +1 -1
@@ -172,10 +172,11 @@ export class WechatBridge extends BaseBotAdapter {
172
172
  const baseUrl = normalizeWechatBridgeUrl(this.settings.webhookUrl);
173
173
  if (!baseUrl)
174
174
  return;
175
+ const cursor = { sinceEpochSeconds };
175
176
  while (this.running) {
176
177
  this.abortController = new AbortController();
177
178
  try {
178
- const response = await proxiedFetch(`${baseUrl}/messages/stream?since=${sinceEpochSeconds}`, {
179
+ const response = await proxiedFetch(`${baseUrl}/messages/stream?since=${cursor.sinceEpochSeconds}`, {
179
180
  method: 'GET',
180
181
  headers: wechatBridgeHeaders(this.settings),
181
182
  signal: this.abortController.signal,
@@ -183,19 +184,8 @@ export class WechatBridge extends BaseBotAdapter {
183
184
  });
184
185
  if (!response.ok || !response.body)
185
186
  throw new Error(`WeChat stream HTTP ${response.status}`);
186
- for await (const raw of readSseJsonObjects(response.body)) {
187
- const messages = Array.isArray(raw) ? raw : [raw];
188
- for (const message of messages) {
189
- const event = mapWechatBridgeMessage(message);
190
- if (!event)
191
- continue;
192
- sinceEpochSeconds = Math.max(sinceEpochSeconds, Math.floor(event.receivedAt / 1000));
193
- this.readiness = 'operational';
194
- this.reason = undefined;
195
- this.emitIncomingMessage(event);
196
- this.emitStatusChange();
197
- }
198
- }
187
+ const messages = readSseJsonObjects(response.body);
188
+ while (await this.processNextLiveMessages(messages, cursor)) { }
199
189
  if (this.running)
200
190
  await sleep(1_000);
201
191
  }
@@ -212,6 +202,31 @@ export class WechatBridge extends BaseBotAdapter {
212
202
  }
213
203
  }
214
204
  }
205
+ async processNextLiveMessages(stream, cursor) {
206
+ // Keep the iterator result and mapped events out of the next idle read's frame.
207
+ const next = await stream.next();
208
+ if (next.done)
209
+ return false;
210
+ try {
211
+ const messages = Array.isArray(next.value) ? next.value : [next.value];
212
+ for (const message of messages) {
213
+ const event = mapWechatBridgeMessage(message);
214
+ if (!event)
215
+ continue;
216
+ cursor.sinceEpochSeconds = Math.max(cursor.sinceEpochSeconds, Math.floor(event.receivedAt / 1000));
217
+ this.readiness = 'operational';
218
+ this.reason = undefined;
219
+ this.emitIncomingMessage(event);
220
+ this.emitStatusChange();
221
+ }
222
+ return true;
223
+ }
224
+ catch (error) {
225
+ // Match for-await: close after processing failures, preserving the original error.
226
+ await stream.return(undefined).catch(() => { });
227
+ throw error;
228
+ }
229
+ }
215
230
  async streamIlinkMessages(cursor) {
216
231
  const baseUrl = normalizeWechatIlinkBaseUrl(this.settings.webhookUrl);
217
232
  const token = this.settings.token.trim();
@@ -402,24 +417,67 @@ export function mapWechatIlinkMessage(raw) {
402
417
  }
403
418
  export async function* readSseJsonObjects(body) {
404
419
  const decoder = new TextDecoder();
420
+ const iterator = body[Symbol.asyncIterator]();
405
421
  let buffer = '';
406
- for await (const chunk of body) {
407
- buffer += decoder.decode(chunk, { stream: true });
408
- let boundary = findSseBoundary(buffer);
409
- while (boundary) {
410
- const event = buffer.slice(0, boundary.index);
411
- buffer = buffer.slice(boundary.index + boundary.length);
412
- const data = event
413
- .split(/\r?\n/)
414
- .filter((line) => line.startsWith('data:'))
415
- .map((line) => line.slice(5).trimStart())
416
- .join('\n')
417
- .trim();
418
- if (data)
419
- yield JSON.parse(data);
420
- boundary = findSseBoundary(buffer);
422
+ let closeIterator = false;
423
+ try {
424
+ while (true) {
425
+ // A rejected next() does not close its iterator under for-await semantics.
426
+ closeIterator = false;
427
+ let text = await readSseTextChunk(iterator, decoder);
428
+ if (text === undefined)
429
+ return;
430
+ closeIterator = true;
431
+ buffer += text;
432
+ text = undefined;
433
+ let boundary = findSseBoundary(buffer);
434
+ while (boundary) {
435
+ let data = sseEventData(buffer.slice(0, boundary.index));
436
+ buffer = buffer.slice(boundary.index + boundary.length);
437
+ if (data)
438
+ yield JSON.parse(data);
439
+ data = '';
440
+ boundary = findSseBoundary(buffer);
441
+ }
442
+ }
443
+ }
444
+ catch (error) {
445
+ if (closeIterator) {
446
+ closeIterator = false;
447
+ try {
448
+ await iterator.return?.();
449
+ }
450
+ catch { }
421
451
  }
452
+ throw error;
453
+ }
454
+ finally {
455
+ if (closeIterator)
456
+ await iterator.return?.();
457
+ }
458
+ }
459
+ async function readSseTextChunk(iterator, decoder) {
460
+ const next = await iterator.next();
461
+ if (next.done)
462
+ return;
463
+ try {
464
+ return decoder.decode(next.value, { stream: true });
422
465
  }
466
+ catch (error) {
467
+ try {
468
+ await iterator.return?.();
469
+ }
470
+ catch { }
471
+ throw error;
472
+ }
473
+ }
474
+ function sseEventData(event) {
475
+ return event
476
+ .split(/\r?\n/)
477
+ .filter((line) => line.startsWith('data:'))
478
+ .map((line) => line.slice(5).trimStart())
479
+ .join('\n')
480
+ .trim();
423
481
  }
424
482
  export async function testWechatBridge(channel) {
425
483
  const baseUrl = normalizeWechatBridgeUrl(channel.webhookUrl);
@@ -16,7 +16,7 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { WSClient } from '@wecom/aibot-node-sdk';
19
+ import { createRequire } from 'node:module';
20
20
  import { BaseBotAdapter, botReadinessFromSettings } from './base-adapter.js';
21
21
  const AUTH_TIMEOUT_MS = 15_000;
22
22
  /** Detach bridge callbacks before disconnect can synchronously emit. */
@@ -78,36 +78,39 @@ export class WeComBotBridge extends BaseBotAdapter {
78
78
  return;
79
79
  }
80
80
  this.explicitlyStopped = false;
81
- const client = new WSClient({
82
- botId,
83
- secret,
84
- maxReconnectAttempts: -1,
85
- logger: quietLogger,
86
- });
87
- this.client = client;
88
- this.wire(client, botId);
89
- await new Promise((resolve, reject) => {
90
- let settled = false;
91
- const onAuthenticated = () => finish(resolve);
92
- const onError = (error) => finish(() => reject(error));
93
- const cleanup = () => {
94
- clearTimeout(timer);
95
- client.off('authenticated', onAuthenticated);
96
- client.off('error', onError);
97
- };
98
- const finish = (fn) => {
99
- if (settled)
100
- return;
101
- settled = true;
102
- cleanup();
103
- fn();
104
- };
105
- const timer = setTimeout(() => finish(() => reject(new Error('WeCom authentication timed out'))), AUTH_TIMEOUT_MS);
106
- client.once('authenticated', onAuthenticated);
107
- client.once('error', onError);
108
- client.connect();
109
- })
110
- .then(() => {
81
+ let startedClient;
82
+ try {
83
+ const { WSClient } = createRequire(import.meta.url)('@wecom/aibot-node-sdk');
84
+ const client = new WSClient({
85
+ botId,
86
+ secret,
87
+ maxReconnectAttempts: -1,
88
+ logger: quietLogger,
89
+ });
90
+ startedClient = client;
91
+ this.client = client;
92
+ this.wire(client, botId);
93
+ await new Promise((resolve, reject) => {
94
+ let settled = false;
95
+ const onAuthenticated = () => finish(resolve);
96
+ const onError = (error) => finish(() => reject(error));
97
+ const cleanup = () => {
98
+ clearTimeout(timer);
99
+ client.off('authenticated', onAuthenticated);
100
+ client.off('error', onError);
101
+ };
102
+ const finish = (fn) => {
103
+ if (settled)
104
+ return;
105
+ settled = true;
106
+ cleanup();
107
+ fn();
108
+ };
109
+ const timer = setTimeout(() => finish(() => reject(new Error('WeCom authentication timed out'))), AUTH_TIMEOUT_MS);
110
+ client.once('authenticated', onAuthenticated);
111
+ client.once('error', onError);
112
+ client.connect();
113
+ });
111
114
  if (this.explicitlyStopped || this.client !== client)
112
115
  return;
113
116
  this.running = true;
@@ -116,17 +119,18 @@ export class WeComBotBridge extends BaseBotAdapter {
116
119
  this.reason = undefined;
117
120
  this.readiness = 'credentials_valid';
118
121
  this.emitStatusChange();
119
- })
120
- .catch((error) => {
121
- if (this.explicitlyStopped || this.client !== client)
122
+ }
123
+ catch (error) {
124
+ if (this.explicitlyStopped || (startedClient && this.client !== startedClient))
122
125
  return;
123
126
  this.running = false;
124
127
  this.recordFailure(error);
125
128
  this.readiness = 'configured';
126
129
  this.emitStatusChange();
127
130
  this.client = null;
128
- closeWeComClient(client);
129
- });
131
+ if (startedClient)
132
+ closeWeComClient(startedClient);
133
+ }
130
134
  }
131
135
  async stop() {
132
136
  this.explicitlyStopped = true;
@@ -945,6 +945,7 @@ export function buildComputerUseTools(deps) {
945
945
  release();
946
946
  if (invocationQueues.get(sessionId) === current) {
947
947
  invocationQueues.delete(sessionId);
948
+ presentationGenerations.delete(sessionId);
948
949
  }
949
950
  }
950
951
  }
@@ -2385,7 +2386,9 @@ export function buildComputerUseTools(deps) {
2385
2386
  }
2386
2387
  const tools = [tool];
2387
2388
  tools.clearSession = (sessionId) => {
2388
- presentationGenerations.set(sessionId, (presentationGenerations.get(sessionId) ?? 0) + 1);
2389
+ if (invocationQueues.has(sessionId)) {
2390
+ presentationGenerations.set(sessionId, (presentationGenerations.get(sessionId) ?? 0) + 1);
2391
+ }
2389
2392
  for (const wake of presentationQueueWaiters.get(sessionId) ?? [])
2390
2393
  wake();
2391
2394
  for (const wake of presentationWaiters.get(sessionId) ?? [])
@@ -142,14 +142,20 @@ function buildReadArtifactTool(deps) {
142
142
  }
143
143
  const offset = input.offset_chars ?? 0;
144
144
  const maxChars = input.max_chars ?? DEEP_RESEARCH_ARTIFACT_READ_DEFAULT_CHARS;
145
- const characters = Array.from(read.text);
146
- const end = Math.min(characters.length, offset + maxChars);
147
- const chunk = safeResearchArtifactContent(characters.slice(offset, end).join(''));
145
+ const selected = [];
146
+ let total = 0;
147
+ for (const character of read.text) {
148
+ if (total >= offset && total < offset + maxChars)
149
+ selected.push(character);
150
+ total += 1;
151
+ }
152
+ const end = Math.min(total, offset + maxChars);
153
+ const chunk = safeResearchArtifactContent(selected.join(''));
148
154
  return [
149
- `<deep-research-artifact id="${ref.artifactId}" role="${ref.role}" offset="${offset}" end="${end}" total="${characters.length}">`,
155
+ `<deep-research-artifact id="${ref.artifactId}" role="${ref.role}" offset="${offset}" end="${end}" total="${total}">`,
150
156
  `Name: ${normalizeInlineText(ref.name)}`,
151
157
  ...(ref.locator ? [`Locator: ${normalizeInlineText(ref.locator)}`] : []),
152
- `Truncated: ${end < characters.length}`,
158
+ `Truncated: ${end < total}`,
153
159
  '',
154
160
  chunk,
155
161
  '</deep-research-artifact>',
@@ -139,10 +139,10 @@ export function computeEditedSource(source, oldString, newString, where) {
139
139
  function finish(content, span, replacement, matchedVia) {
140
140
  const index = content.indexOf(span);
141
141
  const before = content.slice(0, index);
142
- const startLine = before.split('\n').length;
142
+ const startLine = countOccurrences(before, '\n') + 1;
143
143
  // A trailing newline in the span is the last line's terminator, not an
144
144
  // extra line, so it must not bump endLine.
145
- const spanLineCount = span.split('\n').length - (span.endsWith('\n') ? 1 : 0);
145
+ const spanLineCount = countOccurrences(span, '\n') + 1 - (span.endsWith('\n') ? 1 : 0);
146
146
  const endLine = startLine + Math.max(spanLineCount, 1) - 1;
147
147
  // slice-join (not String.replace) so `$&`/`$1` in newString are literal.
148
148
  const next = before + replacement + content.slice(index + span.length);
@@ -25,6 +25,7 @@ import { sandboxPathApi } from './sandbox-paths.js';
25
25
  import { sandboxBoundaryExpansionAllowsPath } from '@maka/core/sandbox-boundary';
26
26
  import { ApplyPatchRejectedError, applyUpdateToContent, createPatchedFile, } from '../apply-patch-file.js';
27
27
  import { computeEditedSource } from '../edit-replace.js';
28
+ import { readTextLineWindow } from '../text-line-window.js';
28
29
  import { createEditUnifiedDiff, createUnifiedDiff } from '../unified-diff.js';
29
30
  import { compareAndDeleteEntry, hostVisibilityAfterWrite, openStableTarget, readModifyWriteThroughHandle, StableWriteFailure, writeThroughHandle, } from '../file-stable-write.js';
30
31
  import { isSupportedImagePath, readWorkspaceImage } from '../image-file.js';
@@ -75,12 +76,10 @@ export async function executeFilesystemOperation(operation, operationBoundary, d
75
76
  }
76
77
  }
77
78
  const content = await fs.readFile(path, 'utf8');
78
- if (operation.offset === undefined && operation.limit === undefined)
79
- return { kind: 'read', content };
80
- const lines = content.split('\n');
81
- const start = operation.offset ?? 0;
82
- const end = operation.limit ? start + operation.limit : lines.length;
83
- return { kind: 'read', content: lines.slice(start, end).join('\n') };
79
+ return {
80
+ kind: 'read',
81
+ content: readTextLineWindow(content, operation.offset, operation.limit),
82
+ };
84
83
  }
85
84
  case 'write': {
86
85
  const path = await resolveWritableAllowed(operation.cwd, operation.path, 'Write', operationBoundary);
@@ -273,7 +273,13 @@ function historyRole(event) {
273
273
  return event.role === 'model' ? 'assistant' : 'user';
274
274
  }
275
275
  function sliceCodePoints(value, maximum) {
276
- return Array.from(value).slice(0, maximum).join('');
276
+ const prefix = [];
277
+ for (const point of value) {
278
+ if (prefix.length >= maximum)
279
+ break;
280
+ prefix.push(point);
281
+ }
282
+ return prefix.join('');
277
283
  }
278
284
  function uniqueSorted(values) {
279
285
  return [...new Set(values)].sort((left, right) => left - right);
@@ -369,7 +369,7 @@ export class MemoryExtractionEngine {
369
369
  return undefined;
370
370
  maximumSplitIndex = requestedTurnStart;
371
371
  }
372
- const split = memoryRangeSplitCandidates(pendingEntries, maximumSplitIndex).find(({ first, second }) => {
372
+ const split = findMemoryRangeSplit(pendingEntries, maximumSplitIndex, ({ first, second }) => {
373
373
  const firstThroughOrdinal = first.at(-1).ordinal;
374
374
  const firstTrigger = 'extract';
375
375
  const firstPrepared = this.prepareRange({
@@ -924,9 +924,9 @@ function memorySegmentOperationId(operationId, afterOrdinal, throughOrdinal, por
924
924
  .update(JSON.stringify({ operationId, afterOrdinal, throughOrdinal }))
925
925
  .digest('hex')}`;
926
926
  }
927
- function memoryRangeSplitCandidates(entries, maximumSplitIndex = entries.length - 1) {
927
+ function findMemoryRangeSplit(entries, maximumSplitIndex = entries.length - 1, accepts) {
928
928
  if (entries.length < 2)
929
- return [];
929
+ return undefined;
930
930
  const weights = entries.map(memoryRangeEventWeight);
931
931
  const total = weights.reduce((sum, weight) => sum + weight, 0);
932
932
  let prefix = 0;
@@ -942,11 +942,16 @@ function memoryRangeSplitCandidates(entries, maximumSplitIndex = entries.length
942
942
  };
943
943
  })
944
944
  .filter(({ index }) => index <= maximumSplitIndex);
945
- return candidates
946
- .sort((left, right) => Number(right.turnBoundary) - Number(left.turnBoundary) ||
945
+ candidates.sort((left, right) => Number(right.turnBoundary) - Number(left.turnBoundary) ||
947
946
  left.distance - right.distance ||
948
- left.index - right.index)
949
- .map(({ index }) => ({ first: entries.slice(0, index), second: entries.slice(index) }));
947
+ left.index - right.index);
948
+ // Keep only the candidate being checked; each pair copies the entire range.
949
+ for (const { index } of candidates) {
950
+ const split = { first: entries.slice(0, index), second: entries.slice(index) };
951
+ if (accepts(split))
952
+ return split;
953
+ }
954
+ return undefined;
950
955
  }
951
956
  function preparedMemoryRangeFits(prepared, trigger) {
952
957
  if (!prepared)
@@ -221,11 +221,8 @@ function prepareWireRequest(body, baseline) {
221
221
  const { previous_response_id: _previousResponseId, ...currentProperties } = body;
222
222
  const fullBody = {
223
223
  ...currentProperties,
224
- input: [
225
- ...structuredClone(baseline.fullBody.input),
226
- ...structuredClone(baseline.responseOutput),
227
- ...structuredClone(body.input),
228
- ],
224
+ // These JSON values are privately owned; only the combined input array needs to be new.
225
+ input: [...baseline.fullBody.input, ...baseline.responseOutput, ...body.input],
229
226
  };
230
227
  const sameProperties = isDeepStrictEqual(requestProperties(baseline.fullBody), requestProperties(fullBody));
231
228
  return {
@@ -16,6 +16,7 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
+ /// <reference lib="es2023.collection" />
19
20
  import { Context } from './plugin-kernel.js';
20
21
  import { fiberStateName, MakaPluginRuntimeError, MakaPluginTransactionBuffer, validateCompositionEntry, validatePluginPackage, validatePluginRootId, } from './plugin-runtime.js';
21
22
  const FIBER_PENDING = 0;
@@ -25,7 +26,13 @@ export class MakaCompositionLoader {
25
26
  #packages = new Map();
26
27
  #roots = new Map();
27
28
  #entries = new Map();
29
+ // Contexts and services can outlive their entries, so intern by symbol lifetime.
28
30
  #isolationLabels = new Map();
31
+ #isolationLabelFinalizer = new FinalizationRegistry(({ label, reference }) => {
32
+ // An old finalizer must not evict a newer incarnation of the same label.
33
+ if (this.#isolationLabels.get(label) === reference)
34
+ this.#isolationLabels.delete(label);
35
+ });
29
36
  #transaction;
30
37
  #compositionGeneration = 0;
31
38
  #fiberGeneration = 0;
@@ -675,10 +682,12 @@ export class MakaCompositionLoader {
675
682
  });
676
683
  }
677
684
  #isolationLabel(label) {
678
- let symbol = this.#isolationLabels.get(label);
685
+ let symbol = this.#isolationLabels.get(label)?.deref();
679
686
  if (!symbol) {
680
687
  symbol = Symbol(label);
681
- this.#isolationLabels.set(label, symbol);
688
+ const reference = new WeakRef(symbol);
689
+ this.#isolationLabels.set(label, reference);
690
+ this.#isolationLabelFinalizer.register(symbol, { label, reference });
682
691
  }
683
692
  return symbol;
684
693
  }
@@ -27,6 +27,13 @@ export function createRequestCustomizationFetch(upstream, customization) {
27
27
  return upstream;
28
28
  }
29
29
  return async (input, init) => {
30
+ // A temporary Request's derived signal can lose abort forwarding after GC.
31
+ // Keep the caller's effective signal; explicit null disconnects input.signal.
32
+ const signal = init?.signal !== undefined
33
+ ? init.signal
34
+ : input instanceof Request
35
+ ? input.signal
36
+ : undefined;
30
37
  const request = new Request(input, init);
31
38
  const nextHeaders = new Headers(request.headers);
32
39
  for (const [name, value] of Object.entries(headers)) {
@@ -42,9 +49,9 @@ export function createRequestCustomizationFetch(upstream, customization) {
42
49
  if (customization.finalizeBody) {
43
50
  throw new Error('Request body finalizer requires a JSON object request body');
44
51
  }
45
- return upstream(request.url, requestInit(request, nextHeaders, body));
52
+ return upstream(request.url, requestInit(request, nextHeaders, body, signal));
46
53
  }
47
- const generatedBody = await parseRequestBody(request, customization.finalizeBody
54
+ const generatedBody = await parseRequestBody(body, customization.finalizeBody
48
55
  ? 'Request body finalizer requires a JSON object request body'
49
56
  : 'Extra request body can only be applied to a JSON object request');
50
57
  for (const key of bodyOverlayKeys) {
@@ -56,15 +63,15 @@ export function createRequestCustomizationFetch(upstream, customization) {
56
63
  body = JSON.stringify(customization.finalizeBody ? customization.finalizeBody(customizedBody) : customizedBody);
57
64
  nextHeaders.delete('content-length');
58
65
  }
59
- return upstream(request.url, requestInit(request, nextHeaders, body));
66
+ return upstream(request.url, requestInit(request, nextHeaders, body, signal));
60
67
  };
61
68
  }
62
- function requestInit(request, headers, body) {
69
+ function requestInit(request, headers, body, signal) {
63
70
  return {
64
71
  method: request.method,
65
72
  headers: [...headers.entries()],
66
73
  ...(body === null ? {} : { body, duplex: 'half' }),
67
- signal: request.signal,
74
+ signal,
68
75
  cache: request.cache,
69
76
  credentials: request.credentials,
70
77
  integrity: request.integrity,
@@ -81,10 +88,10 @@ function requestHasJsonBody(request) {
81
88
  const contentType = request.headers.get('content-type');
82
89
  return (contentType === null || /(^|\s|;)application\/(?:[\w.+-]+\+)?json(?:\s*;|$)/i.test(contentType));
83
90
  }
84
- async function parseRequestBody(request, invalidBodyMessage) {
91
+ async function parseRequestBody(body, invalidBodyMessage) {
85
92
  let parsed;
86
93
  try {
87
- parsed = JSON.parse(await request.clone().text());
94
+ parsed = JSON.parse(new TextDecoder().decode(body));
88
95
  }
89
96
  catch {
90
97
  throw new Error(invalidBodyMessage);
@@ -81,6 +81,7 @@ export async function exportSessionBundle(input) {
81
81
  allowShared: true,
82
82
  destinationRoot: stateRoot,
83
83
  sessionId: input.sessionId,
84
+ ...(input.lease ? { lease: input.lease } : {}),
84
85
  requireQuiescent: true,
85
86
  includeSubtree: true,
86
87
  omitDiagnostics: true,
@@ -0,0 +1,100 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ /**
20
+ * Import a `.maka-session` bundle into a workspace.
21
+ *
22
+ * The mirror of the export: `hydrate()` verifies and unpacks what `pack()`
23
+ * sealed, and `importSessionBundleState()` merges what the export policy
24
+ * prepared. This module is the thin part -- verify, merge, report.
25
+ *
26
+ * The bundle names its connection by slug; nothing here resolves it. A Session
27
+ * whose slug this workspace does not have arrives in the state the app already
28
+ * shows for that: stale, and routable once the user picks a connection.
29
+ */
30
+ import { mkdtemp, rm, stat } from 'node:fs/promises';
31
+ import { join } from 'node:path';
32
+ import { tmpdir } from 'node:os';
33
+ import { SessionBundleFileError, } from '@maka/storage/session-bundle-contract';
34
+ import { createSessionBundleFileService } from '@maka/storage/session-bundle-file-service';
35
+ import { importSessionBundleState, SessionBundleImportError, } from '@maka/storage/session-bundle-policy';
36
+ import { SESSION_EXPORT_BUNDLE_LIMITS } from './session-export.js';
37
+ export async function importSessionBundle(input) {
38
+ if (!(await isDirectory(input.workspaceRoot))) {
39
+ return {
40
+ ok: false,
41
+ reason: { kind: 'workspace_not_found', workspaceRoot: input.workspaceRoot },
42
+ };
43
+ }
44
+ const limits = input.limits ?? SESSION_EXPORT_BUNDLE_LIMITS;
45
+ const staging = await mkdtemp(join(tmpdir(), 'maka-session-import-'));
46
+ try {
47
+ const service = createSessionBundleFileService();
48
+ // Read the identity before hydrating: the codec verifies a bundle against a
49
+ // Session id, and the bundle is the only thing that knows which one.
50
+ const inspection = await service.inspect({ source: { path: input.source }, limits });
51
+ const manifest = readExportManifest(inspection.stateIdentity.bytes);
52
+ const hydration = await service.hydrate({
53
+ source: { path: input.source },
54
+ limits,
55
+ expectedSessionId: manifest?.rootSessionId ?? inspection.manifest.envelope.sessionId,
56
+ destinationRoot: join(staging, 'hydrated'),
57
+ });
58
+ const merged = await importSessionBundleState({
59
+ stateRoot: input.workspaceRoot,
60
+ bundleStateRoot: hydration.stateRoot,
61
+ ...(input.lease ? { lease: input.lease } : {}),
62
+ });
63
+ return { ok: true, ...merged, manifest };
64
+ }
65
+ catch (error) {
66
+ if (error instanceof SessionBundleImportError) {
67
+ switch (error.code) {
68
+ case 'session_exists':
69
+ return { ok: false, reason: { kind: 'session_exists', message: error.message } };
70
+ case 'schema_unsupported':
71
+ return { ok: false, reason: { kind: 'schema_unsupported', message: error.message } };
72
+ case 'conflict':
73
+ return { ok: false, reason: { kind: 'conflict', message: error.message } };
74
+ default:
75
+ return { ok: false, reason: { kind: 'io_failed', message: error.message } };
76
+ }
77
+ }
78
+ if (error instanceof SessionBundleFileError) {
79
+ return { ok: false, reason: { kind: 'bundle_unreadable', message: error.message } };
80
+ }
81
+ return { ok: false, reason: { kind: 'io_failed', message: String(error) } };
82
+ }
83
+ finally {
84
+ await rm(staging, { recursive: true, force: true }).catch(() => { });
85
+ }
86
+ }
87
+ function readExportManifest(bytes) {
88
+ try {
89
+ const parsed = JSON.parse(Buffer.from(bytes).toString('utf8'));
90
+ return parsed.format === 'maka.session-export' ? parsed : undefined;
91
+ }
92
+ catch {
93
+ return undefined;
94
+ }
95
+ }
96
+ async function isDirectory(path) {
97
+ return stat(path)
98
+ .then((metadata) => metadata.isDirectory())
99
+ .catch(() => false);
100
+ }
@@ -3608,7 +3608,12 @@ function buildAgentOutputCommittedResult(input) {
3608
3608
  truncated: withoutText.omittedArtifactIds > 0,
3609
3609
  };
3610
3610
  }
3611
- const codePoints = Array.from(text);
3611
+ const codePoints = [];
3612
+ for (const point of text) {
3613
+ if (codePoints.length >= input.maxBytes)
3614
+ break;
3615
+ codePoints.push(point);
3616
+ }
3612
3617
  let low = 0;
3613
3618
  let high = codePoints.length;
3614
3619
  let best = { ...withoutText, textTruncated: true };