maka-agent 0.2.1-dev.89.20261016 → 0.2.1-dev.91.20261018

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 (52) hide show
  1. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  2. package/node_modules/@maka/core/dist/event-wait.js +98 -2
  3. package/node_modules/@maka/core/dist/runtime-event.js +3 -0
  4. package/node_modules/@maka/core/dist/session.js +2 -1
  5. package/node_modules/@maka/core/dist/turn-origin.js +14 -0
  6. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +22 -0
  7. package/node_modules/@maka/runtime/dist/plugin-agent-service.js +7 -1
  8. package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +14 -7
  9. package/node_modules/@maka/runtime/dist/plugin-executor-service.js +6 -0
  10. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +1 -0
  11. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +1 -0
  12. package/node_modules/@maka/runtime-host/dist/assistant/bot/avatar.js +89 -0
  13. package/node_modules/@maka/runtime-host/dist/assistant/bot/bridge.js +52 -0
  14. package/node_modules/@maka/runtime-host/dist/assistant/bot/capabilities.js +52 -0
  15. package/node_modules/@maka/runtime-host/dist/assistant/bot/controller.js +300 -0
  16. package/node_modules/@maka/runtime-host/dist/assistant/bot/conversation.js +116 -0
  17. package/node_modules/@maka/runtime-host/dist/assistant/bot/host.js +133 -0
  18. package/node_modules/@maka/runtime-host/dist/assistant/bot/store.js +223 -0
  19. package/node_modules/@maka/runtime-host/dist/assistant/index-initiative/assistant.js +3 -1
  20. package/node_modules/@maka/runtime-host/dist/assistant/index-initiative/controller.js +61 -4
  21. package/node_modules/@maka/runtime-host/dist/assistant/index-initiative/host.js +88 -14
  22. package/node_modules/@maka/runtime-host/dist/assistant/index-initiative/prompt.js +1 -2
  23. package/node_modules/@maka/runtime-host/dist/assistant/index-initiative/store.js +4 -3
  24. package/node_modules/@maka/runtime-host/dist/assistant/memory-network/controller.js +185 -29
  25. package/node_modules/@maka/runtime-host/dist/assistant/memory-network/host.js +18 -11
  26. package/node_modules/@maka/runtime-host/dist/assistant/proactive-matters/clarification.js +124 -0
  27. package/node_modules/@maka/runtime-host/dist/assistant/proactive-matters/controller.js +23 -2
  28. package/node_modules/@maka/runtime-host/dist/assistant/proactive-matters/delegation.js +29 -11
  29. package/node_modules/@maka/runtime-host/dist/assistant/proactive-matters/delivery.js +11 -0
  30. package/node_modules/@maka/runtime-host/dist/assistant/proactive-matters/host.js +2 -7
  31. package/node_modules/@maka/runtime-host/dist/assistant/proactive-matters/prompt.js +1 -1
  32. package/node_modules/@maka/runtime-host/dist/assistant/proactive-matters/store.js +5 -3
  33. package/node_modules/@maka/runtime-host/dist/assistant/service.js +23 -0
  34. package/node_modules/@maka/runtime-host/dist/protocol/index.js +7 -1
  35. package/node_modules/@maka/runtime-host/dist/protocol/message.js +20 -1
  36. package/node_modules/@maka/runtime-host/dist/protocol/personal-assistant.js +33 -0
  37. package/node_modules/@maka/runtime-host/dist/protocol/turn.js +3 -1
  38. package/node_modules/@maka/runtime-host/dist/server/avatar-image-source.js +66 -0
  39. package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +0 -19
  40. package/node_modules/@maka/runtime-host/dist/server/event-wait-adapter.js +47 -0
  41. package/node_modules/@maka/runtime-host/dist/server/event-wait-manager.js +1186 -0
  42. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +156 -36
  43. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +3 -2
  44. package/node_modules/@maka/storage/dist/agent-run-store-contract.js +3 -1
  45. package/node_modules/@maka/storage/dist/cursor-session-adapter.js +604 -0
  46. package/node_modules/@maka/storage/dist/event-wait-authority.js +132 -2
  47. package/node_modules/@maka/storage/dist/execution-stores.js +19 -1
  48. package/node_modules/@maka/storage/dist/external-session-adapters.js +2 -0
  49. package/node_modules/@maka/storage/dist/sqlite-event-wait-authority.js +116 -1
  50. package/node_modules/@maka/storage/dist/sqlite-workflow-schema.js +16 -1
  51. package/node_modules/@maka/storage/package.json +1 -0
  52. package/package.json +1 -1
