@pikku/core 0.12.137 → 0.12.139

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 (78) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/dev/hot-reload.js +5 -2
  3. package/dist/env.d.ts +18 -0
  4. package/dist/env.js +60 -1
  5. package/dist/function/abort-scope.d.ts +6 -5
  6. package/dist/function/abort-scope.js +6 -17
  7. package/dist/function/function-runner.js +2 -1
  8. package/dist/middleware/addon-scope-cap.d.ts +19 -0
  9. package/dist/middleware/addon-scope-cap.js +34 -0
  10. package/dist/middleware/auth-apikey.js +1 -1
  11. package/dist/middleware/auth-bearer.js +1 -1
  12. package/dist/middleware/auth-cookie.js +1 -1
  13. package/dist/middleware/index.d.ts +1 -0
  14. package/dist/middleware/index.js +1 -0
  15. package/dist/schema.js +3 -1
  16. package/dist/scopes.d.ts +8 -0
  17. package/dist/scopes.js +23 -0
  18. package/dist/services/content-service.d.ts +13 -1
  19. package/dist/services/in-memory-queue-service.js +4 -2
  20. package/dist/services/in-memory-workflow-service.js +7 -6
  21. package/dist/services/index.d.ts +2 -2
  22. package/dist/services/index.js +1 -1
  23. package/dist/services/local-content-request-handler.js +21 -60
  24. package/dist/services/local-content.d.ts +45 -1
  25. package/dist/services/local-content.js +202 -18
  26. package/dist/services/local-variables.d.ts +5 -0
  27. package/dist/services/local-variables.js +7 -2
  28. package/dist/services/queue-webhook-service.js +1 -1
  29. package/dist/services/scoped-content-service.d.ts +35 -0
  30. package/dist/services/scoped-content-service.js +171 -0
  31. package/dist/services/webhook-service.d.ts +2 -2
  32. package/dist/services/webhook-service.js +6 -4
  33. package/dist/types/core.types.d.ts +10 -1
  34. package/dist/types/state.types.d.ts +8 -0
  35. package/dist/utils/hmac.d.ts +25 -10
  36. package/dist/utils/hmac.js +202 -24
  37. package/dist/wirings/addon/addon-reach.d.ts +3 -0
  38. package/dist/wirings/addon/addon-reach.js +21 -0
  39. package/dist/wirings/addon/addon-runner.d.ts +6 -0
  40. package/dist/wirings/addon/addon-runner.js +13 -0
  41. package/dist/wirings/addon/define-screens.d.ts +32 -0
  42. package/dist/wirings/addon/define-screens.js +5 -0
  43. package/dist/wirings/addon/index.d.ts +2 -0
  44. package/dist/wirings/addon/index.js +1 -0
  45. package/dist/wirings/addon/wire-addon.d.ts +27 -0
  46. package/dist/wirings/addon/wire-addon.js +4 -0
  47. package/dist/wirings/agent/agent-agui.js +65 -62
  48. package/dist/wirings/agent/agent-interrupt.d.ts +4 -1
  49. package/dist/wirings/agent/agent-interrupt.js +3 -3
  50. package/dist/wirings/agent/agent-memory.js +5 -6
  51. package/dist/wirings/agent/agent-model-config.js +2 -1
  52. package/dist/wirings/agent/agent-prepare.js +24 -13
  53. package/dist/wirings/agent/agent-stream.js +38 -34
  54. package/dist/wirings/agent/agent.types.d.ts +8 -1
  55. package/dist/wirings/channel/channel-handler.js +1 -1
  56. package/dist/wirings/channel/local/local-channel-handler.js +5 -1
  57. package/dist/wirings/channel/local/local-channel-runner.js +6 -4
  58. package/dist/wirings/channel/local/local-eventhub-service.d.ts +1 -0
  59. package/dist/wirings/channel/local/local-eventhub-service.js +4 -1
  60. package/dist/wirings/cli/channel/cli-channel-runner.js +7 -2
  61. package/dist/wirings/cli/channel/cli-raw-client-runner.js +8 -4
  62. package/dist/wirings/cli/cli-runner.js +1 -1
  63. package/dist/wirings/gateway/gateway-runner.js +3 -3
  64. package/dist/wirings/http/http-runner.js +6 -6
  65. package/dist/wirings/rpc/rpc-runner.d.ts +4 -0
  66. package/dist/wirings/rpc/rpc-runner.js +41 -2
  67. package/dist/wirings/rpc/rpc-types.d.ts +6 -0
  68. package/dist/wirings/scheduler/scheduler-runner.js +1 -1
  69. package/dist/wirings/scope/define-scope.d.ts +19 -0
  70. package/dist/wirings/scope/define-scope.js +19 -0
  71. package/dist/wirings/scope/index.d.ts +2 -2
  72. package/dist/wirings/scope/index.js +2 -2
  73. package/dist/wirings/trigger/trigger-runner.js +3 -1
  74. package/dist/wirings/trigger/webhook-source-runner.js +5 -2
  75. package/dist/wirings/virtual-user/virtual-user-scaffold.js +3 -2
  76. package/dist/wirings/workflow/workflow-invocation-id.js +85 -8
  77. package/package.json +3 -1
  78. package/src/public-surface.json +65 -18
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Declares the screens an addon ships, which the host mounts when it is wired
3
+ * with `ui: true`. Read statically by `pikku all`, so `path`, `title`, `nav`, `scopes` and `app` must be literals.
4
+ */
5
+ export const defineScreens = (manifest) => manifest;
@@ -1,5 +1,7 @@
1
1
  export { wireAddon } from './wire-addon.js';
