operon-agents 0.1.0-alpha.1 → 0.1.0-alpha.3

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.
@@ -44,6 +44,8 @@ export interface StagingOptions {
44
44
  /** The extension's data folder, already created. */
45
45
  readonly dataDir?: string;
46
46
  createSession: ExtensionHostContext["createSession"];
47
+ /** Resolve one of the definition's `uses` names to its service handle. */
48
+ service(name: string): unknown;
47
49
  warn(message: string): void;
48
50
  }
49
51
  /**
@@ -33,6 +33,7 @@ export function stageDefinition(definition, options) {
33
33
  return { sessionDef: definition };
34
34
  const host = {
35
35
  createSession: (sessionOptions) => options.createSession(sessionOptions),
36
+ services: Object.freeze(Object.fromEntries((definition.uses ?? []).map((name) => [name, options.service(name)]))),
36
37
  ...(options.dir !== undefined ? { dir: options.dir } : {}),
37
38
  ...(options.dataDir !== undefined ? { dataDir: options.dataDir } : {}),
38
39
  warn: (message) => options.warn(`[extension ${definition.id}] ${message}`),
@@ -170,6 +171,7 @@ export class HarnessExtensionManager {
170
171
  dir: await this.loader.manifestDir(definition.id),
171
172
  dataDir,
172
173
  createSession: (sessionOptions) => this.bridge.createSession(sessionOptions),
174
+ service: (name) => this.bridge.services.handle(name),
173
175
  warn: (message) => this.bridge.warn(message),
174
176
  });
175
177
  }
@@ -516,10 +516,11 @@ export interface ExtensionAPI {
516
516
  /**
517
517
  * What an extension's `create` half is handed — the harness reach it needs to build a
518
518
  * process-shared instance. Deliberately narrow: session creation (a spawn factory's one
519
- * requirement) and a data directory (the file form's own folder; absent by value). No
520
- * service-registration call: the instance IS the return value, registered under the extension's `id`.
519
+ * requirement), a data directory (the file form's own folder; absent by value) and the handles
520
+ * of the services it declared in `uses`. No service-registration call: the instance IS the
521
+ * return value, registered under the extension's `id`.
521
522
  */