@@ -29,7 +29,12 @@ export const EVENT_WAIT_LIMITS = Object.freeze({
29
29
  evidenceRefs: 8,
30
30
  evidenceRefBytes: 1024,
31
31
  reasonBytes: 1024,
32
- recordBytes: 32768,
32
+ cursorBytes: 2048,
33
+ observationAttempts: 1_000_000,
34
+ // Every combination of bounded fields, including observation and a cancelled
35
+ // priorResolution, fits below 42 KiB even with JSON escaping. Leave room for
36
+ // the full lifecycle so an accepted result can always retain its facts on cancel.
37
+ recordBytes: 48 * 1024,
33
38
  });
34
39
  const encoder = new TextEncoder();
35
40
  const invalidIdCharacterPattern = /[^A-Za-z0-9_-]/u;
@@ -187,7 +192,7 @@ export function decodeEventWaitRecord(value) {
187
192
  'deadlineAt',
188
193
  'status',
189
194
  ...lifecycleKeys,
190
- ], root.status === 'cancelled' ? ['priorResolution'] : []);
195
+ ], root.status === 'cancelled' ? ['priorResolution', 'observation'] : ['observation']);
191
196
  if (r.schemaVersion !== EVENT_WAIT_SCHEMA_VERSION)
192
197
  invalid('schemaVersion');
193
198
  const lease = shape(r.goalControlLease, ['goalId', 'generation']);
@@ -230,6 +235,62 @@ export function decodeEventWaitRecord(value) {
230
235
  invalid('lifecycle time');
231
236
  return result;
232
237
  };
