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

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 (47) hide show
  1. package/README.md +44 -1
  2. package/dist/advanced.d.ts +15 -0
  3. package/dist/advanced.js +15 -0
  4. package/dist/app-server/client.d.ts +3 -1
  5. package/dist/app-server/client.js +8 -5
  6. package/dist/app-server/main.js +31 -15
  7. package/dist/app-server/params.d.ts +529 -0
  8. package/dist/app-server/params.js +85 -0
  9. package/dist/app-server/protocol.d.ts +23 -8
  10. package/dist/app-server/protocol.js +45 -37
  11. package/dist/app-server/server.d.ts +11 -3
  12. package/dist/app-server/server.js +92 -53
  13. package/dist/cron/cron-expr.js +1 -1
  14. package/dist/cron/extension.js +2 -2
  15. package/dist/cron/index.d.ts +2 -4
  16. package/dist/cron/index.js +2 -3
  17. package/dist/cron/tools.js +3 -3
  18. package/dist/extensions/index.d.ts +19 -5
  19. package/dist/extensions/index.js +66 -10
  20. package/dist/extensions/loader.d.ts +2 -2
  21. package/dist/extensions/loader.js +23 -15
  22. package/dist/extensions/manager.d.ts +17 -14
  23. package/dist/extensions/manager.js +29 -26
  24. package/dist/extensions/runtime.d.ts +69 -74
  25. package/dist/extensions/runtime.js +200 -391
  26. package/dist/extensions/types.d.ts +102 -77
  27. package/dist/harness-test-hooks.d.ts +9 -0
  28. package/dist/harness-test-hooks.js +8 -0
  29. package/dist/harness.d.ts +697 -359
  30. package/dist/harness.js +1520 -577
  31. package/dist/index.d.ts +20 -18
  32. package/dist/index.js +16 -23
  33. package/dist/internal.d.ts +5 -0
  34. package/dist/internal.js +4 -0
  35. package/dist/local.d.ts +34 -11
  36. package/dist/local.js +128 -34
  37. package/dist/providers.d.ts +139 -0
  38. package/dist/providers.js +256 -0
  39. package/dist/telemetry-posthog.d.ts +88 -0
  40. package/dist/telemetry-posthog.js +142 -0
  41. package/dist/telemetry.d.ts +4 -0
  42. package/dist/telemetry.js +3 -0
  43. package/dist/workspace-mcp.d.ts +66 -0
  44. package/dist/workspace-mcp.js +230 -0
  45. package/package.json +13 -6
  46. package/dist/core.d.ts +0 -12
  47. package/dist/core.js +0 -12
package/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  Public SDK entrypoint for building agents.
4
4
 
5
5
  ```ts
6
- import { Agent, Runner, Session, ChatModel, tool } from "operon-agents";
6
+ import { Agent, ChatModel, defineTool } from "operon-agents";
7
+ import { Runner, Session } from "operon-agents/advanced";
7
8
  ```
8
9
 
9
10
  Use `operon-agents` from applications. `operon-agents-core` is the lower-level
@@ -16,3 +17,45 @@ import { TransportMCPServer } from "operon-agents/mcp";
16
17
  import { bashTool, readTool } from "operon-agents/tools";
17
18
  import { OTelTracingProcessor } from "operon-agents/tracing";
