@pikku/core 0.12.138 → 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 (34) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/middleware/addon-scope-cap.d.ts +19 -0
  3. package/dist/middleware/addon-scope-cap.js +34 -0
  4. package/dist/middleware/index.d.ts +1 -0
  5. package/dist/middleware/index.js +1 -0
  6. package/dist/scopes.d.ts +8 -0
  7. package/dist/scopes.js +23 -0
  8. package/dist/services/content-service.d.ts +13 -1
  9. package/dist/services/index.d.ts +2 -1
  10. package/dist/services/index.js +1 -0
  11. package/dist/services/local-content-request-handler.js +21 -60
  12. package/dist/services/local-content.d.ts +45 -1
  13. package/dist/services/local-content.js +202 -18
  14. package/dist/services/scoped-content-service.d.ts +35 -0
  15. package/dist/services/scoped-content-service.js +171 -0
  16. package/dist/types/state.types.d.ts +8 -0
  17. package/dist/wirings/addon/addon-reach.d.ts +3 -0
  18. package/dist/wirings/addon/addon-reach.js +21 -0
  19. package/dist/wirings/addon/addon-runner.d.ts +6 -0
  20. package/dist/wirings/addon/addon-runner.js +13 -0
  21. package/dist/wirings/addon/define-screens.d.ts +32 -0
  22. package/dist/wirings/addon/define-screens.js +5 -0
  23. package/dist/wirings/addon/index.d.ts +2 -0
  24. package/dist/wirings/addon/index.js +1 -0
  25. package/dist/wirings/addon/wire-addon.d.ts +27 -0
  26. package/dist/wirings/addon/wire-addon.js +4 -0
  27. package/dist/wirings/agent/agent-prepare.js +3 -0
  28. package/dist/wirings/rpc/rpc-runner.d.ts +4 -0
  29. package/dist/wirings/rpc/rpc-runner.js +41 -2
  30. package/dist/wirings/rpc/rpc-types.d.ts +6 -0
  31. package/dist/wirings/scope/index.d.ts +1 -1
  32. package/dist/wirings/scope/index.js +1 -1
  33. package/package.json +1 -1
  34. package/src/public-surface.json +59 -15