2
2
  export type { WireAddonConfig } from './wire-addon.js';
3
+ export { defineScreens } from './define-screens.js';
4
+ export type { ScreensManifest, AddonScreen, AddonComponentScreen, AddonAppScreen, } from './define-screens.js';
3
5
  export { wireRemoteAddon } from './wire-remote-addon.js';
4
6
  export type { WireRemoteAddonConfig, RemoteAddonAuth, } from './wire-remote-addon.js';
5
7
  export { resolveRemoteAddonToken, RemoteAddonAuthError, } from './remote-addon-auth.js';
@@ -1,3 +1,4 @@
1
1
  export { wireAddon } from './wire-addon.js';
2
+ export { defineScreens } from './define-screens.js';
2
3
  export { wireRemoteAddon } from './wire-remote-addon.js';
3
4
  export { resolveRemoteAddonToken, RemoteAddonAuthError, } from './remote-addon-auth.js';
@@ -41,8 +41,21 @@ export type WireAddonConfig = {
41
41
  mcpEndpoint?: boolean | string;
42
42
  /** Filters this addon in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
43
43
  tags?: string[];
44
+ /**
45
+ * Mounts the screens the addon ships (see `defineScreens`) in the host under
46
+ * `/addons/<name>`. Without it the addon's screens are ignored. The package
47
+ * must declare at least one, or `pikku all` rejects the wiring.
48
+ */
49
+ ui?: boolean;
44
50
  /** Required of every function in the addon, on top of the function's own. */
45
51
  scopes?: string[];
52
+ /**
53
+ * The other addons this one may call, as `{ '<package>': '<name>' }`: the
54
+ * package it was written against, and the name that package is wired under
55
+ * in this app. The addon calls `'<package>:<function>'`; anything it did not
56
+ * list is refused, whatever else the app has wired.
57
+ */
58
+ uses?: Record<string, string>;
46
59
  /** Points a secret the addon reads at a different key in this deployment, so two instances can hold different credentials. */
47
60
  secretOverrides?: Record<string, string>;
48
61
  /** Points a variable the addon reads at a different key in this deployment. */
@@ -65,6 +78,20 @@ export type WireAddonConfig = {
65
78
  * deploy manifest, and only the consuming app can grant it.
66
79
  */
67
80
  globalCredentials?: string;
81
+ /**
82
+ * The folder of the content store this instance owns. Everything it stores
83
+ * lands under it and it cannot see past it. Defaults to `name`.
84
+ */
85
+ contentBucket?: string;
86
+ /**
87
+ * Paths outside its own folder this instance may use, by path prefix:
88
+ * `read` allows reads and signed downloads, `write` adds uploads, writes and
89
+ * deletes. The longest matching prefix decides. The addon names a granted
90
+ * path by starting its bucket with `@`.
91
+ */
92
+ contentGrants?: Record<string, 'read' | 'write'>;
93
+ /** Hands over the whole `ContentService` unscoped. The value is the reason, recorded in the deploy manifest. */
94
+ globalContent?: string;
68
95
  };
69
96
  /**
70
97
  * Installs an addon into this project: its functions, wirings and scopes become
@@ -14,6 +14,7 @@ export const wireAddon = (config) => {
14
14
  tags: config.tags,
15
15
  ...(config.expose !== undefined ? { expose: config.expose } : {}),
16
16
  ...(config.scopes ? { scopes: config.scopes } : {}),
17
+ ...(config.uses ? { uses: config.uses } : {}),
17
18
  ...(config.secretOverrides
18
19
  ? { secretOverrides: config.secretOverrides }
19
20
  : {}),
@@ -31,6 +32,9 @@ export const wireAddon = (config) => {
31
32
  ...(config.globalCredentials
32
33
  ? { globalCredentials: config.globalCredentials }
33
34
  : {}),
35
+ ...(config.contentBucket ? { contentBucket: config.contentBucket } : {}),
36
+ ...(config.contentGrants ? { contentGrants: config.contentGrants } : {}),
37
+ ...(config.globalContent ? { globalContent: config.globalContent } : {}),
34
38
  });
35
39
  };
36
40
  /**
@@ -27,59 +27,62 @@ export function wrapChannelWithAGUI(inner, options) {
27
27
  let sawUsage = false;
28
28
  let usageModel;
29
29
  const usageTotals = { input: 0, output: 0 };
30
- function send(event) {
30
+ async function send(event) {
31
31
  if (!runStartedSent) {
32
32
  runStartedSent = true;
33
- inner.send({
33
+ await inner.send({
34
34
  type: 'RUN_STARTED',
35
35
  threadId,
36
36
  runId: resolveRunId(),
37
37
  });
38
38
  }
39
- inner.send(event);
39
+ await inner.send(event);
40
40
  }
41
- function endTextMessage() {
41
+ async function endTextMessage() {
42
42
  if (textMessageId) {
43
- send({ type: 'TEXT_MESSAGE_END', messageId: textMessageId });
43
+ await send({ type: 'TEXT_MESSAGE_END', messageId: textMessageId });
44
44
  textMessageId = null;
45
45
  }
46
46
  }
47
- function endThinkingMessage() {
47
+ async function endThinkingMessage() {
48
48
  if (thinkingMessageId) {
49
- send({ type: 'THINKING_TEXT_MESSAGE_END', messageId: thinkingMessageId });
50
- send({ type: 'THINKING_END' });
49
+ await send({
50
+ type: 'THINKING_TEXT_MESSAGE_END',
51
+ messageId: thinkingMessageId,
52
+ });
53
+ await send({ type: 'THINKING_END' });
51
54
  thinkingMessageId = null;
52
55
  }
53
56
  }
54
- function endStep() {
57
+ async function endStep() {
55
58
  if (openStepName) {
56
- send({ type: 'STEP_FINISHED', stepName: openStepName });
59
+ await send({ type: 'STEP_FINISHED', stepName: openStepName });
57
60
  openStepName = null;
58
61
  }
59
62
  }
60
- function ensureTextMessage() {
63
+ async function ensureTextMessage() {
61
64
  if (!textMessageId) {
62
65
  textMessageId = randomUUID();
63
- send({ type: 'TEXT_MESSAGE_START', messageId: textMessageId });
66
+ await send({ type: 'TEXT_MESSAGE_START', messageId: textMessageId });
64
67
  }
65
68
  return textMessageId;
66
69
  }
67
- function ensureThinkingMessage() {
70
+ async function ensureThinkingMessage() {
68
71
  if (!thinkingMessageId) {
69
72
  thinkingMessageId = randomUUID();
70
- send({ type: 'THINKING_START' });
71
- send({
73
+ await send({ type: 'THINKING_START' });
74
+ await send({
72
75
  type: 'THINKING_TEXT_MESSAGE_START',
73
76
  messageId: thinkingMessageId,
74
77
  });
75
78
  }
76
79
  return thinkingMessageId;
77
80
  }
78
- function finishRun() {
79
- endTextMessage();
80
- endThinkingMessage();
81
- endStep();
82
- send({
81
+ async function finishRun() {
82
+ await endTextMessage();
83
+ await endThinkingMessage();
84
+ await endStep();
85
+ await send({
83
86
  type: 'RUN_FINISHED',
84
87
  threadId,
85
88
  runId: resolveRunId(),
@@ -110,14 +113,14 @@ export function wrapChannelWithAGUI(inner, options) {
110
113
  remote: (funcName, data) => inner.remote(funcName, data),
111
114
  sendBinary: (data) => inner.sendBinary(data),
112
115
  close: () => inner.close(),
113
- send: (event) => {
116
+ send: async (event) => {
114
117
  if (terminal)
115
118
  return;
116
119
  switch (event.type) {
117
120
  case 'text-delta': {
118
- endThinkingMessage();
119
- const id = ensureTextMessage();
120
- send({
121
+ await endThinkingMessage();
122
+ const id = await ensureTextMessage();
123
+ await send({
121
124
  type: 'TEXT_MESSAGE_CONTENT',
122
125
  messageId: id,
123
126
  delta: event.text,
@@ -125,9 +128,9 @@ export function wrapChannelWithAGUI(inner, options) {
125
128
  break;
126
129
  }
127
130
  case 'reasoning-delta': {
128
- endTextMessage();
129
- const id = ensureThinkingMessage();
130
- send({
131
+ await endTextMessage();
132
+ const id = await ensureThinkingMessage();
133
+ await send({
131
134
  type: 'THINKING_TEXT_MESSAGE_CONTENT',
132
135
  messageId: id,
133
136
  delta: event.text,
@@ -135,19 +138,19 @@ export function wrapChannelWithAGUI(inner, options) {
135
138
  break;
136
139
  }
137
140
  case 'tool-call': {
138
- endTextMessage();
139
- endThinkingMessage();
140
- send({
141
+ await endTextMessage();
142
+ await endThinkingMessage();
143
+ await send({
141
144
  type: 'TOOL_CALL_START',
142
145
  toolCallId: event.toolCallId,
143
146
  toolCallName: event.toolName,
144
147
  });
145
- send({
148
+ await send({
146
149
  type: 'TOOL_CALL_ARGS',
147
150
  toolCallId: event.toolCallId,
148
151
  delta: resultToString(event.args) || '{}',
149
152
  });
150
- send({
153
+ await send({
151
154
  type: 'TOOL_CALL_END',
152
155
  toolCallId: event.toolCallId,
153
156
  toolCallName: event.toolName,
@@ -155,7 +158,7 @@ export function wrapChannelWithAGUI(inner, options) {
155
158
  break;
156
159
  }
157
160
  case 'tool-result': {
158
- send({
161
+ await send({
159
162
  type: 'TOOL_CALL_RESULT',
160
163
  messageId: randomUUID(),
161
164
  toolCallId: event.toolCallId,
@@ -165,8 +168,8 @@ export function wrapChannelWithAGUI(inner, options) {
165
168
  break;
166
169
  }
167
170
  case 'usage': {
168
- endTextMessage();
169
- endThinkingMessage();
171
+ await endTextMessage();
172
+ await endThinkingMessage();
170
173
  sawUsage = true;
171
174
  usageTotals.input += event.tokens.input;
172
175
  usageTotals.output += event.tokens.output;
@@ -175,30 +178,30 @@ export function wrapChannelWithAGUI(inner, options) {
175
178
  break;
176
179
  }
177
180
  case 'error': {
178
- endTextMessage();
179
- endThinkingMessage();
180
- endStep();
181
- send({ type: 'RUN_ERROR', message: event.message });
181
+ await endTextMessage();
182
+ await endThinkingMessage();
183
+ await endStep();
184
+ await send({ type: 'RUN_ERROR', message: event.message });
182
185
  terminal = true;
183
186
  break;
184
187
  }
185
188
  case 'done': {
186
- finishRun();
189
+ await finishRun();
187
190
  break;
188
191
  }
189
192
  case 'step-start': {
190
- endTextMessage();
191
- endThinkingMessage();
192
- endStep();
193
+ await endTextMessage();
194
+ await endThinkingMessage();
195
+ await endStep();
193
196
  stepSeq += 1;
194
197
  openStepName = `${event.agent ?? 'step'}#${stepSeq}`;
195
- send({ type: 'STEP_STARTED', stepName: openStepName });
198
+ await send({ type: 'STEP_STARTED', stepName: openStepName });
196
199
  break;
197
200
  }
198
201
  case 'approval-request': {
199
- endTextMessage();
200
- endThinkingMessage();
201
- send({
202
+ await endTextMessage();
203
+ await endThinkingMessage();
204
+ await send({
202
205
  type: 'CUSTOM',
203
206
  name: 'pikku:approval-request',
204
207
  value: {
@@ -214,9 +217,9 @@ export function wrapChannelWithAGUI(inner, options) {
214
217
  break;
215
218
  }
216
219
  case 'credential-request': {
217
- endTextMessage();
218
- endThinkingMessage();
219
- send({
220
+ await endTextMessage();
221
+ await endThinkingMessage();
222
+ await send({
220
223
  type: 'CUSTOM',
221
224
  name: 'pikku:credential-request',
222
225
  value: {
@@ -234,7 +237,7 @@ export function wrapChannelWithAGUI(inner, options) {
234
237
  break;
235
238
  }
236
239
  case 'generative-ui': {
237
- send({
240
+ await send({
238
241
  type: 'CUSTOM',
239
242
  name: 'pikku:generative-ui',
240
243
  value: { spec: event.spec },
@@ -242,7 +245,7 @@ export function wrapChannelWithAGUI(inner, options) {
242
245
  break;
243
246
  }
244
247
  case 'data': {
245
- send({
248
+ await send({
246
249
  type: 'CUSTOM',
247
250
  name: 'pikku:data',
248
251
  value: { name: event.name, data: event.data },
@@ -250,7 +253,7 @@ export function wrapChannelWithAGUI(inner, options) {
250
253
  break;
251
254
  }
252
255
  case 'agent-call': {
253
- send({
256
+ await send({
254
257
  type: 'CUSTOM',
255
258
  name: 'pikku:agent-call',
256
259
  value: {
@@ -262,7 +265,7 @@ export function wrapChannelWithAGUI(inner, options) {
262
265
  break;
263
266
  }
264
267
  case 'agent-result': {
265
- send({
268
+ await send({
266
269
  type: 'CUSTOM',
267
270
  name: 'pikku:agent-result',
268
271
  value: {
@@ -274,7 +277,7 @@ export function wrapChannelWithAGUI(inner, options) {
274
277
  break;
275
278
  }
276
279
  case 'suspended': {
277
- send({
280
+ await send({
278
281
  type: 'CUSTOM',
279
282
  name: 'pikku:suspended',
280
283
  value: { reason: event.reason, missingRpcs: event.missingRpcs },
@@ -282,10 +285,10 @@ export function wrapChannelWithAGUI(inner, options) {
282
285
  break;
283
286
  }
284
287
  case 'interrupted': {
285
- endTextMessage();
286
- endThinkingMessage();
287
- endStep();
288
- send({
288
+ await endTextMessage();
289
+ await endThinkingMessage();
290
+ await endStep();
291
+ await send({
289
292
  type: 'CUSTOM',
290
293
  name: 'pikku:interrupted',
291
294
  value: {
@@ -298,7 +301,7 @@ export function wrapChannelWithAGUI(inner, options) {
298
301
  }
299
302
  // knowledge: decisions/internals/agent-speech-travels-as-a-custom-agui-event.md
300
303
  case 'audio-delta': {
301
- send({
304
+ await send({
302
305
  type: 'CUSTOM',
303
306
  name: 'pikku:audio-delta',
304
307
  value: {
@@ -310,11 +313,11 @@ export function wrapChannelWithAGUI(inner, options) {
310
313
  break;
311
314
  }
312
315
  case 'audio-done': {
313
- send({ type: 'CUSTOM', name: 'pikku:audio-done', value: {} });
316
+ await send({ type: 'CUSTOM', name: 'pikku:audio-done', value: {} });
314
317
  break;
315
318
  }
316
319
  case 'transcript': {
317
- send({
320
+ await send({
318
321
  type: 'CUSTOM',
319
322
  name: 'pikku:transcript',
320
323
  value: { text: event.text },
@@ -1,3 +1,4 @@
1
+ import { type AbortScope } from '../../function/abort-scope.js';
1
2
  import type { AgentStorageService } from '../../services/agent-storage-service.js';
2
3
  /**
3
4
  * Why an in-flight agent run was cut short.
@@ -126,7 +127,9 @@ export declare const getInFlightTools: (runId: string) => string[];
126
127
  export declare const trackToolExecution: <T extends {
127
128
  name: string;
128
129
  readonly?: boolean;
129
- execute: (input: any) => Promise<any>;
130
+ execute: (input: any, options?: {
131
+ abortScope?: AbortScope;
132
+ }) => Promise<any>;
130
133
  }>(tools: T[], handle: InterruptibleRunHandle) => T[];
131
134
  export declare const trackInterruptNote: (threadId: string, write: Promise<void>) => void;
132
135
  /** Await an interrupted tool result still being written to this thread. */
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from './agent-utils.js';
2
- import { AbandonedError, runInAbortScope, } from '../../function/abort-scope.js';
2
+ import { AbandonedError } from '../../function/abort-scope.js';
3
3
  /**
4
4
  * Thrown out of the step loop when a run is interrupted, so an interruption is
5
5
  * distinguishable from a genuine failure at the one `catch` that would
@@ -162,7 +162,7 @@ export const trackToolExecution = (tools, handle) => tools.map((tool) => {
162
162
  }
163
163
  return {
164
164
  ...tool,
165
- execute: (input) => {
165
+ execute: (input, options) => {
166
166
  // Assumed to have changed something unless it says otherwise — the tool
167
167
  // that never declares its checkpoint is the one that cannot be assumed
168
168
  // harmless. `beginChanges()` can only ever make this more precise.
@@ -178,7 +178,7 @@ export const trackToolExecution = (tools, handle) => tools.map((tool) => {
178
178
  call.mutating = true;
179
179
  },
180
180
  };
181
- return handle.trackTool(tool.name, randomUUID(), () => runInAbortScope(scope, () => tool.execute(input)), {
181
+ return handle.trackTool(tool.name, randomUUID(), () => tool.execute(input, { ...options, abortScope: scope }), {
182
182
  get collectResult() {
183
183
  return call.mutating;
184
184
  },
@@ -1,4 +1,3 @@
1
- import { randomUUID } from 'crypto';
2
1
  export function resolveMemoryServices(agent, singletonServices) {
3
2
  const memoryConfig = agent.memory;
4
3
  const storage = memoryConfig?.storage
@@ -83,7 +82,7 @@ export async function loadContextMessages(memoryConfig, storage, input, workingM
83
82
  const workingMem = await workingMemoryStorage.getWorkingMemory(input.threadId, 'thread');
84
83
  const prompt = buildWorkingMemoryPrompt(workingMem, workingMemoryJsonSchema);
85
84
  contextMessages.push({
86
- id: `wm-${randomUUID()}`,
85
+ id: `wm-${globalThis.crypto.randomUUID()}`,
87
86
  role: 'system',
88
87
  content: prompt,
89
88
  createdAt: new Date(),
@@ -99,9 +98,9 @@ export async function saveMessages(storage, threadId, resourceId, memoryConfig,
99
98
  const newMessages = userMessage ? [userMessage] : [];
100
99
  for (const step of result.steps) {
101
100
  if (step.toolCalls?.length) {
102
- const toolCallIds = step.toolCalls.map(() => randomUUID());
101
+ const toolCallIds = step.toolCalls.map(() => globalThis.crypto.randomUUID());
103
102
  newMessages.push({
104
- id: randomUUID(),
103
+ id: globalThis.crypto.randomUUID(),
105
104
  role: 'assistant',
106
105
  toolCalls: step.toolCalls.map((tc, i) => ({
107
106
  id: toolCallIds[i],
@@ -111,7 +110,7 @@ export async function saveMessages(storage, threadId, resourceId, memoryConfig,
111
110
  createdAt: new Date(),
112
111
  });
113
112
  newMessages.push({
114
- id: randomUUID(),
113
+ id: globalThis.crypto.randomUUID(),
115
114
  role: 'tool',
116
115
  toolResults: step.toolCalls.map((tc, i) => ({
117
116
  id: toolCallIds[i],
@@ -132,7 +131,7 @@ export async function saveMessages(storage, threadId, resourceId, memoryConfig,
132
131
  ]
133
132
  : responseText;
134
133
  newMessages.push({
135
- id: randomUUID(),
134
+ id: globalThis.crypto.randomUUID(),
136
135
  role: 'assistant',
137
136
  content: assistantContent || undefined,
138
137
  createdAt: new Date(),
@@ -1,9 +1,10 @@
1
1
  // knowledge: decisions/internals/agent-model-config-stays-a-single-resolution-seam.md
2
2
  import { pikkuState } from '../../pikku-state.js';
3
+ import { readEnvVariable } from '../../env.js';
3
4
  const isProviderQualified = (model) => model.includes('/');
4
5
  /** `PIKKU_MODEL_ALIASES=cheap:openai/gpt-5-mini,tool:anthropic/claude-sonnet-5` */
5
6
  const envAliases = () => {
6
- const raw = process.env.PIKKU_MODEL_ALIASES;
7
+ const raw = readEnvVariable('PIKKU_MODEL_ALIASES');
7
8
  if (!raw)
8
9
  return {};
9
10
  const aliases = {};
@@ -247,7 +247,7 @@ export function createScopedChannel(parent, agentName, session) {
247
247
  },
248
248
  close: () => { },
249
249
  sendBinary: (data) => parent.sendBinary(data),
250
- send: (event) => {
250
+ send: async (event) => {
251
251
  if (event.type === 'done')
252
252
  return;
253
253
  if (event.type === 'approval-request') {
@@ -267,10 +267,14 @@ export function createScopedChannel(parent, agentName, session) {
267
267
  event.type === 'tool-result' ||
268
268
  event.type === 'usage' ||
269
269
  event.type === 'error') {
270
- parent.send({ ...event, agent: agentName, session });
270
+ await parent.send({
271
+ ...event,
272
+ agent: agentName,
273
+ session,
274
+ });
271
275
  }
272
276
  else {
273
- parent.send(event);
277
+ await parent.send(event);
274
278
  }
275
279
  },
276
280
  setState: (s) => parent.setState(s),
@@ -385,6 +389,9 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
385
389
  credentialGrants: capturedAddonConfig?.credentialGrants,
386
390
  globalSecrets: capturedAddonConfig?.globalSecrets,
387
391
  globalCredentials: capturedAddonConfig?.globalCredentials,
392
+ contentBucket: capturedAddonConfig?.contentBucket,
393
+ contentGrants: capturedAddonConfig?.contentGrants,
394
+ globalContent: capturedAddonConfig?.globalContent,
388
395
  }
389
396
  : undefined;
390
397
  services = await getOrCreatePackageSingletonServices(capturedPkg, singletonServices, addonInstance);
@@ -402,10 +409,12 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
402
409
  needsApproval: needsApproval || undefined,
403
410
  approvalDescriptionFn,
404
411
  readonly: fnMeta?.readonly || undefined,
405
- execute: async (toolInput) => {
412
+ execute: async (toolInput, options) => {
406
413
  const wire = params.sessionService
407
414
  ? { ...createMiddlewareSessionWireProps(params.sessionService) }
408
415
  : {};
416
+ if (options?.abortScope)
417
+ wire.abortScope = options.abortScope;
409
418
  const rpcService = new ContextAwareRPCService(singletonServices, wire, { sessionService: params.sessionService });
410
419
  return rpcService.rpc(toolName, toolInput);
411
420
  },
@@ -457,7 +466,7 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
457
466
  streamContext.delegateState.delegated = true;
458
467
  }
459
468
  const { channel } = streamContext;
460
- channel.send({
469
+ await channel.send({
461
470
  type: 'agent-call',
462
471
  agentName: subAgentName,
463
472
  session,
@@ -468,11 +477,11 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
468
477
  ? subChannel
469
478
  : {
470
479
  ...subChannel,
471
- send: (event) => {
480
+ send: async (event) => {
472
481
  if (event.type === 'text-delta' ||
473
482
  event.type === 'reasoning-delta')
474
483
  return;
475
- subChannel.send(event);
484
+ await subChannel.send(event);
476
485
  },
477
486
  };
478
487
  const resultText = await streamAgent(subAgentName, buildSubAgentRunInput(message, threadId, resourceId, parentContext), effectiveChannel, params, agentSessionMap, streamContext.options);
@@ -486,7 +495,7 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
486
495
  subApprovals: subChannel.approvals,
487
496
  };
488
497
  }
489
- channel.send({
498
+ await channel.send({
490
499
  type: 'agent-result',
491
500
  agentName: subAgentName,
492
501
  session,
@@ -544,7 +553,7 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
544
553
  name: workflowName,
545
554
  description: wfMeta.description || workflowName,
546
555
  inputSchema,
547
- execute: async (toolInput) => {
556
+ execute: async (toolInput, options) => {
548
557
  const workflowService = singletonServices.workflowService;
549
558
  if (!workflowService) {
550
559
  throw new Error(`workflowService is not configured — cannot run workflow tool '${workflowName}'`);
@@ -552,6 +561,8 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
552
561
  const wire = params.sessionService
553
562
  ? { ...createMiddlewareSessionWireProps(params.sessionService) }
554
563
  : {};
564
+ if (options?.abortScope)
565
+ wire.abortScope = options.abortScope;
555
566
  const rpcService = new ContextAwareRPCService(singletonServices, wire, { sessionService: params.sessionService });
556
567
  return workflowService.runToCompletion(workflowName, toolInput, rpcService, { wire: { type: 'internal' } });
557
568
  },
@@ -560,9 +571,9 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
560
571
  }
561
572
  for (const tool of tools) {
562
573
  const originalExecute = tool.execute;
563
- tool.execute = async (toolInput) => {
574
+ tool.execute = async (toolInput, options) => {
564
575
  try {
565
- return await originalExecute(toolInput);
576
+ return await originalExecute(toolInput, options);
566
577
  }
567
578
  catch (err) {
568
579
  if (err?.payload?.error === 'missing_credential') {
@@ -581,7 +592,7 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
581
592
  if (hasToolHooks) {
582
593
  for (const tool of tools) {
583
594
  const originalExecute = tool.execute;
584
- tool.execute = async (toolInput) => {
595
+ tool.execute = async (toolInput, options) => {
585
596
  const toolCallId = randomUUID();
586
597
  let args = (toolInput ?? {});
587
598
  for (const mw of agentMiddlewares) {
@@ -600,7 +611,7 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
600
611
  let result;
601
612
  let execError;
602
613
  try {
603
- result = await originalExecute(args);
614
+ result = await originalExecute(args, options);
604
615
  }
605
616
  catch (err) {
606
617
  execError = err;