operon-agents 0.1.0-alpha.2 → 0.1.0-alpha.20
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.
- package/README.md +42 -0
- package/dist/app-server/client.js +8 -5
- package/dist/app-server/main.js +1 -1
- package/dist/app-server/params.d.ts +115 -0
- package/dist/app-server/params.js +81 -0
- package/dist/app-server/protocol.d.ts +10 -4
- package/dist/app-server/protocol.js +45 -37
- package/dist/app-server/server.d.ts +0 -1
- package/dist/app-server/server.js +46 -35
- package/dist/core.d.ts +2 -2
- package/dist/core.js +2 -2
- package/dist/cron/cron-expr.js +1 -1
- package/dist/cron/extension.js +2 -2
- package/dist/extensions/index.d.ts +19 -5
- package/dist/extensions/index.js +66 -10
- package/dist/extensions/loader.d.ts +2 -2
- package/dist/extensions/loader.js +23 -15
- package/dist/extensions/manager.d.ts +15 -13
- package/dist/extensions/manager.js +28 -25
- package/dist/extensions/runtime.d.ts +69 -74
- package/dist/extensions/runtime.js +183 -381
- package/dist/extensions/types.d.ts +55 -45
- package/dist/harness.d.ts +339 -180
- package/dist/harness.js +673 -420
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -0
- package/dist/internal.d.ts +1 -0
- package/dist/internal.js +1 -0
- package/dist/local.d.ts +25 -8
- package/dist/local.js +103 -33
- package/dist/providers.d.ts +144 -0
- package/dist/providers.js +280 -0
- package/dist/telemetry-posthog.d.ts +88 -0
- package/dist/telemetry-posthog.js +142 -0
- package/dist/telemetry.d.ts +4 -0
- package/dist/telemetry.js +3 -0
- package/dist/workspace-mcp.d.ts +62 -0
- package/dist/workspace-mcp.js +222 -0
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -16,3 +16,45 @@ import { TransportMCPServer } from "operon-agents/mcp";
|
|
|
16
16
|
import { bashTool, readTool } from "operon-agents/tools";
|
|
17
17
|
import { OTelTracingProcessor } from "operon-agents/tracing";
|
|
18
18
|
```
|
|
19
|
+
|
|
20
|
+
## Tracing
|
|
21
|
+
|
|
22
|
+
Every session drives the `TracingProcessor` the `harness` hook returns as `tracing`: one
|
|
23
|
+
trace per run (a prompt and everything it triggers), spans for agents, turns, model generations,
|
|
24
|
+
tool calls and sub-agents, the session id on every span as `gen_ai.conversation.id`. By default
|
|
25
|
+
spans are metadata only. Pass `content: "delta"` to also record the system prompt, the messages
|
|
26
|
+
the model was asked to answer, its output, tool arguments and results — enough to replay a
|
|
27
|
+
conversation in a trace viewer. Local Jaeger:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
docker run --rm -p 16686:16686 -p 4318:4318 jaegertracing/jaeger:2.9.0
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { NodeTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
35
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
36
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
37
|
+
import { OTelTracingProcessor } from "operon-agents/tracing";
|
|
38
|
+
|
|
39
|
+
const provider = new NodeTracerProvider({
|
|
40
|
+
resource: resourceFromAttributes({ "service.name": "my-agent" }),
|
|
41
|
+
spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces" }))],
|
|
42
|
+
});
|
|
43
|
+
const tracing = new OTelTracingProcessor({ tracer: provider.getTracer("my-agent"), tracerProvider: provider, content: "delta", shutdownProvider: true });
|
|
44
|
+
|
|
45
|
+
const harness = await createLocalHarness({
|
|
46
|
+
model,
|
|
47
|
+
harness: () => ({ tracing }),
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`content: "full"` repeats the entire context on every generation (trace size grows with steps ×
|
|
52
|
+
context); `contentMaxChars` caps each attribute; `redact: true` masks tokens, emails and keys
|
|
53
|
+
before export when the collector is not on the same machine.
|
|
54
|
+
|
|
55
|
+
A run that pauses for an answer (`turn.paused`) ends its trace at the pause — the turn with
|
|
56
|
+
reason `paused`, the suspended tool calls marked, not failed — and the run that continues it is
|
|
57
|
+
the next trace of the same session. Spans are exported when they end, so a wait that may last
|
|
58
|
+
days is never an open span. `examples/linear-github/server/langfuse.ts` shows the same
|
|
59
|
+
processor exporting to Langfuse.
|
|
60
|
+
|
|
@@ -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
|
|
70
|
-
|
|
71
|
-
|
|
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 });
|
package/dist/app-server/main.js
CHANGED
|
@@ -63,7 +63,7 @@ async function main() {
|
|
|
63
63
|
const harness = createHarness({
|
|
64
64
|
model: args.model ?? "anthropic/claude-opus-4-8",
|
|
65
65
|
resolveModel,
|
|
66
|
-
...(args.home !== undefined ? {
|
|
66
|
+
...(args.home !== undefined ? { harness: () => ({ sessionRepository: new DiskSessionRepository(args.home) }) } : {}),
|
|
67
67
|
...(args.workdir !== undefined ? { workDir: args.workdir } : {}),
|
|
68
68
|
permission: { mode: args.permission ?? "yolo" },
|
|
69
69
|
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime shape of every host → server request and server → host reverse request in
|
|
3
|
+
* `protocol.ts`. That module stays pure types so other languages can mirror it; this one is the
|
|
4
|
+
* TypeScript side's proof that a message read off the wire actually has that shape before a
|
|
5
|
+
* handler touches it. A miss is an `InvalidParams` error that names the field.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import type { Message, QuestionItem } from "operon-agents-core";
|
|
9
|
+
import type { ClientCancelRequestParams, ClientRequestApprovalParams, ClientRequestQuestionParams, InitializeParams, SessionFollowUpParams, SessionForkParams, SessionInvokeParams, SessionNewParams, SessionPromptParams, SessionRef, SessionRespondParams, SessionResumeParams, SessionSnapshotParams, SessionSteerParams } from "./protocol.ts";
|
|
10
|
+
export declare const SessionRefSchema: z.ZodObject<{
|
|
11
|
+
sessionId: z.ZodString;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export declare const InitializeParamsSchema: z.ZodObject<{
|
|
14
|
+
protocolVersion: z.ZodNumber;
|
|
15
|
+
clientInfo: z.ZodOptional<z.ZodObject<{
|
|
16
|
+
name: z.ZodString;
|
|
17
|
+
version: z.ZodOptional<z.ZodString>;
|
|
18
|
+
}, z.core.$strip>>;
|
|
19
|
+
clientCapabilities: z.ZodOptional<z.ZodObject<{
|
|
20
|
+
approval: z.ZodOptional<z.ZodBoolean>;
|
|
21
|
+
question: z.ZodOptional<z.ZodBoolean>;
|
|
22
|
+
}, z.core.$strip>>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
export declare const SessionNewParamsSchema: z.ZodObject<{
|
|
25
|
+
workDir: z.ZodOptional<z.ZodString>;
|
|
26
|
+
title: z.ZodOptional<z.ZodString>;
|
|
27
|
+
model: z.ZodOptional<z.ZodString>;
|
|
28
|
+
}, z.core.$strip>;
|
|
29
|
+
export declare const SessionResumeParamsSchema: z.ZodObject<{
|
|
30
|
+
sessionId: z.ZodString;
|
|
31
|
+
}, z.core.$strip>;
|
|
32
|
+
export declare const SessionForkParamsSchema: z.ZodObject<{
|
|
33
|
+
title: z.ZodOptional<z.ZodString>;
|
|
34
|
+
sessionId: z.ZodString;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
37
|
+
sessionId: z.ZodString;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
export declare const SessionCancelParamsSchema: z.ZodObject<{
|
|
40
|
+
sessionId: z.ZodString;
|
|
41
|
+
}, z.core.$strip>;
|
|
42
|
+
export declare const SessionPromptParamsSchema: z.ZodObject<{
|
|
43
|
+
input: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<Message[], Message[]>]>;
|
|
44
|
+
sessionId: z.ZodString;
|
|
45
|
+
}, z.core.$strip>;
|
|
46
|
+
export declare const SessionSnapshotParamsSchema: z.ZodObject<{
|
|
47
|
+
addresses: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
48
|
+
maxMessages: z.ZodOptional<z.ZodNumber>;
|
|
49
|
+
sessionId: z.ZodString;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
export declare const SessionRespondParamsSchema: z.ZodObject<{
|
|
52
|
+
answers: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
53
|
+
decision: z.ZodEnum<{
|
|
54
|
+
approved: "approved";
|
|
55
|
+
rejected: "rejected";
|
|
56
|
+
cancelled: "cancelled";
|
|
57
|
+
}>;
|
|
58
|
+
scope: z.ZodOptional<z.ZodLiteral<"session">>;
|
|
59
|
+
feedback: z.ZodOptional<z.ZodString>;
|
|
60
|
+
}, z.core.$strip>>;
|
|
61
|
+
sessionId: z.ZodString;
|
|
62
|
+
}, z.core.$strip>;
|
|
63
|
+
export declare const SessionSteerParamsSchema: z.ZodObject<{
|
|
64
|
+
text: z.ZodString;
|
|
65
|
+
sessionId: z.ZodString;
|
|
66
|
+
}, z.core.$strip>;
|
|
67
|
+
export declare const SessionFollowUpParamsSchema: z.ZodObject<{
|
|
68
|
+
text: z.ZodString;
|
|
69
|
+
sessionId: z.ZodString;
|
|
70
|
+
}, z.core.$strip>;
|
|
71
|
+
export declare const SessionInvokeParamsSchema: z.ZodObject<{
|
|
72
|
+
method: z.ZodString;
|
|
73
|
+
args: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodUnknown>>>;
|
|
74
|
+
sessionId: z.ZodString;
|
|
75
|
+
}, z.core.$strip>;
|
|
76
|
+
export declare const ClientRequestApprovalParamsSchema: z.ZodObject<{
|
|
77
|
+
request: z.ZodObject<{
|
|
78
|
+
toolCallId: z.ZodString;
|
|
79
|
+
toolName: z.ZodString;
|
|
80
|
+
approvalRule: z.ZodString;
|
|
81
|
+
}, z.core.$loose>;
|
|
82
|
+
sessionId: z.ZodString;
|
|
83
|
+
}, z.core.$strip>;
|
|
84
|
+
export declare const ClientRequestQuestionParamsSchema: z.ZodObject<{
|
|
85
|
+
request: z.ZodObject<{
|
|
86
|
+
turnId: z.ZodString;
|
|
87
|
+
toolCallId: z.ZodString;
|
|
88
|
+
questions: z.ZodCustom<readonly QuestionItem[], readonly QuestionItem[]>;
|
|
89
|
+
}, z.core.$loose>;
|
|
90
|
+
sessionId: z.ZodString;
|
|
91
|
+
}, z.core.$strip>;
|
|
92
|
+
export declare const ClientCancelRequestParamsSchema: z.ZodObject<{
|
|
93
|
+
id: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
94
|
+
}, z.core.$strip>;
|
|
95
|
+
/** Parse `params` against a method's schema; a miss is `InvalidParams` naming the field. */
|
|
96
|
+
export declare function parseParams<S extends z.ZodType>(schema: S, params: unknown): z.output<S>;
|
|
97
|
+
export declare const INVOKABLE: Set<string>;
|
|
98
|
+
type Assert<T extends true> = T;
|
|
99
|
+
type Extends<A, B> = A extends B ? true : false;
|
|
100
|
+
export type _Initialize = Assert<Extends<InitializeParams, z.input<typeof InitializeParamsSchema>>>;
|
|
101
|
+
export type _SessionNew = Assert<Extends<SessionNewParams, z.input<typeof SessionNewParamsSchema>>>;
|
|
102
|
+
export type _SessionResume = Assert<Extends<SessionResumeParams, z.input<typeof SessionResumeParamsSchema>>>;
|
|
103
|
+
export type _SessionFork = Assert<Extends<SessionForkParams, z.input<typeof SessionForkParamsSchema>>>;
|
|
104
|
+
export type _SessionRef = Assert<Extends<SessionRef, z.input<typeof SessionRefSchema>>>;
|
|
105
|
+
export type _SessionPrompt = Assert<Extends<SessionPromptParams, z.input<typeof SessionPromptParamsSchema>>>;
|
|
106
|
+
export type _SessionSnapshot = Assert<Extends<SessionSnapshotParams, z.input<typeof SessionSnapshotParamsSchema>>>;
|
|
107
|
+
export type _SessionRespond = Assert<Extends<SessionRespondParams, z.input<typeof SessionRespondParamsSchema>>>;
|
|
108
|
+
export type _SessionSteer = Assert<Extends<SessionSteerParams, z.input<typeof SessionSteerParamsSchema>>>;
|
|
109
|
+
export type _SessionFollowUp = Assert<Extends<SessionFollowUpParams, z.input<typeof SessionFollowUpParamsSchema>>>;
|
|
110
|
+
export type _SessionInvoke = Assert<Extends<SessionInvokeParams, z.input<typeof SessionInvokeParamsSchema>>>;
|
|
111
|
+
export type _ClientRequestApproval = Assert<Extends<z.output<typeof ClientRequestApprovalParamsSchema>, ClientRequestApprovalParams>>;
|
|
112
|
+
export type _ClientRequestQuestion = Assert<Extends<z.output<typeof ClientRequestQuestionParamsSchema>, ClientRequestQuestionParams>>;
|
|
113
|
+
export type _ClientCancelRequest = Assert<Extends<ClientCancelRequestParams, z.input<typeof ClientCancelRequestParamsSchema>>>;
|
|
114
|
+
export {};
|
|
115
|
+
//# sourceMappingURL=params.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime shape of every host → server request and server → host reverse request in
|
|
3
|
+
* `protocol.ts`. That module stays pure types so other languages can mirror it; this one is the
|
|
4
|
+
* TypeScript side's proof that a message read off the wire actually has that shape before a
|
|
5
|
+
* handler touches it. A miss is an `InvalidParams` error that names the field.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { ErrorCode, INVOKABLE_METHODS } from "./protocol.js";
|
|
9
|
+
import { RpcError } from "./codec.js";
|
|
10
|
+
const sessionRef = { sessionId: z.string().min(1, "sessionId required") };
|
|
11
|
+
export const SessionRefSchema = z.object(sessionRef);
|
|
12
|
+
export const InitializeParamsSchema = z.object({
|
|
13
|
+
protocolVersion: z.number({ error: "protocolVersion (number) required" }),
|
|
14
|
+
clientInfo: z.object({ name: z.string(), version: z.string().optional() }).optional(),
|
|
15
|
+
clientCapabilities: z.object({ approval: z.boolean().optional(), question: z.boolean().optional() }).optional(),
|
|
16
|
+
});
|
|
17
|
+
export const SessionNewParamsSchema = z.object({
|
|
18
|
+
workDir: z.string().optional(),
|
|
19
|
+
title: z.string().optional(),
|
|
20
|
+
model: z.string().optional(),
|
|
21
|
+
});
|
|
22
|
+
export const SessionResumeParamsSchema = SessionRefSchema;
|
|
23
|
+
export const SessionForkParamsSchema = z.object({ ...sessionRef, title: z.string().optional() });
|
|
24
|
+
export const SessionCloseParamsSchema = SessionRefSchema;
|
|
25
|
+
export const SessionCancelParamsSchema = SessionRefSchema;
|
|
26
|
+
const isMessageLike = (value) => typeof value === "object" && value !== null && typeof value.role === "string";
|
|
27
|
+
/** `AgentInput`: a prompt string, or a message array whose contents the core validates itself. */
|
|
28
|
+
const AgentInputSchema = z.union([z.string(), z.custom((value) => Array.isArray(value) && value.every(isMessageLike))], { error: "input must be a string or an array of messages" });
|
|
29
|
+
export const SessionPromptParamsSchema = z.object({ ...sessionRef, input: AgentInputSchema });
|
|
30
|
+
export const SessionSnapshotParamsSchema = z.object({
|
|
31
|
+
...sessionRef,
|
|
32
|
+
addresses: z.array(z.string()).readonly().optional(),
|
|
33
|
+
maxMessages: z.number().int().nonnegative().optional(),
|
|
34
|
+
});
|
|
35
|
+
const ApprovalResponseSchema = z.object({
|
|
36
|
+
decision: z.enum(["approved", "rejected", "cancelled"]),
|
|
37
|
+
scope: z.literal("session").optional(),
|
|
38
|
+
feedback: z.string().optional(),
|
|
39
|
+
});
|
|
40
|
+
export const SessionRespondParamsSchema = z.object({
|
|
41
|
+
...sessionRef,
|
|
42
|
+
answers: z.record(z.string(), ApprovalResponseSchema),
|
|
43
|
+
});
|
|
44
|
+
export const SessionSteerParamsSchema = z.object({ ...sessionRef, text: z.string() });
|
|
45
|
+
export const SessionFollowUpParamsSchema = z.object({ ...sessionRef, text: z.string() });
|
|
46
|
+
export const SessionInvokeParamsSchema = z.object({
|
|
47
|
+
...sessionRef,
|
|
48
|
+
method: z.string(),
|
|
49
|
+
args: z.array(z.unknown()).readonly().optional(),
|
|
50
|
+
});
|
|
51
|
+
// ── Reverse-RPC (server → host), validated by the reference client ───────────
|
|
52
|
+
export const ClientRequestApprovalParamsSchema = z.object({
|
|
53
|
+
...sessionRef,
|
|
54
|
+
request: z.looseObject({ toolCallId: z.string(), toolName: z.string(), approvalRule: z.string() }),
|
|
55
|
+
});
|
|
56
|
+
export const ClientRequestQuestionParamsSchema = z.object({
|
|
57
|
+
...sessionRef,
|
|
58
|
+
request: z.looseObject({
|
|
59
|
+
turnId: z.string(),
|
|
60
|
+
toolCallId: z.string(),
|
|
61
|
+
questions: z.custom((value) => Array.isArray(value), "questions must be an array"),
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
export const ClientCancelRequestParamsSchema = z.object({ id: z.union([z.string(), z.number()]) });
|
|
65
|
+
/** Parse `params` against a method's schema; a miss is `InvalidParams` naming the field. */
|
|
66
|
+
export function parseParams(schema, params) {
|
|
67
|
+
const result = schema.safeParse(params ?? {});
|
|
68
|
+
if (result.success)
|
|
69
|
+
return result.data;
|
|
70
|
+
throw new RpcError(ErrorCode.InvalidParams, describeIssues(result.error.issues));
|
|
71
|
+
}
|
|
72
|
+
function describeIssues(issues) {
|
|
73
|
+
return issues
|
|
74
|
+
.map((issue) => {
|
|
75
|
+
const path = issue.path.map(String).join(".");
|
|
76
|
+
return path.length > 0 ? `${path}: ${issue.message}` : issue.message;
|
|
77
|
+
})
|
|
78
|
+
.join("; ");
|
|
79
|
+
}
|
|
80
|
+
export const INVOKABLE = new Set(INVOKABLE_METHODS);
|
|
81
|
+
//# sourceMappingURL=params.js.map
|
|
@@ -194,11 +194,17 @@ export interface SessionInvokeResult {
|
|
|
194
194
|
readonly value: unknown;
|
|
195
195
|
}
|
|
196
196
|
/**
|
|
197
|
-
* The whitelist of
|
|
198
|
-
*
|
|
199
|
-
*
|
|
197
|
+
* The whitelist of operations reachable through `session/invoke`.
|
|
198
|
+
*
|
|
199
|
+
* `"<service>.<op>"` names an operation on one of the session's capability services
|
|
200
|
+
* (`session.goal.create`); a bare name is a method on the session itself. Run-driving
|
|
201
|
+
* (prompt/steer/follow_up/cancel), lifecycle (close) and handler registration are deliberately
|
|
202
|
+
* excluded — they are first-class methods or host-side concerns.
|
|
203
|
+
*
|
|
204
|
+
* This list stays explicit, one entry per reachable operation: it is a security boundary, so a
|
|
205
|
+
* new capability operation should have to be named here to become remotely callable.
|
|
200
206
|
*/
|
|
201
|
-
export declare const INVOKABLE_METHODS: readonly ["setModel", "setThinking", "setPermissionMode", "
|
|
207
|
+
export declare const INVOKABLE_METHODS: readonly ["setModel", "setThinking", "setPermissionMode", "goal.create", "goal.snapshot", "goal.pause", "goal.resume", "goal.cancel", "goal.changeBudget", "plan.setEnabled", "plan.data", "compaction.request", "compaction.pending", "compaction.cancel", "skills.listSkills", "skills.activateSkill", "plugins.summaries", "plugins.info", "plugins.installSummary", "plugins.setEnabled", "plugins.setMcpServerEnabled", "plugins.remove", "plugins.reload", "background.list", "background.readOutput", "background.readOutputDelta", "background.stop", "background.detach", "background.listSubagents", "background.reconcileSubagents", "background.listWorkflows", "background.getWorkflow", "runCommand", "getRecords", "getAuditRecords", "getContextBreakdown", "mcp.list", "mcp.listTools", "mcp.reconnect"];
|
|
202
208
|
export type InvokableMethod = (typeof INVOKABLE_METHODS)[number];
|
|
203
209
|
/** `session/event` — an AgentEvent verbatim (already carries `sessionId` + `address`). */
|
|
204
210
|
export type SessionEventParams = AgentEvent;
|
|
@@ -42,59 +42,67 @@ export const ErrorCode = {
|
|
|
42
42
|
RequestCancelled: -32011,
|
|
43
43
|
};
|
|
44
44
|
/**
|
|
45
|
-
* The whitelist of
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* The whitelist of operations reachable through `session/invoke`.
|
|
46
|
+
*
|
|
47
|
+
* `"<service>.<op>"` names an operation on one of the session's capability services
|
|
48
|
+
* (`session.goal.create`); a bare name is a method on the session itself. Run-driving
|
|
49
|
+
* (prompt/steer/follow_up/cancel), lifecycle (close) and handler registration are deliberately
|
|
50
|
+
* excluded — they are first-class methods or host-side concerns.
|
|
51
|
+
*
|
|
52
|
+
* This list stays explicit, one entry per reachable operation: it is a security boundary, so a
|
|
53
|
+
* new capability operation should have to be named here to become remotely callable.
|
|
48
54
|
*/
|
|
49
55
|
export const INVOKABLE_METHODS = [
|
|
50
|
-
// runtime settings
|
|
56
|
+
// runtime settings — on the session itself
|
|
51
57
|
"setModel",
|
|
52
58
|
"setThinking",
|
|
53
59
|
"setPermissionMode",
|
|
54
60
|
// goal
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
+
"goal.create",
|
|
62
|
+
"goal.snapshot",
|
|
63
|
+
"goal.pause",
|
|
64
|
+
"goal.resume",
|
|
65
|
+
"goal.cancel",
|
|
66
|
+
"goal.changeBudget",
|
|
61
67
|
// plan
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"clearPlan",
|
|
68
|
+
"plan.setEnabled",
|
|
69
|
+
"plan.data",
|
|
65
70
|
// compaction
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
71
|
+
"compaction.request",
|
|
72
|
+
"compaction.pending",
|
|
73
|
+
"compaction.cancel",
|
|
69
74
|
// skills
|
|
70
|
-
"listSkills",
|
|
71
|
-
"activateSkill",
|
|
75
|
+
"skills.listSkills",
|
|
76
|
+
"skills.activateSkill",
|
|
72
77
|
// plugins
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
// background tasks
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
78
|
+
"plugins.summaries",
|
|
79
|
+
"plugins.info",
|
|
80
|
+
"plugins.installSummary",
|
|
81
|
+
"plugins.setEnabled",
|
|
82
|
+
"plugins.setMcpServerEnabled",
|
|
83
|
+
"plugins.remove",
|
|
84
|
+
"plugins.reload",
|
|
85
|
+
// background tasks, subagents and workflow runs — one ledger, three views
|
|
86
|
+
"background.list",
|
|
87
|
+
"background.readOutput",
|
|
88
|
+
"background.readOutputDelta",
|
|
89
|
+
"background.stop",
|
|
90
|
+
"background.detach",
|
|
91
|
+
"background.listSubagents",
|
|
92
|
+
"background.reconcileSubagents",
|
|
93
|
+
"background.listWorkflows",
|
|
94
|
+
"background.getWorkflow",
|
|
86
95
|
// slash commands — the serializable control surface for extensions (cron et al.):
|
|
87
96
|
// per-feature cron RPCs are gone with the capability, /cron rides this instead.
|
|
88
97
|
"runCommand",
|
|
89
|
-
// conversation log
|
|
98
|
+
// conversation log, and the audit shard beside it
|
|
90
99
|
"getRecords",
|
|
91
|
-
|
|
92
|
-
"listSubagents",
|
|
93
|
-
"reconcileSubagents",
|
|
100
|
+
"getAuditRecords",
|
|
94
101
|
// context introspection
|
|
95
102
|
"getContextBreakdown",
|
|
96
103
|
// mcp
|
|
97
|
-
"
|
|
98
|
-
"
|
|
104
|
+
"mcp.list",
|
|
105
|
+
"mcp.listTools",
|
|
106
|
+
"mcp.reconnect",
|
|
99
107
|
];
|
|
100
108
|
//# sourceMappingURL=protocol.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { errorToBody, failure, notification, request as makeRequest, RpcError, success, toWireSafe, } from "./codec.js";
|
|
2
2
|
import { ErrorCode, INVOKABLE_METHODS, Method, PROTOCOL_VERSION, } from "./protocol.js";
|
|
3
|
-
|
|
3
|
+
import { INVOKABLE, InitializeParamsSchema, parseParams, SessionCancelParamsSchema, SessionCloseParamsSchema, SessionFollowUpParamsSchema, SessionForkParamsSchema, SessionInvokeParamsSchema, SessionNewParamsSchema, SessionPromptParamsSchema, SessionRespondParamsSchema, SessionResumeParamsSchema, SessionSnapshotParamsSchema, SessionSteerParamsSchema, } from "./params.js";
|
|
4
4
|
export class AppServer {
|
|
5
5
|
harness;
|
|
6
6
|
transport;
|
|
@@ -73,43 +73,41 @@ export class AppServer {
|
|
|
73
73
|
}
|
|
74
74
|
async route(method, params) {
|
|
75
75
|
if (method === Method.Initialize)
|
|
76
|
-
return this.onInitialize(params);
|
|
76
|
+
return this.onInitialize(parseParams(InitializeParamsSchema, params));
|
|
77
77
|
if (!this.initialized)
|
|
78
78
|
throw new RpcError(ErrorCode.NotInitialized, "call initialize first");
|
|
79
|
+
// Method lookup before params: an unknown method is `MethodNotFound` whatever it carries.
|
|
79
80
|
switch (method) {
|
|
80
81
|
case Method.SessionNew:
|
|
81
|
-
return this.onSessionNew((params
|
|
82
|
+
return this.onSessionNew(parseParams(SessionNewParamsSchema, params));
|
|
82
83
|
case Method.SessionResume:
|
|
83
|
-
return this.onSessionResume(params);
|
|
84
|
+
return this.onSessionResume(parseParams(SessionResumeParamsSchema, params));
|
|
84
85
|
case Method.SessionFork:
|
|
85
|
-
return this.onSessionFork(params);
|
|
86
|
+
return this.onSessionFork(parseParams(SessionForkParamsSchema, params));
|
|
86
87
|
case Method.SessionList:
|
|
87
88
|
return this.harness.listSessions();
|
|
88
89
|
case Method.SessionClose:
|
|
89
|
-
return this.onSessionClose(params);
|
|
90
|
+
return this.onSessionClose(parseParams(SessionCloseParamsSchema, params));
|
|
90
91
|
case Method.SessionPrompt:
|
|
91
|
-
return this.onSessionPrompt(params);
|
|
92
|
+
return this.onSessionPrompt(parseParams(SessionPromptParamsSchema, params));
|
|
92
93
|
case Method.SessionSnapshot:
|
|
93
|
-
return this.onSessionSnapshot(params);
|
|
94
|
+
return this.onSessionSnapshot(parseParams(SessionSnapshotParamsSchema, params));
|
|
94
95
|
case Method.SessionRespond:
|
|
95
|
-
return this.onSessionRespond(params);
|
|
96
|
+
return this.onSessionRespond(parseParams(SessionRespondParamsSchema, params));
|
|
96
97
|
case Method.SessionSteer:
|
|
97
|
-
return this.onSessionSteer(params);
|
|
98
|
+
return this.onSessionSteer(parseParams(SessionSteerParamsSchema, params));
|
|
98
99
|
case Method.SessionFollowUp:
|
|
99
|
-
return this.onSessionFollowUp(params);
|
|
100
|
+
return this.onSessionFollowUp(parseParams(SessionFollowUpParamsSchema, params));
|
|
100
101
|
case Method.SessionCancel:
|
|
101
|
-
return this.onSessionCancel(params);
|
|
102
|
+
return this.onSessionCancel(parseParams(SessionCancelParamsSchema, params));
|
|
102
103
|
case Method.SessionInvoke:
|
|
103
|
-
return this.onSessionInvoke(params);
|
|
104
|
+
return this.onSessionInvoke(parseParams(SessionInvokeParamsSchema, params));
|
|
104
105
|
default:
|
|
105
106
|
throw new RpcError(ErrorCode.MethodNotFound, `unknown method: ${method}`);
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
// ── Handshake ─────────────────────────────────────────────────────────────
|
|
109
110
|
onInitialize(params) {
|
|
110
|
-
if (typeof params?.protocolVersion !== "number") {
|
|
111
|
-
throw new RpcError(ErrorCode.InvalidParams, "protocolVersion (number) required");
|
|
112
|
-
}
|
|
113
111
|
this.clientApproval = params.clientCapabilities?.approval ?? false;
|
|
114
112
|
this.clientQuestion = params.clientCapabilities?.question ?? false;
|
|
115
113
|
this.initialized = true;
|
|
@@ -132,19 +130,19 @@ export class AppServer {
|
|
|
132
130
|
return { sessionId: session.id, workDir: session.workDir };
|
|
133
131
|
}
|
|
134
132
|
async onSessionResume(params) {
|
|
135
|
-
const session = await this.harness.resumeSession(
|
|
133
|
+
const session = await this.harness.resumeSession(params.sessionId);
|
|
136
134
|
this.wireSession(session);
|
|
137
135
|
return { sessionId: session.id, workDir: session.workDir };
|
|
138
136
|
}
|
|
139
137
|
async onSessionFork(params) {
|
|
140
|
-
const session = await this.harness.forkSession(
|
|
138
|
+
const session = await this.harness.forkSession(params.sessionId, {
|
|
141
139
|
...(params.title !== undefined ? { title: params.title } : {}),
|
|
142
140
|
});
|
|
143
141
|
this.wireSession(session);
|
|
144
142
|
return { sessionId: session.id, workDir: session.workDir };
|
|
145
143
|
}
|
|
146
144
|
async onSessionClose(params) {
|
|
147
|
-
const id =
|
|
145
|
+
const id = params.sessionId;
|
|
148
146
|
this.wired.get(id)?.unsubscribe();
|
|
149
147
|
this.wired.delete(id);
|
|
150
148
|
this.rejectReverseForSession(id, "session closed");
|
|
@@ -176,12 +174,12 @@ export class AppServer {
|
|
|
176
174
|
}
|
|
177
175
|
onSessionSteer(params) {
|
|
178
176
|
const session = this.requireSession(params);
|
|
179
|
-
return { accepted: true, steerId: session.steer(params.text) };
|
|
177
|
+
return { accepted: true, steerId: session.steer(params.text).steerId };
|
|
180
178
|
}
|
|
181
179
|
onSessionFollowUp(params) {
|
|
182
180
|
const session = this.requireSession(params);
|
|
183
|
-
const
|
|
184
|
-
return
|
|
181
|
+
const receipt = session.followUp(params.text);
|
|
182
|
+
return receipt === null ? { accepted: false } : { accepted: true, steerId: receipt.steerId };
|
|
185
183
|
}
|
|
186
184
|
onSessionCancel(params) {
|
|
187
185
|
const session = this.requireSession(params);
|
|
@@ -192,15 +190,34 @@ export class AppServer {
|
|
|
192
190
|
}
|
|
193
191
|
async onSessionInvoke(params) {
|
|
194
192
|
const session = this.requireSession(params);
|
|
195
|
-
if (
|
|
196
|
-
throw new RpcError(ErrorCode.MethodNotFound, `not invokable: ${
|
|
193
|
+
if (!INVOKABLE.has(params.method)) {
|
|
194
|
+
throw new RpcError(ErrorCode.MethodNotFound, `not invokable: ${params.method}`);
|
|
195
|
+
}
|
|
196
|
+
// `"<service>.<op>"` resolves through the session's capability accessor; a bare name is a
|
|
197
|
+
// method on the session. Reading a REQUIRE accessor throws when that capability is not open,
|
|
198
|
+
// which is a legitimate answer for a remote caller — reported as "unavailable", not a crash.
|
|
199
|
+
const dot = params.method.indexOf(".");
|
|
200
|
+
let target = session;
|
|
201
|
+
let name = params.method;
|
|
202
|
+
if (dot > 0) {
|
|
203
|
+
const service = params.method.slice(0, dot);
|
|
204
|
+
name = params.method.slice(dot + 1);
|
|
205
|
+
try {
|
|
206
|
+
target = session[service];
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
throw new RpcError(ErrorCode.MethodNotFound, `capability unavailable: ${service} (${error instanceof Error ? error.message : String(error)})`);
|
|
210
|
+
}
|
|
211
|
+
if (target === undefined || target === null) {
|
|
212
|
+
throw new RpcError(ErrorCode.MethodNotFound, `capability unavailable: ${service}`);
|
|
213
|
+
}
|
|
197
214
|
}
|
|
198
|
-
const fn =
|
|
215
|
+
const fn = target[name];
|
|
199
216
|
if (typeof fn !== "function") {
|
|
200
217
|
throw new RpcError(ErrorCode.MethodNotFound, `not a method: ${params.method}`);
|
|
201
218
|
}
|
|
202
|
-
const args =
|
|
203
|
-
const value = await fn.apply(
|
|
219
|
+
const args = [...(params.args ?? [])];
|
|
220
|
+
const value = await fn.apply(target, args);
|
|
204
221
|
return { value: toWireSafe(value) };
|
|
205
222
|
}
|
|
206
223
|
// ── Per-session wiring: events out, reverse-RPC for approvals/questions ──────
|
|
@@ -258,16 +275,10 @@ export class AppServer {
|
|
|
258
275
|
}
|
|
259
276
|
}
|
|
260
277
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
261
|
-
requireId(ref) {
|
|
262
|
-
if (!ref || typeof ref.sessionId !== "string")
|
|
263
|
-
throw new RpcError(ErrorCode.InvalidParams, "sessionId required");
|
|
264
|
-
return ref.sessionId;
|
|
265
|
-
}
|
|
266
278
|
requireSession(ref) {
|
|
267
|
-
const
|
|
268
|
-
const session = this.harness.getSession(id);
|
|
279
|
+
const session = this.harness.getSession(ref.sessionId);
|
|
269
280
|
if (!session)
|
|
270
|
-
throw new RpcError(ErrorCode.SessionNotFound, `session not found: ${
|
|
281
|
+
throw new RpcError(ErrorCode.SessionNotFound, `session not found: ${ref.sessionId}`);
|
|
271
282
|
return session;
|
|
272
283
|
}
|
|
273
284
|
}
|
package/dist/core.d.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* `import { Runner, defineAgent, defineModel } from 'operon-agents/core'`
|
|
5
5
|
*
|
|
6
6
|
* Everything the core engine exposes: Runner / Session / Agent / capabilities /
|
|
7
|
-
*
|
|
8
|
-
* no-filesystem agent (a `
|
|
7
|
+
* environments / tools. Pass no environment and omit file tools to run a stateless,
|
|
8
|
+
* no-filesystem agent (a `NullEnvironment` refuses any stray I/O). For the
|
|
9
9
|
* batteries-included facade, import from `operon-agents` instead.
|
|
10
10
|
*/
|
|
11
11
|
export * from "operon-agents-core";
|
package/dist/core.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* `import { Runner, defineAgent, defineModel } from 'operon-agents/core'`
|
|
5
5
|
*
|
|
6
6
|
* Everything the core engine exposes: Runner / Session / Agent / capabilities /
|
|
7
|
-
*
|
|
8
|
-
* no-filesystem agent (a `
|
|
7
|
+
* environments / tools. Pass no environment and omit file tools to run a stateless,
|
|
8
|
+
* no-filesystem agent (a `NullEnvironment` refuses any stray I/O). For the
|
|
9
9
|
* batteries-included facade, import from `operon-agents` instead.
|
|
10
10
|
*/
|
|
11
11
|
export * from "operon-agents-core";
|
package/dist/cron/cron-expr.js
CHANGED
|
@@ -52,7 +52,7 @@ function parseField(field, min, max, name) {
|
|
|
52
52
|
return out;
|
|
53
53
|
}
|
|
54
54
|
// Cron numeric fields are digit-only; `Number(...)` would otherwise accept '', '1e1',
|
|
55
|
-
// '0x10', '+5', ' 3 ' — none valid cron. Gate before conversion to
|
|
55
|
+
// '0x10', '+5', ' 3 ' — none valid cron. Gate before conversion to surface typos.
|
|
56
56
|
const DIGIT_ONLY = /^\d+$/;
|
|
57
57
|
function parseCronInt(raw, name, role) {
|
|
58
58
|
if (!DIGIT_ONLY.test(raw)) {
|
package/dist/cron/extension.js
CHANGED
|
@@ -29,8 +29,8 @@ export function normalizeCronTaskInput(manager, input) {
|
|
|
29
29
|
export function cronExtension(options = {}) {
|
|
30
30
|
return {
|
|
31
31
|
id: "cron",
|
|
32
|
-
async
|
|
33
|
-
// One manager per session:
|
|
32
|
+
async session(api) {
|
|
33
|
+
// One manager per session: session() runs per session open, so scheduling state is
|
|
34
34
|
// session-scoped exactly as the capability's openSession was.
|
|
35
35
|
const manager = new CronManager(options);
|
|
36
36
|
manager.attach({
|