@@ -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
  /**
@@ -389,6 +389,9 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
389
389
  credentialGrants: capturedAddonConfig?.credentialGrants,
390
390
  globalSecrets: capturedAddonConfig?.globalSecrets,
391
391
  globalCredentials: capturedAddonConfig?.globalCredentials,
392
+ contentBucket: capturedAddonConfig?.contentBucket,
393
+ contentGrants: capturedAddonConfig?.contentGrants,
394
+ globalContent: capturedAddonConfig?.globalContent,
392
395
  }
393
396
  : undefined;
394
397
  services = await getOrCreatePackageSingletonServices(capturedPkg, singletonServices, addonInstance);
@@ -14,6 +14,9 @@ export declare class RemoteAddonRequestError extends PikkuError {
14
14
  readonly httpStatus: number;
15
15
  constructor(namespace: string, fnName: string, status: number, detail: string);
16
16
  }
17
+ export declare class AddonNotUsedError extends PikkuError {
18
+ constructor(caller: string, target: string);
19
+ }
17
20
  export declare const resolveNamespace: (namespacedFunction: string) => ResolvedFunction | null;
18
21
  export declare class ContextAwareRPCService {
19
22
  private services;
@@ -26,6 +29,7 @@ export declare class ContextAwareRPCService {
26
29
  }, packageName?: string | null);
27
30
  rpcExposed(funcName: string, data: any): Promise<any>;
28
31
  rpc<In = any, Out = any>(funcName: string, data: In): Promise<Out>;
32
+ private mapUsedAddon;
29
33
  /**
30
34
  * Resolution half of an addon RPC call: never executes anything, so an
31
35
  * `RPCNotFoundError` thrown by the function itself can never be mistaken for
@@ -1,3 +1,4 @@
1
+ import { addonCallerOf, addonMayReach } from '../addon/addon-reach.js';
1
2
  import { forwardStepName, isCompensationStepName, } from '../../function/compensation-name.js';
2
3
  import { runPikkuFunc } from '../../function/function-runner.js';
3
4
  import { addonInstanceForNamespace } from '../addon/addon-runner.js';
@@ -43,6 +44,15 @@ addError(RemoteAddonRequestError, {
43
44
  status: 502,
44
45
  message: 'Remote addon request failed.',
45
46
  });
47
+ export class AddonNotUsedError extends PikkuError {
48
+ constructor(caller, target) {
49
+ super(`Addon '${caller}' called '${target}' but does not list it in its wireAddon 'uses'.`);
50
+ }
51
+ }
52
+ addError(AddonNotUsedError, {
53
+ status: 403,
54
+ message: 'Addon called another addon it does not use.',
55
+ });
46
56
  export const resolveNamespace = (namespacedFunction) => {
47
57
  const colonIndex = namespacedFunction.indexOf(':');
48
58
  if (colonIndex === -1) {
@@ -68,6 +78,7 @@ const resolvePikkuFunction = (rpcName, packageName = null) => {
68
78
  if (pkgMeta) {
69
79
  return { pikkuFuncId: pkgMeta.pikkuFuncId || rpcName, packageName };
70
80
  }
81
+ throw new RPCNotFoundError(rpcName);
71
82
  }
72
83
  const rpc = pikkuState(null, 'rpc', 'meta');
73
84
  let rpcMeta = rpc[rpcName];
@@ -119,6 +130,10 @@ export class ContextAwareRPCService {
119
130
  this.packageName = packageName;
120
131
  }
121
132
  async rpcExposed(funcName, data) {
133
+ const addonCaller = addonCallerOf(this.wire);
134
+ if (addonCaller && !addonMayReach(addonCaller, funcName)) {
135
+ throw new AddonNotUsedError(addonCaller, funcName);
136
+ }
122
137
  let functionMeta;
123
138
  let resolvedAddon = null;
124
139
  if (funcName.includes(':')) {
@@ -159,6 +174,7 @@ export class ContextAwareRPCService {
159
174
  ...this.wire,
160
175
  };
161
176
  if (funcName.includes(':')) {
177
+ funcName = this.mapUsedAddon(funcName);
162
178
  const addonCall = this.resolveAddonFunction(funcName);
163
179
  if (addonCall !== NOT_RESOLVED) {
164
180
  return await this.executeAddonFunction(addonCall, data, updatedWire);
@@ -170,7 +186,7 @@ export class ContextAwareRPCService {
170
186
  }
171
187
  catch (e) {
172
188
  if (e instanceof RPCNotFoundError) {
173
- if (this.services.deploymentService) {
189
+ if (!this.packageName && this.services.deploymentService) {
174
190
  const session = await resolveWireSession(this.wire);
175
191
  return this.services.deploymentService.invoke(funcName, data, session, this.wire.traceId);
176
192
  }
@@ -189,6 +205,20 @@ export class ContextAwareRPCService {
189
205
  addonInstance,
190
206
  });
191
207
  }
208
+ mapUsedAddon(funcName) {
209
+ const caller = this.wire.addonNamespace;
210
+ if (!caller)
211
+ return funcName;
212
+ const colon = funcName.indexOf(':');
213
+ const prefix = funcName.slice(0, colon);
214
+ if (prefix === caller)
215
+ return funcName;
216
+ const uses = pikkuState(null, 'addons', 'packages').get(caller)?.uses;
217
+ const mapped = uses?.[prefix];
218
+ if (mapped)
219
+ return `${mapped}${funcName.slice(colon)}`;
220
+ throw new AddonNotUsedError(caller, funcName);
221
+ }
192
222
  /**
193
223
  * Resolution half of an addon RPC call: never executes anything, so an
194
224
  * `RPCNotFoundError` thrown by the function itself can never be mistaken for
@@ -243,6 +273,9 @@ export class ContextAwareRPCService {
243
273
  credentialGrants: resolved.addonConfig?.credentialGrants,
244
274
  globalSecrets: resolved.addonConfig?.globalSecrets,
245
275
  globalCredentials: resolved.addonConfig?.globalCredentials,
276
+ contentBucket: resolved.addonConfig?.contentBucket,
277
+ contentGrants: resolved.addonConfig?.contentGrants,
278
+ globalContent: resolved.addonConfig?.globalContent,
246
279
  },
247
280
  };
248
281
  }
@@ -305,6 +338,7 @@ export class ContextAwareRPCService {
305
338
  ...wire,
306
339
  };
307
340
  if (rpcName.includes(':')) {
341
+ rpcName = this.mapUsedAddon(rpcName);
308
342
  const addonCall = this.resolveAddonFunction(rpcName);
309
343
  if (addonCall !== NOT_RESOLVED) {
310
344
  return await this.executeAddonFunction(addonCall, data, mergedWire);
@@ -315,7 +349,9 @@ export class ContextAwareRPCService {
315
349
  resolved = resolvePikkuFunction(rpcName, this.packageName);
316
350
  }
317
351
  catch (e) {
318
- if (e instanceof RPCNotFoundError && this.services.deploymentService) {
352
+ if (e instanceof RPCNotFoundError &&
353
+ !this.packageName &&
354
+ this.services.deploymentService) {
319
355
  const session = await resolveWireSession(mergedWire);
320
356
  return this.services.deploymentService.invoke(rpcName, data, session, mergedWire.traceId);
321
357
  }
@@ -337,6 +373,9 @@ export class ContextAwareRPCService {
337
373
  if (!this.services.workflowService) {
338
374
  throw new Error('WorkflowService service not available');
339
375
  }
376
+ if (workflowName.includes(':')) {
377
+ workflowName = this.mapUsedAddon(workflowName);
378
+ }
340
379
  const parentRunId = this.wire.workflowStep?.runId ?? this.wire.graph?.runId;
341
380
  const wire = options?.wire ?? {
342
381
  type: this.wire.wireType ?? 'unknown',
@@ -59,6 +59,12 @@ export interface ResolvedFunction {
59
59
  globalSecrets?: string;
60
60
  /** Set by the consuming app: hand this instance the unscoped `CredentialService` */