522
- export interface ExtensionHostContext {
523
+ export interface ExtensionHostContext<TServices extends Readonly<Record<string, unknown>> = Readonly<Record<string, unknown>>> {
523
524
  /** Open a brand-new session on this harness — what a spawn factory calls. */
524
525
  createSession(options?: {
525
526
  readonly title?: string;
@@ -528,6 +529,14 @@ export interface ExtensionHostContext {
528
529
  } & Record<string, unknown>): Promise<{
529
530
  readonly id: string;
530
531
  }>;
532
+ /**
533
+ * Handles to the services named in `uses`, by name — the same handles `setup` gets as
534
+ * `ctx.services`, available to the process half too. What lets a file-loaded bundle take its
535
+ * configuration from a host-registered service (`createHarness({ services })`) instead of
536
+ * baking it in: a reload re-runs `create`, so re-reading the service IS the config update.
537
+ * Method calls resolve the CURRENT provider; the handles expose methods only.
538
+ */
539
+ readonly services: TServices;
531
540
  /** The extension's own folder when it was file-loaded; `undefined` when passed by value.
532
541
  * Code only — an update replaces it wholesale. Keep files in `dataDir`. */
533
542
  readonly dir?: string;
@@ -584,7 +593,7 @@ export interface ExtensionDefinition<TShared = unknown, TParams = unknown, TServ
584
593
  /** Per-handler invocation budget. Defaults to 30s for decision points, 1s for observers. */
585
594
  readonly timeoutMs?: number;
586
595
  /** The process-shared half. Absent ⇒ an ordinary per-session extension. See above. */
587
- create?(host: ExtensionHostContext): TShared | Promise<TShared>;
596
+ create?(host: ExtensionHostContext<TServices>): TShared | Promise<TShared>;
588
597
  /** Services this extension consumes, by name — other extensions' `create` results (their
589
598
  * `id`s) or host-registered `services`. Resolved into `ctx.services` for `setup`. */
590
599
  readonly uses?: readonly (keyof TServices & string)[];
package/dist/harness.d.ts CHANGED
@@ -27,6 +27,21 @@ export interface DeliveryOptions {
27
27
  /** auto: steer a running turn, or start an idle one. */
28
28
  readonly mode?: DeliveryMode;
29
29
  }
30
+ /**
31
+ * Provenance a worker hands `dispatchAccepted` for an input whose acceptance is already
32
+ * journaled. `user` / `user_follow_up`: the user's own words, delivered on their behalf by the
33
+ * party holding the session's control surface (a managed API caller) — rendered bare, filed as
34
+ * steering / follow-up. `external`: another party's words, rendered inside the envelope.
35
+ */
36
+ export type AcceptedOrigin = (SteerOrigin & {
37
+ readonly kind: "external";
38
+ }) | {
39
+ readonly kind: "user";
40
+ readonly deliveryId: string;
41
+ } | {
42
+ readonly kind: "user_follow_up";
43
+ readonly deliveryId: string;
44
+ };
30
45
  export interface DeliveryReceipt {
31
46
  readonly deliveryId: string;
32
47
  readonly sessionId: string;
@@ -445,9 +460,7 @@ export declare class HarnessSession<TContext = unknown> {
445
460
  * Run state is read HERE, not at acceptance: by now a turn may have started or ended, so
446
461
  * whether this steers work in flight or starts a fresh turn can only be decided now.
447
462
  */
448
- dispatchAccepted(input: string, origin: SteerOrigin & {
449
- readonly kind: "external";
450
- }, acceptedAt?: number): DeliveryReceipt;
463
+ dispatchAccepted(input: string, origin: AcceptedOrigin, acceptedAt?: number): DeliveryReceipt;
451
464
  /**
452
465
  * Resume a durably-interrupted run. When a `prompt` returns `status:"interrupted"` — which
453
466
  * happens when no live approval handler is registered (`no live responder ⇒ durable`) — the run's
@@ -564,6 +577,7 @@ export declare class HarnessSession<TContext = unknown> {
564
577
  setPluginMcpServerEnabled(...args: Parameters<Session["setPluginMcpServerEnabled"]>): ReturnType<Session["setPluginMcpServerEnabled"]>;
565
578
  removePlugin(...args: Parameters<Session["removePlugin"]>): ReturnType<Session["removePlugin"]>;
566
579
  reloadPlugins(...args: Parameters<Session["reloadPlugins"]>): ReturnType<Session["reloadPlugins"]>;
580
+ listMcpTools(...args: Parameters<Session["listMcpTools"]>): ReturnType<Session["listMcpTools"]>;
567
581
  listMcpServers(...args: Parameters<Session["listMcpServers"]>): ReturnType<Session["listMcpServers"]>;
568
582
  reconnectMcpServer(...args: Parameters<Session["reconnectMcpServer"]>): ReturnType<Session["reconnectMcpServer"]>;
569
583
  /** Close the session and release its resources. */
package/dist/harness.js CHANGED
@@ -415,7 +415,9 @@ export class HarnessSession {
415
415
  if (this.closed)
416
416
  throw new Error(`session "${this.id}" is closed`);
417
417
  const deliveryId = origin.deliveryId;
418
- const channel = origin.channel ?? "steering";
418
+ const channel = origin.kind === "external"
419
+ ? origin.channel ?? "steering"
420
+ : origin.kind === "user_follow_up" ? "follow_up" : "steering";
419
421
  if (this.currentRun !== undefined) {
420
422
  const receipt = this.core.steer.steer(input, origin);
421
423
  return {
@@ -427,13 +429,17 @@ export class HarnessSession {
427
429
  steerId: receipt.steerId,
428
430
  };
429
431
  }
430
- const promptOrigin = {
431
- kind: "external",
432
- source: origin.source,
433
- deliveryId,
434
- ...(origin.actor !== undefined ? { actor: origin.actor } : {}),
435
- ...(origin.metadata !== undefined ? { metadata: origin.metadata } : {}),
436
- };
432
+ // A fresh turn is a fresh prompt: a follow-up landing on an idle session is simply the
433
+ // user's next prompt, so it is journaled as `user`, not `user_follow_up`.
434
+ const promptOrigin = origin.kind === "external"
435
+ ? {
436
+ kind: "external",
437
+ source: origin.source,
438
+ deliveryId,
439
+ ...(origin.actor !== undefined ? { actor: origin.actor } : {}),
440
+ ...(origin.metadata !== undefined ? { metadata: origin.metadata } : {}),
441
+ }
442
+ : { kind: "user", deliveryId };
437
443
  const message = {
438
444
  role: "user",
439
445
  content: [{ type: "text", text: renderSteerText(origin, input) }],
@@ -798,6 +804,9 @@ export class HarnessSession {
798
804
  return this.core.reloadPlugins(...args);
799
805
  }
800
806
  // ── MCP ──
807
+ listMcpTools(...args) {
808
+ return this.core.listMcpTools(...args);
809
+ }
801
810
  listMcpServers(...args) {
802
811
  return this.core.listMcpServers(...args);
803
812
  }
@@ -931,6 +940,7 @@ export class Harness {
931
940
  }
932
941
  return this.createSession(sessionOptions);
933
942
  },
943
+ service: (name) => this.services.handle(name),
934
944
  warn: (message) => console.warn(`[extensions] ${message}`),
935
945
  });
936
946
  return staged instanceof Promise ? staged.then(done, failed) : done(staged);
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "operon-agents",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.3",
4
4
  "description": "Public SDK entrypoint for building agents.",
5
5
  "type": "module",
6
+ "engines": {
7
+ "node": ">=22"
8
+ },
6
9
  "license": "MIT",
7
10
  "repository": {
8
11
  "type": "git",
@@ -64,7 +67,7 @@
64
67
  ],
65
68
  "dependencies": {
66
69
  "zod": "^4.4.3",
67
- "operon-agents-core": "0.1.0-alpha.1"
70
+ "operon-agents-core": "0.1.0-alpha.3"
68
71
  },
69
72
  "devDependencies": {
70
73
  "@earendil-works/pi-ai": "0.84.1"