maka-agent 0.2.0-dev.41.20260918 → 0.2.0-dev.42.20260918

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 (35) hide show
  1. package/LICENSE +7 -4
  2. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  3. package/node_modules/@maka/core/dist/client-plugin-bridge.js +19 -0
  4. package/node_modules/@maka/core/dist/events.js +58 -7
  5. package/node_modules/@maka/core/dist/interaction.js +38 -4
  6. package/node_modules/@maka/core/dist/llm-connections.js +4 -0
  7. package/node_modules/@maka/core/dist/model-metadata.js +89 -0
  8. package/node_modules/@maka/core/dist/recall.js +198 -15
  9. package/node_modules/@maka/core/dist/redaction.js +8 -0
  10. package/node_modules/@maka/core/dist/session-reference.js +149 -0
  11. package/node_modules/@maka/core/package.json +2 -0
  12. package/node_modules/@maka/runtime/dist/model-factory.js +5 -3
  13. package/node_modules/@maka/runtime/dist/model-history.js +13 -2
  14. package/node_modules/@maka/runtime/dist/plugin-client-bridge-service.js +308 -0
  15. package/node_modules/@maka/runtime/dist/plugin-composition-loader.js +4 -3
  16. package/node_modules/@maka/runtime/dist/plugin-data-services.js +3 -3
  17. package/node_modules/@maka/runtime/dist/recall-tools.js +70 -2
  18. package/node_modules/@maka/runtime/package.json +1 -0
  19. package/node_modules/@maka/runtime-host/dist/protocol/index.js +7 -1
  20. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +5 -0
  21. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +269 -0
  22. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +2 -0
  23. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +46 -6
  24. package/node_modules/@maka/runtime-host/dist/server/extension-package-manifest.js +19 -1
  25. package/node_modules/@maka/runtime-host/dist/server/plugin-package-loader.js +88 -14
  26. package/node_modules/@maka/runtime-host/dist/server/plugin-package-store.js +22 -5
  27. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +122 -1
  28. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +120 -1
  29. package/node_modules/@maka/runtime-host/dist/server/recall-material-fetch.js +92 -0
  30. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +14 -1
  31. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +17 -1
  32. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +67 -3
  33. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +9 -3
  34. package/node_modules/@maka/runtime-host/dist/server/workhub-target-execution-authority.js +161 -0
  35. package/package.json +1 -1
@@ -52,6 +52,9 @@
52
52
  * plus one weight that BM25 has no way to express: a tool result's term
53
53
  * density reflects machine output rather than relevance.
54
54
  */
55
+ import { isCanonicalArtifactEntityId } from './artifacts.js';
56
+ import { formatAttachmentResourceRef, MAX_ATTACHMENT_COUNT } from './attachments.js';
57
+ import { isAttachmentRef } from './events.js';
55
58
  import { validateWorkspacePrivacyContext } from './incognito.js';
56
59
  import { redactSecrets } from './redaction.js';
57
60
  import { SEARCH_QUERY_MAX_CHARS } from './search.js';
@@ -180,6 +183,76 @@ function stripSyntheticText(text, patterns) {
180
183
  stripped = stripped.replace(pattern, '');
181
184
  return stripped;
182
185
  }