61
61
  globalCredentials?: string;
62
+ /** Set by the consuming app: the content folder this instance owns */
63
+ contentBucket?: string;
64
+ /** Set by the consuming app: paths outside its folder this instance may read or write */
65
+ contentGrants?: Record<string, 'read' | 'write'>;
66
+ /** Set by the consuming app: hand this instance the unscoped `ContentService` */
67
+ globalContent?: string;
62
68
  /** Set by `wireRemoteAddon`: dispatch this namespace's RPCs over HTTP */
63
69
  remote?: boolean;
64
70
  serverUrl?: string | ((services: any) => string | Promise<string>);
@@ -1,4 +1,4 @@
1
1
  export { defineScope, declareScopes } from './define-scope.js';
2
2
  export { flattenScopeDefinitions, validateAndBuildScopeDefinitionsMeta, } from './validate-scope-definitions.js';
3
3
  export type { CoreScopes, CoreScopeNode, FlatScope, ScopeDefinitionMeta, ScopeDefinitionsMeta, ScopeDefinitions, ScopeNodeMeta, ScopeOrigin, } from './scope.types.js';
4
- export { hasScopes, verifyScopes } from '../../scopes.js';
4
+ export { hasScopes, intersectScopes, verifyScopes } from '../../scopes.js';
@@ -1,3 +1,3 @@
1
1
  export { defineScope, declareScopes } from './define-scope.js';
2
2
  export { flattenScopeDefinitions, validateAndBuildScopeDefinitionsMeta, } from './validate-scope-definitions.js';
3
- export { hasScopes, verifyScopes } from '../../scopes.js';
3
+ export { hasScopes, intersectScopes, verifyScopes } from '../../scopes.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.12.138",
3
+ "version": "0.12.139",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/pikkujs/pikku.git",
@@ -2,9 +2,11 @@
2
2
  ".": [],
3
3
  "./types": [],
4
4
  "./middleware": [
5
+ "ADDON_HEADER",
5
6
  "addGlobalMiddleware",
6
7
  "addGlobalPermission",
7
8
  "addTagMiddleware",
9
+ "addonScopeCap",
8
10
  "authAPIKey",
9
11
  "authBearer",
10
12
  "authCookie",
@@ -109,7 +111,9 @@
109
111
  "reconstructStateAt"
110
112
  ],
111
113
  "./workflow/types": [],
112
- "./actor-flow": ["runConversation"],
114
+ "./actor-flow": [
115
+ "runConversation"
116
+ ],
113
117
  "./virtual-user": [
114
118
  "DEFAULT_MAX_INTERVAL_MS",
115
119
  "DEFAULT_MIN_INTERVAL_MS",
@@ -172,7 +176,9 @@
172
176
  "wireHTTP",
173
177
  "wireHTTPRoutes"
174
178
  ],
175
- "./remote": ["buildRemoteHeaders"],
179
+ "./remote": [
180
+ "buildRemoteHeaders"
181
+ ],
176
182
  "./queue": [
177
183
  "QueueJobDiscardedError",
178
184
  "QueueJobFailedError",
@@ -215,6 +221,7 @@
215
221
  ],
216
222
  "./addon": [
217
223
  "RemoteAddonAuthError",
224
+ "defineScreens",
218
225
  "resolveRemoteAddonToken",
219
226
  "wireAddon",
220
227
  "wireRemoteAddon"
@@ -225,7 +232,10 @@
225
232
  "safeFetch",
226
233
  "setDefaultHostResolver"
227
234
  ],
228
- "./node-host-resolver": ["installNodeHostResolver", "nodeHostResolver"],
235
+ "./node-host-resolver": [
236
+ "installNodeHostResolver",
237
+ "nodeHostResolver"
238
+ ],
229
239
  "./mcp": [
230
240
  "MCPEndpointRegistry",
231
241
  "MCPError",
@@ -312,7 +322,10 @@
312
322
  "wantsStackTrace",
313
323
  "wireCLI"
314
324
  ],