238
+ if (Object.hasOwn(r, 'observation')) {
239
+ const x = shape(r.observation, [
240
+ 'schemaVersion',
241
+ 'attempts',
242
+ 'lastAttemptAt',
243
+ 'lastObservedAt',
244
+ 'nextObserveAt',
245
+ 'nextPollAt',
246
+ 'consecutiveErrors',
247
+ 'outcome',
248
+ 'cursor',
249
+ 'eventKey',
250
+ 'reason',
251
+ ]);
252
+ const attempts = integer(x.attempts, 'observation attempts', 1);
253
+ const consecutiveErrors = integer(x.consecutiveErrors, 'consecutive errors');
254
+ const lastAttemptAt = time(x.lastAttemptAt);
255
+ const nextObserveAt = integer(x.nextObserveAt, 'next observation');
256
+ const nextPollAt = integer(x.nextPollAt, 'next poll');
257
+ const lastObservedAt = x.lastObservedAt === null ? null : time(x.lastObservedAt);
258
+ if (x.schemaVersion !== 1 ||
259
+ attempts > EVENT_WAIT_LIMITS.observationAttempts ||
260
+ consecutiveErrors > attempts ||
261
+ nextObserveAt < lastAttemptAt ||
262
+ nextPollAt < nextObserveAt ||
263
+ (x.outcome !== 'checking' && (lastObservedAt === null || lastObservedAt < lastAttemptAt)) ||
264
+ typeof x.outcome !== 'string' ||
265
+ ![
266
+ 'checking',
267
+ 'pending',
268
+ 'unknown',
269
+ 'satisfied',
270
+ 'invalidated',
271
+ 'source_unavailable',
272
+ ].includes(x.outcome))
273
+ invalid('observation');
274
+ Object.assign(base, {
275
+ observation: {
276
+ schemaVersion: 1,
277
+ attempts,
278
+ lastAttemptAt,
279
+ lastObservedAt,
280
+ nextObserveAt,
281
+ nextPollAt,
282
+ consecutiveErrors,
283
+ outcome: x.outcome,
284
+ cursor: x.cursor === null ? null : text(x.cursor, 'cursor', EVENT_WAIT_LIMITS.cursorBytes),
285
+ eventKey: x.eventKey === null
286
+ ? null
287
+ : text(x.eventKey, 'event key', EVENT_WAIT_LIMITS.receiptKeyBytes),
288
+ reason: x.reason === null
289
+ ? null
290
+ : text(x.reason, 'observation reason', EVENT_WAIT_LIMITS.reasonBytes),
291
+ },
292
+ });
293
+ }
233
294
  const resolution = (v, resolvedAt) => {
234
295
  const x = shape(v, ['outcome', 'receiptKey', 'observedAt', 'evidenceRefs']);
235
296
  if (x.outcome !== 'satisfied' &&
@@ -306,7 +367,41 @@ export function assertEventWaitTransition(previous, next) {
306
367
  }
307
368
  if (b.updatedAt < a.updatedAt)
308
369
  invalid('time regression');
370
+ if (a.observation) {
371
+ if (!b.observation ||
372
+ b.observation.attempts < a.observation.attempts ||
373
+ b.observation.attempts > a.observation.attempts + 1 ||
374
+ b.observation.lastAttemptAt < a.observation.lastAttemptAt ||
375
+ (a.observation.lastObservedAt !== null &&
376
+ (b.observation.lastObservedAt === null ||
377
+ b.observation.lastObservedAt < a.observation.lastObservedAt)))
378
+ invalid('observation regression');
379
+ if (b.observation.attempts === a.observation.attempts &&
380
+ b.observation.lastAttemptAt !== a.observation.lastAttemptAt)
381
+ invalid('attempt time without reservation');
382
+ if (b.observation.attempts > a.observation.attempts &&
383
+ (b.status !== 'waiting' ||
384
+ b.observation.outcome !== 'checking' ||
385
+ b.observation.lastObservedAt !== a.observation.lastObservedAt ||
386
+ b.observation.cursor !== a.observation.cursor ||
387
+ b.observation.eventKey !== a.observation.eventKey ||
388
+ b.observation.consecutiveErrors !== a.observation.consecutiveErrors))
389
+ invalid('attempt reservation');
390
+ }
391
+ else if (b.observation && b.observation.attempts !== 1)
392
+ invalid('initial observation budget');
309
393
  if (a.status === 'waiting') {
394
+ if (b.status === 'waiting' && b.observation && !same(a.observation, b.observation)) {
395
+ if (b.observation.attempts > (a.observation?.attempts ?? 0) &&
396
+ b.observation.outcome === 'checking')
397
+ return;
398
+ if (a.observation?.outcome === 'checking' &&
399
+ (b.observation.outcome === 'pending' || b.observation.outcome === 'unknown'))
400
+ return;
401
+ invalid('observation update without reservation');
402
+ }
403
+ if (b.status === 'resolved' && b.resolvedAt >= b.deadlineAt)
404
+ invalid('resolution after deadline');
310
405
  if (b.status === 'resolved' ||
311
406
  b.status === 'expired' ||
312
407
  (b.status === 'cancelled' && !b.priorResolution))
@@ -314,6 +409,7 @@ export function assertEventWaitTransition(previous, next) {
314
409
  }
315
410
  else if (a.status === 'resolved' &&
316
411
  b.status === 'cancelled' &&
412
+ same(a.observation, b.observation) &&
317
413
  same(b.priorResolution, { resolvedAt: a.resolvedAt, resolution: a.resolution }))
318
414
  return;
319
415
  invalid('state transition');
@@ -173,6 +173,7 @@ export function runtimeEventEnvelopeValueDomains() {
173
173
  }
174
174
  const RUNTIME_EVENT_SHAPE = defineObjectShape()(['id', 'invocationId', 'runId', 'sessionId', 'turnId', 'ts', 'partial', 'role', 'author'], ['branch', 'origin', 'modelVisibility', 'status', 'content', 'actions', 'refs']);
175
175
  const TEXT_CONTENT_SHAPE = defineObjectShape()(['kind', 'text'], [
176
+ 'summary',
176
177
  'displayText',
177
178
  'interrupted',
178
179
  'origin',
@@ -377,6 +378,7 @@ export function decodeRuntimeEvent(value) {
377
378
  content: {
378
379
  kind: 'text',
379
380
  ...normalizeMessageContent(value.content),
381
+ ...(value.content.summary !== undefined ? { summary: value.content.summary } : {}),
380
382
  ...(value.content.origin !== undefined
381
383
  ? { origin: decodeTurnOrigin(value.content.origin) }
382
384
  : {}),
@@ -403,6 +405,7 @@ function isRuntimeEventContent(value) {
403
405
  switch (value.kind) {
404
406
  case 'text':
405
407
  if (!hasExactShape(value, TEXT_CONTENT_SHAPE) ||
408
+ (value.summary !== undefined && typeof value.summary !== 'string') ||
406
409
  (value.origin !== undefined && !isTurnOrigin(value.origin)) ||
407
410
  (value.steering !== undefined && value.steering !== true) ||
408
411
  (value.interrupted !== undefined && value.interrupted !== true) ||
@@ -393,7 +393,7 @@ const USER_MESSAGE_SHAPE = defineObjectShape()(['type', 'id', 'turnId', 'ts', 't
393
393
  'coordinationActionId',
394
394
  'origin',
395
395
  ]);
396
- const ASSISTANT_MESSAGE_SHAPE = defineObjectShape()(['type', 'id', 'turnId', 'ts', 'text', 'modelId'], ['thinking', 'contentOrder', 'providerOptions', 'interrupted']);
396
+ const ASSISTANT_MESSAGE_SHAPE = defineObjectShape()(['type', 'id', 'turnId', 'ts', 'text', 'modelId'], ['thinking', 'contentOrder', 'providerOptions', 'interrupted', 'summary']);
397
397
  const TOOL_CALL_MESSAGE_SHAPE = defineObjectShape()(['type', 'id', 'turnId', 'ts', 'toolName', 'args'], [
398
398
  'activityKind',
399
399
  'displayName',
@@ -610,6 +610,7 @@ function decodeMessage(value, decodeToolResultContent) {
610
610
  hasMessageEnvelope(message, true) &&
611
611
  typeof message.text === 'string' &&
612
612
  typeof message.modelId === 'string' &&
613
+ (message.summary === undefined || typeof message.summary === 'string') &&
613
614
  (message.interrupted === undefined || message.interrupted === true) &&
614
615
  (message.providerOptions === undefined || isRecord(message.providerOptions)) &&
615
616
  (message.thinking === undefined || isAssistantThinking(message.thinking)) &&
@@ -17,6 +17,7 @@
17
17
  * under the License.
18
18
  */
19
19
  import { defineObjectShape, hasExactShape, isRecord } from './record-schema.js';
20
+ const EXTERNAL_CONNECTOR_ORIGIN_SHAPE = defineObjectShape()(['kind', 'connectorId'], ['senderId']);
20
21
  const SCHEDULED_TASK_ORIGIN_SHAPE = defineObjectShape()(['kind', 'scheduledTaskId'], []);
21
22
  const CLOUD_ACTIVATION_ORIGIN_SHAPE = defineObjectShape()(['kind', 'activationId'], []);
22
23
  const LEGACY_AUTOMATION_ORIGIN_SHAPE = defineObjectShape()(['kind', 'automationId'], []);
@@ -26,6 +27,19 @@ const AGENT_GRAPH_ORIGIN_SHAPE = defineObjectShape()(['kind', 'graphId', 'wakeId
26
27
  export function decodeTurnOrigin(value) {
27
28
  if (!isRecord(value))
28
29
  return undefined;
30
+ if (value.kind === 'external_connector') {
31
+ if (!hasExactShape(value, EXTERNAL_CONNECTOR_ORIGIN_SHAPE) ||
32
+ typeof value.connectorId !== 'string' ||
33
+ !value.connectorId.length ||
34
+ (value.senderId !== undefined &&
35
+ (typeof value.senderId !== 'string' || !value.senderId.length)))
36
+ return undefined;
37
+ return {
38
+ kind: 'external_connector',
39
+ connectorId: value.connectorId,
40
+ ...(value.senderId !== undefined ? { senderId: value.senderId } : {}),
41
+ };
42
+ }
29
43
  if (value.kind === 'workhub_result') {
30
44
  const keys = ['kind', 'eventId', 'actionId', 'delegationId', 'targetSessionId', 'targetTurnId'];
31
45
  if (Object.keys(value).length !== keys.length ||
@@ -1748,6 +1748,7 @@ export class AiSdkTurn {
1748
1748
  throw providerOutcome.failure;
1749
1749
  finishReason = providerOutcome.finishReason;
1750
1750
  await queue.waitUntilConsumedThroughCurrent();
1751
+ let toolRequestedFinish = false;
1751
1752
  if (returnedToolCalls.length > 0) {
1752
1753
  const continuationBudgetRemains = maxSteps === undefined || runtimeSteps < maxSteps;
1753
1754
  if (continuationBudgetRemains && !this.deps.backend.loadTurnRuntimeEvents) {
@@ -1819,6 +1820,12 @@ export class AiSdkTurn {
1819
1820
  throw outcome.reason;
1820
1821
  const settlement = outcome.value;
1821
1822
  const toolCall = returnedToolCalls[index];
1823
+ const definition = toolCall && toolsByName.get(toolCall.toolName);
1824
+ if (returnedToolCalls.length === 1 &&
1825
+ definition?.executionSemantics === 'exclusive_step' &&
1826
+ definition.finishTurnOnSuccess &&
1827
+ !settlement.providerError)
1828
+ toolRequestedFinish = true;
1822
1829
  if (isPlanToolResult(settlement.result)) {
1823
1830
  this.handlePlanToolResult(settlement.result, queue);
1824
1831
  }
@@ -1851,6 +1858,21 @@ export class AiSdkTurn {
1851
1858
  ...(providerStepUsage ? { usage: providerStepUsage } : {}),
1852
1859
  });
1853
1860
  lastCompletedStepHadToolResult = returnedToolCalls.length > 0;
1861
+ // Finish only after the tool result is durably consumed. The Host can
1862
+ // veto completion (for example, new Inbox input raced with Sleep).
1863
+ if (toolRequestedFinish && !this.aborted && !this.loopStopRequested) {
1864
+ const decision = await this.deps.backend.beforeTurnFinish?.({
1865
+ sessionId: this.deps.backend.sessionId,
1866
+ turnId,
1867
+ signal: turnAbortController.signal,
1868
+ });
1869
+ if (!decision || decision.allow) {
1870
+ finishReason = 'stop';
1871
+ this.deps.modelAdapter.clearContinuation(this.turnId);
1872
+ break agentLoop;
1873
+ }
1874
+ finishFeedback = decision.feedback;
1875
+ }
1854
1876
  const stepLimitReached = maxSteps !== undefined && runtimeSteps >= maxSteps;
1855
1877
  if (sandboxBoundaryFinalizationStep ||
1856
1878
  (stepLimitReached &&
@@ -100,7 +100,13 @@ export class PluginAgentService extends Service {
100
100
  return Object.freeze({
101
101
  ...descriptor,
102
102
  id,
103
- followup: (message) => service.runtime().followup(id, message, invocation),
103
+ followup: (message, options) => service.runtime().followup(id, message, invocation, options),
104
+ messageExecution: async (messageId) => {
105
+ const runtime = service.runtime();
106
+ if (!runtime.messageExecution)
107
+ throw new Error('Runtime message execution query unavailable');
108
+ return runtime.messageExecution(id, messageId, invocation);
109
+ },
104
110
  steer: (message) => service.runtime().steer(id, message, invocation),
105
111
  inject: (message) => service.runtime().inject(id, message, invocation),
106
112
  cancel: () => service.runtime().cancel(id, invocation),
@@ -59,8 +59,8 @@ export class PluginExecutorBackend {
59
59
  yield event;
60
60
  queue.ackConsumed();
61
61
  }
62
- const returnedResult = await producer;
63
- if (returnedResult) {
62
+ const settlement = await producer;
63
+ if (settlement === 'published') {
64
64
  // The Runtime Kernel requests the next item only after onSessionEvent
65
65
  // resolves. Reaching this point means its terminal event was accepted.
66
66
  // The Plugin decides whether its external execution actually settled;
@@ -78,8 +78,11 @@ export class PluginExecutorBackend {
78
78
  finally {
79
79
  queue.noteConsumerDetached();
80
80
  abort.abort(new Error('Plugin executor event consumer detached'));
81
- const returnedResult = await producer.catch(() => false);
82
- if (returnedResult &&
81
+ // A settled result that was not acknowledged is abandoned, including one
82
+ // the Runtime rejected before publishing it. A provider failure before
83
+ // settlement is neither acknowledged nor abandoned.
84
+ const settlement = await producer.catch(() => 'unsettled');
85
+ if (settlement !== 'unsettled' &&
83
86
  this.#binding.acknowledgeExecution &&
84
87
  !acknowledged &&
85
88
  this.#binding.abandonExecution)
@@ -113,6 +116,7 @@ export class PluginExecutorBackend {
113
116
  let result;
114
117
  let failure;
115
118
  let failed = false;
119
+ let settled = false;
116
120
  try {
117
121
  result = await this.#binding.execute({
118
122
  sessionId: this.sessionId,
@@ -130,6 +134,9 @@ export class PluginExecutorBackend {
130
134
  ...(input.quotes ? { quotes: input.quotes } : {}),
131
135
  }, {
132
136
  signal,
137
+ onSettled: () => {
138
+ settled = true;
139
+ },
133
140
  onEvent: (event) => {
134
141
  if (event.type === 'thinking_delta')
135
142
  thinkingText += event.text;
@@ -148,14 +155,14 @@ export class PluginExecutorBackend {
148
155
  this.#publishCancellation(turnId, { status: 'cancelled', source: 'caller' }, queue);
149
156
  else
150
157
  this.#publishFailure(turnId, failure instanceof Error ? failure.message : 'External executor failed', undefined, false, queue);
151
- return false;
158
+ return settled ? 'rejected' : 'unsettled';
152
159
  }
153
160
  if (result === undefined) {
154
161
  this.#publishFailure(turnId, 'External executor returned no terminal result', undefined, false, queue);
155
- return false;
162
+ return settled ? 'rejected' : 'unsettled';
156
163
  }
157
164
  this.#publishResult(turnId, messageId, result, queue);
158
- return true;
165
+ return 'published';
159
166
  }
160
167
  async #requestPermission(input, request, signal, toolUseIds, queue) {
161
168
  const hosted = input.hostedInteraction;
@@ -280,6 +280,12 @@ export class PluginExecutorService extends Service {
280
280
  return normalizePermissionResult(result, normalized);
281
281
  },
282
282
  });
283
+ try {
284
+ options.onSettled?.();
285
+ }
286
+ catch {
287
+ // A settlement observer must not change external execution.
288
+ }
283
289
  if (signal.aborted) {
284
290
  const cancelled = cancelledResult(signal.reason);
285
291
  const normalized = normalizeResult(result);
@@ -90,6 +90,7 @@ export function backfillRuntimeEventsFromStoredMessages(input) {
90
90
  content: {
91
91
  kind: 'text',
92
92
  text: message.text,
93
+ ...(message.summary !== undefined ? { summary: message.summary } : {}),
93
94
  ...(message.interrupted ? { interrupted: true } : {}),
94
95
  },
95
96
  actions: { stateDelta: recoveryState(now, message) },
@@ -659,6 +659,7 @@ function projectText(event, state, messages) {
659
659
  turnId: event.turnId,
660
660
  ts: event.ts,
661
661
  text: event.content.text,
662
+ ...(event.content.summary !== undefined ? { summary: event.content.summary } : {}),
662
663
  ...(event.content.interrupted ? { interrupted: true } : {}),
663
664
  ...(event.content.providerOptions !== undefined
664
665
  ? { providerOptions: structuredClone(event.content.providerOptions) }
@@ -0,0 +1,89 @@
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 { createHash } from 'node:crypto';
20
+ import { z } from 'zod';
21
+ import { validateImageBytes } from '@maka/runtime/image-file';
22
+ import { assistantAvatarSchema, assistantEmojiSchema, DEFAULT_ASSISTANT_AVATAR, } from '../../protocol/personal-assistant.js';
23
+ export function initializeAvatar(ctx, store, bot, register) {
24
+ const current = () => store.get('avatar');
25
+ const avatar = () => {
26
+ const { imageData: _, ...metadata } = current() ?? {};
27
+ return assistantAvatarSchema.safeParse(metadata).data ?? DEFAULT_ASSISTANT_AVATAR;
28
+ };
29
+ ctx.api.register('assistant.avatar', () => ({ avatar: avatar() }));
30
+ // Bounded transfers keep large images out of the normal status polling and model transcript.
31
+ ctx.api.register('assistant.avatar-image', (raw) => {
32
+ const { id, offset } = z
33
+ .object({ id: z.string(), offset: z.number().int().nonnegative().default(0) })
34
+ .parse(raw);
35
+ const saved = current();
36
+ if (avatar().image?.id !== id || !saved?.imageData)
37
+ throw Error('Avatar changed; refresh its metadata');
38
+ if (offset >= saved.imageData.length)
39
+ throw Error('Invalid avatar offset');
40
+ const data = saved.imageData.slice(offset, offset + 192 * 1024);
41
+ return {
42
+ id,
43
+ offset,
44
+ data,
45
+ nextOffset: offset + data.length < saved.imageData.length ? offset + data.length : null,
46
+ };
47
+ });
48
+ register('AssistantSetAvatar', '更换你自己的头像,入口和聊天消息同步显示,重启后保留。image 填生成或找到的图片的当前工具会话工作目录内的本地路径或公网 HTTP(S) 图片网址(PNG/JPEG/WebP/GIF,最多 2 MiB),不要传 base64。也可用 emoji 填一个 emoji;两者选一。background 可填 #RRGGBB。图片会保存副本;此工具只设置头像,生成或找图使用已有工具,必要时委派任务。不会发送消息。', z
49
+ .object({
50
+ emoji: assistantEmojiSchema.optional(),
51
+ image: z.string().trim().min(1).max(8192).optional(),
52
+ background: z
53
+ .string()
54
+ .regex(/^#[0-9a-fA-F]{6}$/)
55
+ .default('#E5EDFF'),
56
+ })
57
+ .strict()
58
+ .refine((value) => Boolean(value.emoji) !== Boolean(value.image), 'Provide exactly one of emoji or image'), async (input, call) => {
59
+ let saved;
60
+ if (input.image) {
61
+ if (!ctx.readAvatarImage)
62
+ throw Error('Image loading is unavailable on this Host');
63
+ const worker = bot.worker();
64
+ const owner = bot.owner();
65
+ const result = await ctx.readAvatarImage(call.sessionId, input.image, call.abortSignal);
66
+ call.abortSignal.throwIfAborted();
67
+ bot.ready();
68
+ if (bot.worker() !== worker || bot.owner() !== owner)
69
+ throw Error('Assistant binding changed');
70
+ const image = validateImageBytes(result.bytes, 'chat');
71
+ const bytes = Buffer.from(image.bytes);
72
+ const imageData = bytes.toString('base64');
73
+ saved = {
74
+ background: input.background,
75
+ image: {
76
+ id: createHash('sha256').update(bytes).digest('hex'),
77
+ mimeType: image.mimeType,
78
+ length: imageData.length,
79
+ },
80
+ imageData,
81
+ };
82
+ }
83
+ else
84
+ saved = { emoji: input.emoji, background: input.background };
85
+ // One SQLite value makes image content and metadata an atomic replacement.
86
+ store.set('avatar', saved);
87
+ return { updated: true, avatar: avatar() };
88
+ });
89
+ }
@@ -0,0 +1,52 @@
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
+ /** In-process routing only; durable mailbox and wake state belong to the Bot. */
20
+ export class AssistantBotBridge {
21
+ endpoint;
22
+ register(endpoint) {
23
+ this.endpoint = endpoint;
24
+ }
25
+ status() {
26
+ return this.endpoint?.status() ?? null;
27
+ }
28
+ isConversation(id) {
29
+ return this.endpoint?.owner() === id;
30
+ }
31
+ hasUserMessage(sessionId, id) {
32
+ return this.isConversation(sessionId) && this.endpoint.hasUserMessage(id);
33
+ }
34
+ isWorker(id) {
35
+ return this.endpoint?.worker() === id;
36
+ }
37
+ ownerFor(id) {
38
+ return this.isWorker(id) ? this.endpoint?.owner() : undefined;
39
+ }
40
+ async receive(sessionId, id, content, origin) {
41
+ if (!this.isConversation(sessionId))
42
+ return false;
43
+ await this.endpoint.receive(id, content, origin);
44
+ return true;
45
+ }
46
+ post(owner, input) {
47
+ if (!this.isConversation(owner))
48
+ return false;
49
+ this.endpoint.post(input);
50
+ return true;
51
+ }
52
+ }
@@ -0,0 +1,52 @@
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
+ export const BOT_TOOLS = new Set([
20
+ 'InboxList',
21
+ 'InboxRead',
22
+ 'InboxCheck',
23
+ 'SendUser',
24
+ 'AssistantSetAvatar',
25
+ 'Sleep',
26
+ 'AssistantConversation',
27
+ 'MatterDelegate',
28
+ 'MatterTasks',
29
+ 'MatterTaskMessage',
30
+ 'MatterReply',
31
+ 'MatterTaskControl',
32
+ 'MatterDelegationRecover',
33
+ 'MatterFeedbackRetry',
34
+ 'InitiativeEnable',
35
+ 'InitiativeConfigure',
36
+ 'InitiativeControl',
37
+ 'InitiativeStatus',
38
+ 'MemoryIndexCreate',
39
+ 'MemoryIndexCreateGroup',
40
+ 'MemoryIndexMaintain',
41
+ 'MemoryIndexControl',
42
+ 'MemoryIndexAddSources',
43
+ 'MemoryIndexList',
44
+ 'MemoryIndexRead',
45
+ 'MemoryIndexContent',
46
+ 'MemoryIndexLinks',
47
+ 'MemoryOriginal',
48
+ 'MemoryHistory',
49
+ 'MemorySources',
50
+ 'MemoryRange',
51
+ ]);
52
+ export const BOT_DELEGATION_TOOLS = new Set([...BOT_TOOLS].filter((name) => name.startsWith('Matter') || name.startsWith('Initiative') || name.startsWith('Memory')));