186
+ /**
187
+ * The files a message carried. A screenshot pasted under "have a look" leaves
188
+ * no trace in the text, so without this the message is unreachable by any
189
+ * term the user would think to search.
190
+ */
191
+ export function recallMaterials(message) {
192
+ if (message.type !== 'user')
193
+ return [];
194
+ const attachments = message.attachments;
195
+ if (!Array.isArray(attachments) || attachments.length === 0)
196
+ return [];
197
+ // Shape first, then the cap: a record older or stranger than the current
198
+ // shape projects nothing, and a run of those ahead of a valid attachment
199
+ // must not spend the cap that valid one needed.
200
+ return attachments
201
+ .filter(isAttachmentRef)
202
+ .slice(0, MAX_ATTACHMENT_COUNT)
203
+ .map((attachment) => {
204
+ const resource = readableAttachmentResource(attachment);
205
+ const location = retrievableMaterialLocation(attachment);
206
+ return {
207
+ name: attachment.name,
208
+ kind: attachment.kind,
209
+ mimeType: attachment.mimeType,
210
+ bytes: attachment.bytes,
211
+ ...(resource ? { resource } : {}),
212
+ ...(location ?? {}),
213
+ };
214
+ });
215
+ }
216
+ /**
217
+ * The address `Read` accepts, present only for a material `Read` can actually
218
+ * return. `Read` answers an image with the image and anything else with the
219
+ * file's text, and refuses a PDF outright — so a PDF's address is one the
220
+ * caller can only fail on, which is the same reason a material in another
221
+ * Session carries no address.
222
+ */
223
+ function readableAttachmentResource(attachment) {
224
+ if (attachment.kind === 'pdf')
225
+ return null;
226
+ return formatAttachmentResourceRef(attachment.ref);
227
+ }
228
+ /**
229
+ * The pair that names a material a retrieval tool can fetch: the Session that
230
+ * holds it and the artifact inside it. Only a durable Session artifact can be
231
+ * fetched, so a ref of any other kind names nothing.
232
+ */
233
+ function retrievableMaterialLocation(attachment) {
234
+ if (attachment.ref.kind !== 'session_file')
235
+ return null;
236
+ if (!isCanonicalArtifactEntityId(attachment.ref.relativePath))
237
+ return null;
238
+ return {
239
+ sourceSessionId: attachment.ref.sessionId,
240
+ materialId: attachment.ref.relativePath,
241
+ };
242
+ }
243
+ /**
244
+ * What the predicate runs on: a message's prose plus the names of the files it
245
+ * carried, so a material is reachable by the name a person would remember.
246
+ * Names are matched but not returned as text — they come back as materials.
247
+ */
248
+ function recallMatchableText(message) {
249
+ const prose = recallSearchableText(message);
250
+ const materials = recallMaterials(message);
251
+ if (materials.length === 0)
252
+ return prose;
253
+ const names = materials.map((material) => material.name).join('\n');
254
+ return prose === undefined || prose.length === 0 ? names : `${prose}\n${names}`;
255
+ }
183
256
  /** Visits every string value in a JSON-like value, in document order, until the visitor declines. */
184
257
  function collectStringLeaves(value, visit) {
185
258
  if (typeof value === 'string')
@@ -261,7 +334,7 @@ export async function runRecall(request, deps, options = {}) {
261
334
  collected.hits.sort((left, right) => right.score - left.score || compareHitOrder(left, right));
262
335
  const sessionById = new Map(sessions.map((session) => [session.id, session]));
263
336
  const anchors = applySessionQuota(collected.hits, limit);
264
- const passages = assemblePassages(collected.transcripts, anchors, sessionById);
337
+ const passages = assemblePassages(collected.transcripts, anchors, sessionById, options.activeSessionId);
265
338
  return {
266
339
  ok: true,
267
340
  facts,
@@ -334,7 +407,7 @@ export async function expandRecallPassage(request, deps, options = {}) {
334
407
  if (!transcript) {
335
408
  return { ok: false, reason: 'not_found', message: 'That Session was not found.' };
336
409
  }
337
- const message = transcript.find((candidate) => candidate.id === anchorMessageId && recallSearchableText(candidate) !== undefined);
410
+ const message = transcript.find((candidate) => candidate.id === anchorMessageId && isPassageMessage(candidate));
338
411
  if (!message) {
339
412
  return { ok: false, reason: 'not_found', message: 'That passage anchor was not found.' };
340
413
  }
@@ -355,12 +428,77 @@ export async function expandRecallPassage(request, deps, options = {}) {
355
428
  tf: new Map(),
356
429
  matchedTerms: [],
357
430
  score: 0,
358
- }, transcript, session, RECALL_PASSAGE_MAX_BYTES, { before, after });
431
+ }, transcript, session, RECALL_PASSAGE_MAX_BYTES, options.activeSessionId, { before, after });
359
432
  if (!built) {
360
433
  return { ok: false, reason: 'not_found', message: 'That passage could not be rebuilt.' };
361
434
  }
362
435
  return { ok: true, passage: built.passage };
363
436
  }