315
- "./cli/command-parser": ["generateCommandHelp", "parseCLIArguments"],
325
+ "./cli/command-parser": [
326
+ "generateCommandHelp",
327
+ "parseCLIArguments"
328
+ ],
316
329
  "./cli/channel": [
317
330
  "executeCLIViaChannel",
318
331
  "executeRawCLIViaChannel",
@@ -330,6 +343,7 @@
330
343
  "defineScope",
331
344
  "flattenScopeDefinitions",
332
345
  "hasScopes",
346
+ "intersectScopes",
333
347
  "validateAndBuildScopeDefinitionsMeta",
334
348
  "verifyScopes"
335
349
  ],
@@ -373,8 +387,13 @@
373
387
  "deriveOAuth2AppSecrets",
374
388
  "validateAndBuildSecretDefinitionsMeta"
375
389
  ],
376
- "./variable": ["defineVariable", "validateAndBuildVariableDefinitionsMeta"],
377
- "./webhook": ["defineOutgoingWebhook"],
390
+ "./variable": [
391
+ "defineVariable",
392
+ "validateAndBuildVariableDefinitionsMeta"
393
+ ],
394
+ "./webhook": [
395
+ "defineOutgoingWebhook"
396
+ ],
378
397
  "./oauth2": [],
379
398
  "./errors": [
380
399
  "AIProviderAuthError",
@@ -442,18 +461,17 @@
442
461
  "InMemoryWorkflowService",
443
462
  "IncomingWebhookService",
444
463
  "JsonConsoleLogger",
464
+ "LeaseLostError",
465
+ "LeaseTakenError",
445
466
  "LocalCredentialService",
446
467
  "LocalEmailService",
447
468
  "LocalGatewayService",
448
469
  "LocalSecretService",
449
470
  "LocalVariablesService",
450
- "LeaseLostError",
451
- "LeaseTakenError",
452
471
  "LogLevel",
453
472
  "NoopAuditService",
454
473
  "PIKKU_OUTGOING_WEBHOOK_QUEUE_NAME",
455
474
  "PikkuCredentialWireService",
456
- "holdLease",
457
475
  "PikkuSessionService",
458
476
  "QueueWebhookService",
459
477
  "REMOTE_JOBS_SECRET_HEADER",
@@ -461,6 +479,7 @@
461
479
  "REMOTE_QUEUE_JOB_PATH",
462
480
  "REMOTE_SCHEDULER_JOB_PATH",
463
481
  "SchedulerService",
482
+ "ScopedContentService",
464
483
  "ScopedCredentialService",
465
484
  "ScopedSecretService",
466
485
  "SecretAccessDeniedError",
@@ -477,6 +496,7 @@
477
496
  "createStubProxy",
478
497
  "deriveActorSecret",
479
498
  "getStubTracker",
499
+ "holdLease",
480
500
  "isTestRun",
481
501
  "pikkuRemoteQueueJobFunc",
482
502
  "pikkuRemoteScheduledJobFunc",
@@ -500,10 +520,27 @@
500
520
  "subjectIdOf",
501
521
  "validateAndBuildFeatureFlagDefinitionsMeta"
502
522
  ],
503
- "./services/local-meta": ["LocalMetaService"],
504
- "./services/v8-coverage": ["V8CoverageService"],
505
- "./services/istanbul-coverage": ["IstanbulCoverageService"],
506
- "./services/local-content": ["LocalContent", "signedContentPath"],
523
+ "./services/local-meta": [
524
+ "LocalMetaService"
525
+ ],
526
+ "./services/v8-coverage": [
527
+ "V8CoverageService"
528
+ ],
529
+ "./services/istanbul-coverage": [
530
+ "IstanbulCoverageService"
531
+ ],
532
+ "./services/local-content": [
533
+ "CONTENT_VISIBILITIES",
534
+ "LocalContent",
535
+ "PUBLIC_URL_SEGMENT",
536
+ "UploadTooLargeError",
537
+ "parseContentSizeLimit",
538
+ "parseContentUrlPath",
539
+ "resolveContentRequestTarget",
540
+ "resolveVisibilityPath",
541
+ "signedContentPath",
542
+ "streamUploadToFile"
543
+ ],
507
544
  "./services/local-content-request-handler": [
508
545
  "createLocalContentRequestHandler",
509
546
  "verifySignedContentRequest"
@@ -592,6 +629,13 @@
592
629
  "getSchema",
593
630
  "validateSchema"
594
631
  ],
595
- "./testing": ["clearPikkuRuntimeState", "defineServiceTests"],
596
- "./dev": ["pikkuDevReloader", "reconcileAddonRegistry", "reloadGeneratedMeta"]
632
+ "./testing": [
633
+ "clearPikkuRuntimeState",
634
+ "defineServiceTests"
635
+ ],
636
+ "./dev": [
637
+ "pikkuDevReloader",
638
+ "reconcileAddonRegistry",
639
+ "reloadGeneratedMeta"
640
+ ]
597
641
  }