18
19
  ```
20
+
21
+ ## Tracing
22
+
23
+ Every session drives the `TracingProcessor` the `infrastructure` hook returns as `tracing`: one
24
+ trace per run (a prompt and everything it triggers), spans for agents, turns, model generations,
25
+ tool calls and sub-agents, the session id on every span as `gen_ai.conversation.id`. By default
26
+ spans are metadata only. Pass `content: "delta"` to also record the system prompt, the messages
27
+ the model was asked to answer, its output, tool arguments and results — enough to replay a
28
+ conversation in a trace viewer. Local Jaeger:
29
+
30
+ ```sh
31
+ docker run --rm -p 16686:16686 -p 4318:4318 jaegertracing/jaeger:2.9.0
32
+ ```
33
+
34
+ ```ts
35
+ import { NodeTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
36
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
37
+ import { resourceFromAttributes } from "@opentelemetry/resources";
38
+ import { OTelTracingProcessor } from "operon-agents/tracing";
39
+
40
+ const provider = new NodeTracerProvider({
41
+ resource: resourceFromAttributes({ "service.name": "my-agent" }),
42
+ spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces" }))],
43
+ });
44
+ const tracing = new OTelTracingProcessor({ tracer: provider.getTracer("my-agent"), tracerProvider: provider, content: "delta", shutdownProvider: true });
45
+
46
+ const harness = await createLocalHarness({
47
+ config: { defaults: { model } },
48
+ infrastructure: () => ({ tracing }),
49
+ });
50
+ ```
51
+
52
+ `content: "full"` repeats the entire context on every generation (trace size grows with steps ×
53
+ context); `contentMaxChars` caps each attribute; `redact: true` masks tokens, emails and keys
54
+ before export when the collector is not on the same machine.
55
+
56
+ A run that pauses for an answer (`turn.paused`) ends its trace at the pause — the turn with
57
+ reason `paused`, the suspended tool calls marked, not failed — and the run that continues it is
58
+ the next trace of the same session. Spans are exported when they end, so a wait that may last
59
+ days is never an open span. `examples/linear-github/server/langfuse.ts` shows the same
60
+ processor exporting to Langfuse.
61
+
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `operon-agents/advanced` — everything beyond the main entry point: extension and cron plumbing,
3
+ * custom capabilities, stores, providers, MCP wiring and the event and store contracts.
4
+ *
5
+ * Supported, but for hosts composing their own deployment. Most applications only need
6
+ * `operon-agents`. Engine internals with no stability guarantee live on `operon-agents/internal`.
7
+ */
8
+ export * from "./harness.ts";
9
+ export * from "./extensions/index.ts";
10
+ export * from "./cron/index.ts";
11
+ export * from "./providers.ts";
12
+ export * from "./local.ts";
13
+ export * from "./workspace-mcp.ts";
14
+ export * from "operon-agents-core";
15
+ //# sourceMappingURL=advanced.d.ts.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `operon-agents/advanced` — everything beyond the main entry point: extension and cron plumbing,
3
+ * custom capabilities, stores, providers, MCP wiring and the event and store contracts.
4
+ *
5
+ * Supported, but for hosts composing their own deployment. Most applications only need
6
+ * `operon-agents`. Engine internals with no stability guarantee live on `operon-agents/internal`.
7
+ */
8
+ export * from "./harness.js";
9
+ export * from "./extensions/index.js";
10
+ export * from "./cron/index.js";
11
+ export * from "./providers.js";
12
+ export * from "./local.js";
13
+ export * from "./workspace-mcp.js";
14
+ export * from "operon-agents-core";
15
+ //# sourceMappingURL=advanced.js.map
@@ -7,7 +7,7 @@
7
7
  * in-process for tests.
8
8
  */
9
9
  import { type ChildProcess } from "node:child_process";
10
- import type { AgentEvent, ApprovalRequest, ApprovalResponse, QuestionRequest, QuestionResult, RunResult, SessionSummary } from "operon-agents-core";
10
+ import type { AgentEvent, ApprovalRequest, ApprovalResponse, QuestionRequest, QuestionResult, RunResult, SessionConfig, SessionSummary } from "operon-agents-core";
11
11
  import { type JsonRpcTransport } from "./codec.ts";
12
12
  import { type ClientCapabilities, type InitializeResult, type SessionFollowUpResult, type SessionInfo, type SessionSnapshotResult, type SessionSteerResult } from "./protocol.ts";
13
13
  export type ApprovalHandler = (request: ApprovalRequest, sessionId: string) => Promise<ApprovalResponse> | ApprovalResponse;
@@ -49,10 +49,12 @@ export declare class AppServerClient {
49
49
  workDir?: string;
50
50
  title?: string;
51
51
  model?: string;
52
+ config?: SessionConfig;
52
53
  }): Promise<SessionInfo>;
53
54
  resumeSession(sessionId: string): Promise<SessionInfo>;
54
55
  forkSession(sessionId: string, opts?: {
55
56
  title?: string;
57
+ config?: SessionConfig;
56
58
  }): Promise<SessionInfo>;
57
59
  listSessions(): Promise<readonly SessionSummary[]>;
58
60
  closeSession(sessionId: string): Promise<unknown>;
@@ -9,6 +9,7 @@
9
9
  import { spawn } from "node:child_process";
10
10
  import { classify, isFailure, nodeStreamTransport, request as makeRequest, RpcError, success, } from "./codec.js";
11
11
  import { Method, PROTOCOL_VERSION, } from "./protocol.js";
12
+ import { ClientCancelRequestParamsSchema, ClientRequestApprovalParamsSchema, ClientRequestQuestionParamsSchema, parseParams, } from "./params.js";
12
13
  export class AppServerClient {
13
14
  transport;
14
15
  child;
@@ -66,9 +67,11 @@ export class AppServerClient {
66
67
  return;
67
68
  }
68
69
  if (method === Method.ClientCancelRequest) {
69
- const { id } = (params ?? {});
70
- this.inboundAbort.get(id)?.abort();
71
- this.inboundAbort.delete(id);
70
+ const parsed = ClientCancelRequestParamsSchema.safeParse(params);
71
+ if (!parsed.success)
72
+ return;
73
+ this.inboundAbort.get(parsed.data.id)?.abort();
74
+ this.inboundAbort.delete(parsed.data.id);
72
75
  }
73
76
  }
74
77
  async onReverseRequest(id, method, params) {
@@ -88,13 +91,13 @@ export class AppServerClient {
88
91
  }
89
92
  async dispatchReverse(method, params, signal) {
90
93
  if (method === Method.ClientRequestApproval) {
91
- const p = params;
94
+ const p = parseParams(ClientRequestApprovalParamsSchema, params);
92
95
  if (!this.approvalHandler)
93
96
  return { decision: "rejected", feedback: "no approval handler" };
94
97
  return this.approvalHandler(p.request, p.sessionId);
95
98
  }
96
99
  if (method === Method.ClientRequestQuestion) {
97
- const p = params;
100
+ const p = parseParams(ClientRequestQuestionParamsSchema, params);
98
101
  if (!this.questionHandler)
99
102
  return null;
100
103
  return this.questionHandler(p.request, p.sessionId, { signal });
@@ -5,14 +5,22 @@
5
5
  *
6
6
  * operon-app-server --home ~/.agent --workdir /repo --model anthropic/claude-opus-4-...
7
7
  *
8
+ * Configuration comes from the same `config.toml` files any other host reads (`loadHarnessConfig`:
9
+ * `<home>/config.toml`, then the working directory's `.agents/config.toml` and
10
+ * `.agents/config.local.toml`), with the command-line flags laid over it. `--no-config` skips the
11
+ * files.
12
+ *
8
13
  * stdout carries the protocol (NDJSON JSON-RPC). stderr is logs. We defensively
9
14
  * redirect console.log/info/debug to stderr so a stray log can never corrupt the
10
15
  * protocol stream.
11
16
  */
12
- import { defineModel, DiskSessionRepository } from "operon-agents-core";
17
+ import { DiskSessionRepository, loadHarnessConfig } from "operon-agents-core";
13
18
  import { createHarness } from "../harness.js";
14
19
  import { nodeStreamTransport } from "./codec.js";
15
20
  import { AppServer } from "./server.js";
21
+ const PERMISSION_MODES = ["manual", "workspace", "yolo", "auto"];
22
+ /** What this binary has always run on when nothing names a model. */
23
+ const FALLBACK_MODEL = "anthropic/claude-opus-4-8";
16
24
  function parseArgs(argv) {
17
25
  const out = {};
18
26
  for (let i = 0; i < argv.length; i++) {
@@ -31,24 +39,19 @@ function parseArgs(argv) {
31
39
  break;
32
40
  case "--permission": {
33
41
  const mode = take();
34
- if (mode === "manual" || mode === "workspace" || mode === "yolo")
42
+ if (PERMISSION_MODES.includes(mode))
35
43
  out.permission = mode;
36
44
  break;
37
45
  }
46
+ case "--no-config":
47
+ out.noConfig = true;
48
+ break;
38
49
  default:
39
50
  break;
40
51
  }
41
52
  }
42
53
  return out;
43
54
  }
44
- /** Resolve a `provider/model` id into a ChatModel (API keys come from the env). */
45
- function resolveModel(id) {
46
- const slash = id.indexOf("/");
47
- if (slash <= 0 || slash === id.length - 1) {
48
- throw new Error(`invalid --model "${id}": expected "provider/model" (e.g. anthropic/claude-opus-4-...)`);
49
- }
50
- return defineModel({ provider: id.slice(0, slash), model: id.slice(slash + 1) });
51
- }
52
55
  function redirectStdoutLogsToStderr() {
53
56
  const toStderr = (...parts) => {
54
57
  process.stderr.write(`${parts.map((p) => (typeof p === "string" ? p : JSON.stringify(p))).join(" ")}\n`);
@@ -60,12 +63,25 @@ function redirectStdoutLogsToStderr() {
60
63
  async function main() {
61
64
  redirectStdoutLogsToStderr();
62
65
  const args = parseArgs(process.argv.slice(2));
63
- const harness = createHarness({
64
- model: args.model ?? "anthropic/claude-opus-4-8",
65
- resolveModel,
66
- ...(args.home !== undefined ? { repository: new DiskSessionRepository(args.home) } : {}),
66
+ // The files first, the flags over them: one configuration, whichever way it was written.
67
+ const file = args.noConfig === true
68
+ ? { defaults: {} }
69
+ : await loadHarnessConfig({
70
+ ...(args.home !== undefined ? { homeDir: args.home } : {}),
71
+ ...(args.workdir !== undefined ? { cwd: args.workdir } : {}),
72
+ });
73
+ const config = {
74
+ ...file,
75
+ defaults: {
76
+ ...file.defaults,
77
+ model: args.model ?? file.defaults.model ?? FALLBACK_MODEL,
78
+ ...(args.permission !== undefined ? { permission: { ...file.defaults.permission, mode: args.permission } } : {}),
79
+ },
67
80
  ...(args.workdir !== undefined ? { workDir: args.workdir } : {}),
68
- permission: { mode: args.permission ?? "yolo" },
81
+ };
82
+ const harness = createHarness({
83
+ config,
84
+ ...(args.home !== undefined ? { infrastructure: () => ({ sessionRepository: new DiskSessionRepository(args.home) }) } : {}),
69
85
  });
70
86
  const transport = nodeStreamTransport(process.stdin, process.stdout, {
71
87
  onParseError: (line, error) => process.stderr.write(`parse error: ${String(error)} on: ${line.slice(0, 200)}\n`),