437
+ /**
438
+ * Brings one material named by a passage into the asking Session.
439
+ *
440
+ * The Session check is the point of this function, not a formality: without it
441
+ * the tool would be a way to read any artifact by id, including from the
442
+ * Sessions recall itself refuses to surface — incognito, retired simulator
443
+ * transcripts, whatever a future rule excludes. A material is retrievable
444
+ * exactly when recall could have shown you the Session it sits in.
445
+ */
446
+ export async function fetchRecallMaterial(request, deps, options = {}) {
447
+ if (options.abortSignal?.aborted)
448
+ return aborted();
449
+ if (!deps.fetchMaterial) {
450
+ return { ok: false, reason: 'not_found', message: 'Materials are unavailable here.' };
451
+ }
452
+ if (typeof request !== 'object' || request === null || Array.isArray(request)) {
453
+ return { ok: false, reason: 'invalid_query', message: 'Material request must be an object.' };
454
+ }
455
+ const record = request;
456
+ const sessionId = optionalString(record.sessionId);
457
+ const materialId = optionalString(record.materialId);
458
+ if (!sessionId || !materialId) {
459
+ return {
460
+ ok: false,
461
+ reason: 'invalid_query',
462
+ message: 'A material is named by its Session and its material id.',
463
+ };
464
+ }
465
+ if (!options.activeSessionId) {
466
+ return { ok: false, reason: 'not_found', message: 'Materials need an active Session.' };
467
+ }
468
+ const privacyPayload = await deps.getPrivacyContext();
469
+ if (options.abortSignal?.aborted)
470
+ return aborted();
471
+ const privacy = validateWorkspacePrivacyContext(privacyPayload);
472
+ if (!privacy.ok || privacy.value.incognitoActive) {
473
+ return {
474
+ ok: false,
475
+ reason: 'incognito_active',
476
+ message: 'Recall is unavailable while incognito is active.',
477
+ };
478
+ }
479
+ const sessions = eligibleSessions(collapseSessionRevisions(await deps.listSessions(), options.activeSessionId), { sessionId, includeArchived: options.includeArchived === true });
480
+ if (options.abortSignal?.aborted)
481
+ return aborted();
482
+ if (!sessions.some((candidate) => candidate.id === sessionId)) {
483
+ return { ok: false, reason: 'not_found', message: 'That material was not found.' };
484
+ }
485
+ const fetched = await deps.fetchMaterial({
486
+ sourceSessionId: sessionId,
487
+ materialId,
488
+ targetSessionId: options.activeSessionId,
489
+ ...(options.abortSignal ? { abortSignal: options.abortSignal } : {}),
490
+ });
491
+ if (options.abortSignal?.aborted)
492
+ return aborted();
493
+ if (!fetched.ok) {
494
+ return {
495
+ ok: false,
496
+ reason: fetched.reason === 'unsupported' ? 'invalid_query' : 'not_found',
497
+ message: fetched.message,
498
+ };
499
+ }
500
+ return { ok: true, content: fetched.content };
501
+ }
364
502
  function normalizeSpan(value) {
365
503
  if (value === undefined)
366
504
  return RECALL_EXPAND_MAX_NEIGHBOURS;
@@ -602,7 +740,7 @@ function verify(sessionId, message, input) {
602
740
  if (sessionId === input.activeSessionId && turnId && input.excludeTurnIds?.has(turnId)) {
603
741
  return undefined;
604
742
  }
605
- const raw = recallSearchableText(message);
743
+ const raw = recallMatchableText(message);
606
744
  if (raw === undefined)
607
745
  return undefined;
608
746
  // A hit is a term that occurs in the text as it was stored *and* still
@@ -738,7 +876,7 @@ function applySessionQuota(hits, limit) {
738
876
  // first when the total cap binds.
739
877
  return selected.sort((left, right) => right.score - left.score);
740
878
  }
741
- function assemblePassages(transcripts, anchors, sessionById) {
879
+ function assemblePassages(transcripts, anchors, sessionById, activeSessionId) {
742
880
  if (anchors.length === 0)
743
881
  return [];
744
882
  const passages = [];
@@ -749,7 +887,7 @@ function assemblePassages(transcripts, anchors, sessionById) {
749
887
  const transcript = transcripts.get(anchor.sessionId);
750
888
  if (!transcript)
751
889
  continue;
752
- const built = buildPassage(anchor, transcript, sessionById.get(anchor.sessionId), remaining);
890
+ const built = buildPassage(anchor, transcript, sessionById.get(anchor.sessionId), remaining, activeSessionId);
753
891
  if (!built)
754
892
  continue;
755
893
  remaining -= built.bytes;
@@ -757,7 +895,7 @@ function assemblePassages(transcripts, anchors, sessionById) {
757
895
  }
758
896
  return passages;
759
897
  }
760
- function buildPassage(anchor, transcript, session, budget, span = {
898
+ function buildPassage(anchor, transcript, session, budget, activeSessionId, span = {
761
899
  before: RECALL_PASSAGE_NEIGHBOURS,
762
900
  after: RECALL_PASSAGE_NEIGHBOURS,
763
901
  }) {
@@ -779,7 +917,7 @@ function buildPassage(anchor, transcript, session, budget, span = {
779
917
  const render = (message, isAnchor) => {
780
918
  if (rendered.has(message.id))
781
919
  return;
782
- const projected = projectPassageMessage(message, isAnchor);
920
+ const projected = projectPassageMessage(message, isAnchor, activeSessionId);
783
921
  if (!projected)
784
922
  return;
785
923
  const overhead = Buffer.byteLength(JSON.stringify({ ...projected, text: '' }), 'utf8');
@@ -816,8 +954,7 @@ function buildPassage(anchor, transcript, session, budget, span = {
816
954
  // the caller can still reach, so it counts toward the continuation flags the
817
955
  // same way one beyond the span does. Reporting otherwise would tell a caller
818
956
  // there is nothing more in a direction recall just cut short.
819
- const omitted = (entries) => entries.some((entry) => !rendered.has(entry.message.id) &&
820
- projectPassageMessage(entry.message, false) !== undefined);
957
+ const omitted = (entries) => entries.some((entry) => !rendered.has(entry.message.id) && isPassageMessage(entry.message));
821
958
  const passage = {
822
959
  sessionId: anchor.sessionId,
823
960
  sessionTitle: redactSecrets(session?.name ?? ''),
@@ -877,13 +1014,36 @@ function collectNeighbours(transcript, anchorIndex, turnId, span) {
877
1014
  function isPassageNeighbour(message) {
878
1015
  return message.type === 'user' || message.type === 'assistant' || message.type === 'tool_call';
879
1016
  }
880
- function projectPassageMessage(message, isAnchor) {
1017
+ /**
1018
+ * Whether a message can appear in a passage at all. Redaction rewrites text
1019
+ * but never empties it, so this decides the same set `projectPassageMessage`
1020
+ * does without paying for redaction on every message a lookup walks past.
1021
+ */
1022
+ function isPassageMessage(message) {
881
1023
  const raw = recallSearchableText(message);
882
- if (raw === undefined)
883
- return undefined;
884
- const text = redactSecrets(raw).trim();
885
- if (text.length === 0)
1024
+ if (raw !== undefined && raw.trim().length > 0)
1025
+ return true;
1026
+ return recallMaterials(message).length > 0;
1027
+ }
1028
+ function projectPassageMessage(message, isAnchor, activeSessionId) {
1029
+ const raw = recallSearchableText(message);
1030
+ const text = raw === undefined ? '' : redactSecrets(raw).trim();
1031
+ // A message whose whole content was a pasted file has no text of its own.
1032
+ // Dropping it would make the file unreachable in exactly the case this
1033
+ // layer exists for.
1034
+ // A file name is user-authored text like any other, so it leaves through the
1035
+ // same redaction the passage body does; the address beside it is a runtime
1036
+ // identifier and carries nothing to redact.
1037
+ const materials = recallMaterials(message).map((material) => ({
1038
+ ...material,
1039
+ name: redactSecrets(material.name),
1040
+ }));
1041
+ if (text.length === 0 && materials.length === 0)
886
1042
  return undefined;
1043
+ // A material carries an address or a location, never both: the address says
1044
+ // "read this now", the location says "ask for it and it will be brought
1045
+ // here". Offering an address `Read` would refuse is the mistake this split
1046
+ // exists to prevent.
887
1047
  return {
888
1048
  messageId: message.id,
889
1049
  role: passageRole(message),
@@ -891,6 +1051,29 @@ function projectPassageMessage(message, isAnchor) {
891
1051
  text,
892
1052
  timestamp: message.ts,
893
1053
  isAnchor,
1054
+ ...(materials.length > 0
1055
+ ? { materials: materials.map((material) => addressOrLocation(material, activeSessionId)) }
1056
+ : {}),
1057
+ };
1058
+ }
1059
+ /**
1060
+ * Reachability is a fact about where the material itself is stored, not about
1061
+ * the passage that mentioned it. Keying it on the passage would offer an
1062
+ * address for a ref pointing into another Session — the doomed address this
1063
+ * split exists to prevent, with the polarity inverted.
1064
+ */
1065
+ function addressOrLocation(material, activeSessionId) {
1066
+ const { resource, sourceSessionId, materialId, ...named } = material;
1067
+ if (resource && sourceSessionId !== undefined && sourceSessionId === activeSessionId) {
1068
+ return { ...named, resource };
1069
+ }
1070
+ // A location is a thing to ask for, so it is only worth carrying when
1071
+ // asking could succeed. A PDF is refused wherever it is stored.
1072
+ return {
1073
+ ...named,
1074
+ ...(sourceSessionId && materialId && named.kind !== 'pdf'
1075
+ ? { sourceSessionId, materialId }
1076
+ : {}),
894
1077
  };
895
1078
  }
896
1079
  function passageRole(message) {
@@ -197,6 +197,14 @@ export function classifyGeneralizedError(error) {
197
197
  return 'timeout';
198
198
  if (lower.includes('429') || lower.includes('rate'))
199
199
  return 'rate_limited';
200
+ // builder-util-runtime appends generic authentication-token advice to HTTP
201
+ // 404 errors. electron-updater has already classified this particular case
202
+ // as a missing channel artifact, so it is not evidence of bad credentials.
203
+ if (typeof error === 'object' &&
204
+ error !== null &&
205
+ 'code' in error &&
206
+ error.code === 'ERR_UPDATER_CHANNEL_FILE_NOT_FOUND')
207
+ return undefined;
200
208
  if (lower.includes('401') || lower.includes('403') || isAuthenticationErrorText(lower))
201
209
  return 'auth_failed';
202
210
  if (/\b5\d\d\b/.test(lower))
@@ -0,0 +1,149 @@
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
+ import { redactSecrets } from './redaction.js';
20
+ import { userFacingText } from './session.js';
21
+ const utf8Encoder = new TextEncoder();
22
+ /** Default per-reference budget. It is deliberately small enough to leave room for the active task. */
23
+ export const SESSION_SNAPSHOT_DEFAULT_MAX_CHARS = 12_000;
24
+ export const SESSION_SNAPSHOT_MAX_CHARS = 32_000;
25
+ export const SESSION_SNAPSHOT_MAX_BYTES = 32_000;
26
+ export const SESSION_SNAPSHOT_MAX_ITEMS = 24;
27
+ /**
28
+ * Build the model-safe portion of a Session transcript.
29
+ *
30
+ * Only user and assistant text is shareable. Tool calls/results, permission
31
+ * events, system notes, and other Runtime records remain outside this
32
+ * projection. Items are selected from the tail, then restored to transcript
33
+ * order so a large history cannot crowd the current request unexpectedly.
34
+ */
35
+ export function createSessionSnapshot(messages, options) {
36
+ const maxChars = clampPositiveInteger(options.maxChars ?? SESSION_SNAPSHOT_DEFAULT_MAX_CHARS, 1, SESSION_SNAPSHOT_MAX_CHARS);
37
+ const maxItems = clampPositiveInteger(options.maxItems ?? SESSION_SNAPSHOT_MAX_ITEMS, 1, SESSION_SNAPSHOT_MAX_ITEMS);
38
+ const candidates = messages.flatMap((message) => {
39
+ if (message.type !== 'user' && message.type !== 'assistant')
40
+ return [];
41
+ const text = message.type === 'user' ? userFacingText(message) : message.text;
42
+ const normalized = redactSecrets(text).trim();
43
+ if (!normalized)
44
+ return [];
45
+ return [
46
+ {
47
+ role: message.type,
48
+ text: normalized,
49
+ turnId: message.turnId,
50
+ ts: message.ts,
51
+ },
52
+ ];
53
+ });
54
+ const selected = [];
55
+ let usedChars = 0;
56
+ let usedBytes = 0;
57
+ let truncated = false;
58
+ for (let index = candidates.length - 1; index >= 0 && selected.length < maxItems; index -= 1) {
59
+ const candidate = candidates[index];
60
+ const line = formatSnapshotItem(candidate);
61
+ const separator = selected.length > 0 ? 2 : 0;
62
+ const available = maxChars - usedChars - separator;
63
+ const availableBytes = SESSION_SNAPSHOT_MAX_BYTES - usedBytes - separator;
64
+ if (available <= 0 || availableBytes <= 0) {
65
+ truncated = true;
66
+ break;
67
+ }
68
+ const lineBytes = utf8Encoder.encode(line).byteLength;
69
+ if (line.length <= available && lineBytes <= availableBytes) {
70
+ selected.push(candidate);
71
+ usedChars += separator + line.length;
72
+ usedBytes += separator + lineBytes;
73
+ continue;
74
+ }
75
+ if (selected.length === 0) {
76
+ const prefix = formatSnapshotItem({ ...candidate, text: '' });
77
+ const contentBudget = Math.max(0, available - prefix.length);
78
+ const byteBudget = Math.max(0, availableBytes - utf8Encoder.encode(prefix).byteLength);
79
+ if (contentBudget > 0 && byteBudget > 0) {
80
+ const text = sliceAtUtf8Boundary(sliceAtCodePointBoundary(candidate.text, contentBudget), byteBudget).trimEnd();
81
+ if (!text) {
82
+ truncated = true;
83
+ break;
84
+ }
85
+ selected.push({
86
+ ...candidate,
87
+ text,
88
+ });
89
+ usedChars = maxChars;
90
+ }
91
+ }
92
+ truncated = true;
93
+ break;
94
+ }
95
+ if (selected.length < candidates.length)
96
+ truncated = true;
97
+ selected.reverse();
98
+ const text = sliceAtCodePointBoundary(selected.map(formatSnapshotItem).join('\n\n'), maxChars);
99
+ return {
100
+ reference: {
101
+ sessionId: options.sessionId,
102
+ sessionName: redactSecrets(options.sessionName),
103
+ capturedAt: options.capturedAt ?? Date.now(),
104
+ },
105
+ items: selected,
106
+ text,
107
+ estimatedTokens: Math.ceil(text.length / 4),
108
+ maxChars,
109
+ truncated,
110
+ };
111
+ }
112
+ /** Convert a snapshot into the existing inline quote transport. */
113
+ export function sessionSnapshotToQuote(snapshot) {
114
+ const sessionName = redactSecrets(snapshot.reference.sessionName);
115
+ return {
116
+ text: snapshot.text,
117
+ label: sliceAtCodePointBoundary(`Session: ${sessionName}`, 200),
118
+ sourceSessionId: snapshot.reference.sessionId,
119
+ sourceSessionName: sliceAtCodePointBoundary(sessionName, 200),
120
+ sourceCapturedAt: snapshot.reference.capturedAt,
121
+ sourceTruncated: snapshot.truncated,
122
+ };
123
+ }
124
+ function formatSnapshotItem(item) {
125
+ return `${item.role === 'user' ? 'User' : 'Assistant'}: ${item.text}`;
126
+ }
127
+ /** Keep a UTF-16 slice from ending between the halves of a surrogate pair. */
128
+ function sliceAtCodePointBoundary(value, maxCodeUnits) {
129
+ const sliced = value.slice(0, maxCodeUnits);
130
+ const last = sliced.charCodeAt(sliced.length - 1);
131
+ return last >= 0xd800 && last <= 0xdbff ? sliced.slice(0, -1) : sliced;
132
+ }
133
+ function sliceAtUtf8Boundary(value, maxBytes) {
134
+ let bytes = 0;
135
+ let end = 0;
136
+ for (const codePoint of value) {
137
+ const nextBytes = utf8Encoder.encode(codePoint).byteLength;
138
+ if (bytes + nextBytes > maxBytes)
139
+ break;
140
+ bytes += nextBytes;
141
+ end += codePoint.length;
142
+ }
143
+ return value.slice(0, end);
144
+ }
145
+ function clampPositiveInteger(value, min, max) {
146
+ if (!Number.isSafeInteger(value))
147
+ return min;
148
+ return Math.max(min, Math.min(max, value));
149
+ }
@@ -29,6 +29,7 @@
29
29
  "./provider-retry-countdown": "./dist/provider-retry-countdown.js",
30
30
  "./interaction": "./dist/interaction.js",
31
31
  "./session": "./dist/session.js",
32
+ "./session-reference": "./dist/session-reference.js",
32
33
  "./session-revisions": "./dist/session-revisions.js",
33
34
  "./collaboration": "./dist/collaboration.js",
34
35
  "./orchestration": "./dist/orchestration.js",
@@ -66,6 +67,7 @@
66
67
  "./capabilities": "./dist/capabilities.js",
67
68
  "./capability-audit": "./dist/capability-audit.js",
68
69
  "./client-capability-grant": "./dist/client-capability-grant.js",
70
+ "./client-plugin-bridge": "./dist/client-plugin-bridge.js",
69
71
  "./health": "./dist/health.js",
70
72
  "./search": "./dist/search.js",
71
73
  "./bot-events": "./dist/bot-events.js",
@@ -654,11 +654,13 @@ function buildFamilyWire(connection, modelId, level, thinkingOptions, requestedL
654
654
  : {},
655
655
  };
656
656
  case 'commandcode-cli':
657
- // The CLI wire takes `reasoning_effort` as the CLI's own effort words;
658
- // `max` has no counterpart there and rounds down to `high`.
657
+ // The CLI wire takes `reasoning_effort` as the CLI's own effort words.
658
+ // `max` is one of them (claude-fable-5-1, moonshotai/Kimi-K3, …), so the
659
+ // chosen level is forwarded verbatim — rounding it down would silently
660
+ // send a weaker request than the user asked for.
659
661
  return level === undefined || level === 'off'
660
662
  ? {}
661
- : { 'commandcode-cli': { reasoningEffort: level === 'max' ? 'high' : level } };
663
+ : { 'commandcode-cli': { reasoningEffort: level } };
662
664
  default:
663
665
  return {};
664
666
  }
@@ -799,8 +799,19 @@ function formatAttachmentRefs(attachments) {
799
799
  function formatQuoteRefs(quotes) {
800
800
  return quotes
801
801
  .map((q) => {
802
- const label = q.label === undefined ? '' : ` label="${q.label.replace(/"/g, "'")}"`;
803
- return `<quoted_excerpt${label}>\n${q.text}\n</quoted_excerpt>`;
802
+ const attributes = [
803
+ q.label === undefined ? undefined : `label="${quoteAttribute(q.label)}"`,
804
+ q.sourceSessionId === undefined
805
+ ? undefined
806
+ : `source_session="${quoteAttribute(q.sourceSessionId)}"`,
807
+ q.sourceCapturedAt === undefined ? undefined : `captured_at="${q.sourceCapturedAt}"`,
808
+ q.sourceTruncated === undefined ? undefined : `truncated="${q.sourceTruncated}"`,
809
+ ].filter((attribute) => attribute !== undefined);
810
+ const opening = attributes.length > 0 ? `<quoted_excerpt ${attributes.join(' ')}>` : '<quoted_excerpt>';
811
+ return `${opening}\n${q.text}\n</quoted_excerpt>`;
804
812
  })
805
813
  .join('\n');
806
814
  }
815
+ function quoteAttribute(value) {
816
+ return value.replace(/["<&>]/g, (character) => character === '"' ? "'" : `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`);
817
+ }