@plasm_lang/vercel-agent 0.3.63

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 (121) hide show
  1. package/README.md +235 -0
  2. package/agent/.plasm/archives/runs/runs/pr2d5c4a99707b4c19b650553d50229a1d600d28e3d98a9c58f18e5026cecc86ca.json +64 -0
  3. package/agent/.plasm/archives/runs/runs/pr2e0c0d8ad443c63c82da7435ee1a002b0e0fa718b640263c0a9d3e6e5944812f.json +64 -0
  4. package/agent/.plasm/archives/runs/runs/pr2faedb8354f40ee6d828e3af07b421fda9ccda973a4f7347fce3639f03a0a869.json +64 -0
  5. package/agent/.plasm/archives/runs/runs/pr586b47c55547b0702c572bce4255558b22dbe5e682d6359169577e0ea75fe98f.json +64 -0
  6. package/agent/.plasm/archives/runs/runs/pr76212356445e3b00fcf256835aaec18bac68576324b90d0be92d47f0b4a862a7.json +56 -0
  7. package/agent/.plasm/archives/runs/runs/pr9ec805d689e00db9270a9539858f2fb7216c24acbfea943d450e37b641149da1.json +64 -0
  8. package/agent/.plasm/archives/runs/runs/prc3c0c4ba2e28fc94ed6d37b6796e277a7997d9cb3184640d14c35c98bc6d136f.json +64 -0
  9. package/agent/.plasm/archives/runs/runs/prf04de32522f2fdcb17818907d91bccce7dcaecbd1259041cc448d447b6993244.json +64 -0
  10. package/agent/.plasm/archives/traces/traces/local/00000000000000000000000000000000/records.ndjson +1 -0
  11. package/agent/.plasm/archives/traces/traces/local/00000000000000000000000000000000/summary.json +13 -0
  12. package/agent/.plasm/discovery/manifest.json +126 -0
  13. package/agent/.plasm/sessions/TGlzdCBwcm9kdWN0cyBmcm9tIHRoZSBleGVjdXRlX3RpbnkgY2F0YWxvZw.json +44 -0
  14. package/agent/.plasm/sessions/TGlzdCBwcm9kdWN0cyBmcm9tIHRoZSBleGVjdXRlX3RpbnkgY2F0YWxvZw.teaching.tsv +23 -0
  15. package/agent/.plasm/sessions/bGlzdCBleGVjdXRlX3RpbnkgcHJvZHVjdHM.json +151 -0
  16. package/agent/.plasm/sessions/bGlzdCBleGVjdXRlX3RpbnkgcHJvZHVjdHM.teaching.tsv +131 -0
  17. package/agent/.plasm/sessions/bGlzdCBleGVjdXRlX3RpbnkgcHJvZHVjdHMgYXN5bmMgdHJhbnNwb3J0.json +44 -0
  18. package/agent/.plasm/sessions/bGlzdCBleGVjdXRlX3RpbnkgcHJvZHVjdHMgYXN5bmMgdHJhbnNwb3J0.teaching.tsv +23 -0
  19. package/agent/.plasm/stubs/.gitkeep +0 -0
  20. package/agent/.plasm/stubs/execute_tiny.ts +107 -0
  21. package/agent/agent.ts +52 -0
  22. package/agent/catalogs/README.md +15 -0
  23. package/agent/channels/.gitkeep +0 -0
  24. package/agent/channels/execute-tiny-webhook.ts +59 -0
  25. package/agent/channels/health.ts +16 -0
  26. package/agent/hooks/.gitkeep +0 -0
  27. package/agent/hooks/trace-log.ts +10 -0
  28. package/agent/instructions.md +25 -0
  29. package/agent/schedules/.gitkeep +0 -0
  30. package/agent/schedules/ping.ts +13 -0
  31. package/agent/skills/.gitkeep +0 -0
  32. package/agent/skills/plasm-authoring.md +8 -0
  33. package/agent/subagents/.gitkeep +0 -0
  34. package/agent/subagents/tiny/agent.ts +28 -0
  35. package/bin/plasm-agent.mjs +18 -0
  36. package/package.json +77 -0
  37. package/scripts/plasm-node.mjs +19 -0
  38. package/scripts/resolve-ts-extension.mjs +18 -0
  39. package/src/archive/adapters.ts +27 -0
  40. package/src/archive/index.ts +99 -0
  41. package/src/archive/local-run-archive.ts +90 -0
  42. package/src/archive/local-trace-archive.ts +91 -0
  43. package/src/archive/paths.ts +15 -0
  44. package/src/archive/postgres-kv-adapter.ts +72 -0
  45. package/src/archive/prod-archive-store.ts +143 -0
  46. package/src/archive/resolve-backend.ts +60 -0
  47. package/src/archive/run-id.ts +23 -0
  48. package/src/archive/types.ts +75 -0
  49. package/src/archive/vercel-blob-adapter.ts +21 -0
  50. package/src/archive/vercel-kv-adapter.ts +24 -0
  51. package/src/authoring/channel-dispatch.ts +44 -0
  52. package/src/authoring/context.ts +34 -0
  53. package/src/authoring/define-channel.ts +83 -0
  54. package/src/authoring/define-hook.ts +51 -0
  55. package/src/authoring/define-schedule.ts +64 -0
  56. package/src/authoring/define-skill.ts +38 -0
  57. package/src/authoring/hook-runner.ts +18 -0
  58. package/src/authoring/schedule-manager.ts +118 -0
  59. package/src/authoring/slot-loader.ts +253 -0
  60. package/src/authoring/subagent-loader.ts +121 -0
  61. package/src/catalog/loader.ts +71 -0
  62. package/src/cli/build.ts +54 -0
  63. package/src/cli/dev.ts +60 -0
  64. package/src/cli/info.ts +12 -0
  65. package/src/cli/init.ts +372 -0
  66. package/src/cli/link.ts +68 -0
  67. package/src/cli/project-root.ts +57 -0
  68. package/src/define-agent.ts +150 -0
  69. package/src/dev/client/ansi.ts +36 -0
  70. package/src/dev/client/http-session.ts +180 -0
  71. package/src/dev/client/repl.ts +92 -0
  72. package/src/dev/client/slash.ts +119 -0
  73. package/src/dev/dev-session.ts +153 -0
  74. package/src/dev/http.ts +29 -0
  75. package/src/dev/server.ts +147 -0
  76. package/src/dev/session-routes.ts +185 -0
  77. package/src/discovery/project-walker.ts +272 -0
  78. package/src/engine/connect-auth.ts +135 -0
  79. package/src/engine/create-host-transport.ts +7 -0
  80. package/src/engine/fixture-mock-transport.ts +54 -0
  81. package/src/engine/host-transport-bridge.ts +32 -0
  82. package/src/engine/host-transport.ts +84 -0
  83. package/src/engine/napi-binding.ts +265 -0
  84. package/src/evals/define-eval.ts +56 -0
  85. package/src/evals/run-eval.ts +136 -0
  86. package/src/gateway-model.ts +43 -0
  87. package/src/index.ts +296 -0
  88. package/src/instrumentation.ts +56 -0
  89. package/src/load-env.ts +63 -0
  90. package/src/operator/routes.ts +287 -0
  91. package/src/operator/types.ts +63 -0
  92. package/src/operator/ui-shell.ts +134 -0
  93. package/src/project-info.ts +229 -0
  94. package/src/runtime/agent-runtime.ts +469 -0
  95. package/src/runtime/compaction.ts +81 -0
  96. package/src/runtime/logical-session.ts +72 -0
  97. package/src/runtime/plasm-agent.ts +199 -0
  98. package/src/server/plasm-handler.ts +331 -0
  99. package/src/session-state.ts +135 -0
  100. package/src/state/define-state.ts +57 -0
  101. package/src/state/fs-state-adapter.ts +72 -0
  102. package/src/state/kv-state-adapter.ts +62 -0
  103. package/src/state/postgres-state-adapter.ts +116 -0
  104. package/src/stubs/capability-invoke-shape.ts +135 -0
  105. package/src/stubs/catalog-client.ts +170 -0
  106. package/src/stubs/catalog-hash.ts +11 -0
  107. package/src/stubs/catalog-introspection.ts +121 -0
  108. package/src/stubs/cgs-ts-types.ts +164 -0
  109. package/src/stubs/domain-parser.ts +203 -0
  110. package/src/stubs/generator.ts +390 -0
  111. package/src/stubs/input-type-to-ts.ts +233 -0
  112. package/src/stubs/plasm-value-emitter.ts +162 -0
  113. package/src/stubs/program-builder.ts +82 -0
  114. package/src/stubs/stub-symbols.ts +89 -0
  115. package/src/symbol-registry.ts +74 -0
  116. package/src/telemetry/plasm-spans.ts +83 -0
  117. package/src/tools/descriptions.ts +94 -0
  118. package/src/tools/format.ts +29 -0
  119. package/src/tools/harness-tools.ts +65 -0
  120. package/src/tools/plasm-tools.ts +104 -0
  121. package/src/workflow/world-bootstrap.ts +52 -0
@@ -0,0 +1,162 @@
1
+ import type { CapabilityIntrospectionJson } from "./catalog-introspection.js";
2
+ import type { CapabilityBinding } from "./stub-symbols.js";
3
+ import type { CapabilityInvokeShape } from "./capability-invoke-shape.js";
4
+ import { invokeBodyFields, searchFieldName } from "./capability-invoke-shape.js";
5
+
6
+ export interface EmissionField {
7
+ name: string;
8
+ required: boolean;
9
+ access: string;
10
+ kind: "literal" | "number" | "boolean" | "select";
11
+ }
12
+
13
+ export function emissionKindForField(
14
+ field: import("./catalog-introspection.js").InputFieldSchemaJson,
15
+ values: Record<string, import("./catalog-introspection.js").NamedValueSchemaJson>,
16
+ ): EmissionField["kind"] {
17
+ const nv = field.value_ref ? values[field.value_ref] : undefined;
18
+ const ft = nv?.field_type;
19
+ if (typeof ft === "string") {
20
+ if (ft === "integer" || ft === "number") return "number";
21
+ if (ft === "boolean") return "boolean";
22
+ if (ft === "select") return "select";
23
+ }
24
+ if (field.input_type?.type === "value") {
25
+ const inner = field.input_type.field_type;
26
+ if (inner === "integer" || inner === "number") return "number";
27
+ if (inner === "boolean") return "boolean";
28
+ }
29
+ return "literal";
30
+ }
31
+
32
+ export function buildEmissionFields(
33
+ cap: CapabilityIntrospectionJson,
34
+ catalogValues: Record<string, import("./catalog-introspection.js").NamedValueSchemaJson>,
35
+ shape: CapabilityInvokeShape,
36
+ entityIdField: string,
37
+ inputVar: string,
38
+ ): EmissionField[] {
39
+ const body = invokeBodyFields(cap, shape, entityIdField);
40
+ return body.map((f) => ({
41
+ name: f.name,
42
+ required: !!f.required,
43
+ access: `${inputVar}.${f.name}`,
44
+ kind: emissionKindForField(f, catalogValues),
45
+ }));
46
+ }
47
+
48
+ export interface DottedArgCodegen {
49
+ key: string;
50
+ valueExpr: string;
51
+ kind: "literal" | "number" | "boolean" | "select";
52
+ optional: boolean;
53
+ }
54
+
55
+ export function dottedArgsCodegen(
56
+ cap: CapabilityIntrospectionJson,
57
+ catalogValues: Record<string, import("./catalog-introspection.js").NamedValueSchemaJson>,
58
+ shape: CapabilityInvokeShape,
59
+ entityIdField: string,
60
+ inputVar: string,
61
+ ): DottedArgCodegen[] {
62
+ return buildEmissionFields(cap, catalogValues, shape, entityIdField, inputVar).map((f) => ({
63
+ key: f.name,
64
+ valueExpr: f.access,
65
+ kind: f.kind,
66
+ optional: !f.required,
67
+ }));
68
+ }
69
+
70
+ function renderBuildDottedArgsCall(args: DottedArgCodegen[]): string {
71
+ if (!args.length) return '""';
72
+ const entries = args
73
+ .map(
74
+ (a) =>
75
+ `{ key: ${JSON.stringify(a.key)}, value: ${a.valueExpr}, kind: ${JSON.stringify(a.kind)}${a.optional ? ", optional: true" : ""} }`,
76
+ )
77
+ .join(", ");
78
+ return `buildDottedArgs([${entries}])`;
79
+ }
80
+
81
+ /** Generated TypeScript statements that set `program` (uses `buildDottedArgs`, `plasmLiteral`, …). */
82
+ export function renderProgramStatements(
83
+ binding: CapabilityBinding,
84
+ cap: CapabilityIntrospectionJson,
85
+ catalogValues: Record<string, import("./catalog-introspection.js").NamedValueSchemaJson>,
86
+ shape: CapabilityInvokeShape,
87
+ entityIdField: string,
88
+ inputVar: string,
89
+ ): string {
90
+ const sym = binding.entitySymbol;
91
+ const dotted = renderBuildDottedArgsCall(
92
+ dottedArgsCodegen(cap, catalogValues, shape, entityIdField, inputVar),
93
+ );
94
+
95
+ switch (shape) {
96
+ case "RootQuery":
97
+ return `const program = ${JSON.stringify(sym)};`;
98
+ case "ScopedQuery": {
99
+ const args = dottedArgsCodegen(cap, catalogValues, shape, entityIdField, inputVar);
100
+ const dottedCall = renderBuildDottedArgsCall(args);
101
+ return `const preds = ${dottedCall};
102
+ const program = preds ? \`${sym}{\${preds}}\` : ${JSON.stringify(sym)};`;
103
+ }
104
+ case "GetById":
105
+ return `const program = \`${sym}(\${plasmLiteral(${inputVar}.${entityIdField})})\`;`;
106
+ case "SearchText": {
107
+ const q = searchFieldName(cap) ?? "q";
108
+ return `const program = \`${sym}~\${plasmLiteral(${inputVar}.${q})}\`;`;
109
+ }
110
+ case "SearchFiltered": {
111
+ const q = searchFieldName(cap) ?? "q";
112
+ const args = dottedArgsCodegen(cap, catalogValues, shape, entityIdField, inputVar);
113
+ const dottedCall = renderBuildDottedArgsCall(args);
114
+ return `const filterArgs = ${dottedCall};
115
+ const program = filterArgs
116
+ ? \`${sym}~\${plasmLiteral(${inputVar}.${q})}{\${filterArgs}}\`
117
+ : \`${sym}~\${plasmLiteral(${inputVar}.${q})}\`;`;
118
+ }
119
+ case "RootCreate":
120
+ return `const args = ${dotted};
121
+ const program = \`${sym}.create(\${args})\`;`;
122
+ case "ScopedUpdate":
123
+ return `const args = ${dotted};
124
+ const program = \`${sym}(\${plasmLiteral(${inputVar}.${entityIdField})}).update(\${args})\`;`;
125
+ case "ScopedAction": {
126
+ const wire = binding.methodWire;
127
+ const method = binding.methodSymbol ? `.${binding.methodSymbol}` : `.${wire}`;
128
+ return `const program = \`${sym}(\${plasmLiteral(${inputVar}.${entityIdField})})${method}()\`;`;
129
+ }
130
+ case "ScopedDelete":
131
+ return `const program = \`${sym}(\${plasmLiteral(${inputVar}.${entityIdField})}).delete()\`;`;
132
+ case "MethodUnion": {
133
+ const m = binding.methodSymbol ?? "m1";
134
+ return `const program = \`${sym}.${m}(\${plasmLiteral("v1")})\`;`;
135
+ }
136
+ case "MethodObject": {
137
+ const m = binding.methodSymbol ?? binding.methodWire;
138
+ return `const args = ${dotted};
139
+ const program = \`${sym}.${m}(\${args})\`;`;
140
+ }
141
+ default:
142
+ return `const program = ${JSON.stringify(sym)};`;
143
+ }
144
+ }
145
+
146
+ /** @deprecated Prefer {@link renderProgramStatements} — avoids nested-template syntax errors. */
147
+ export function renderProgramExpr(
148
+ binding: CapabilityBinding,
149
+ cap: CapabilityIntrospectionJson,
150
+ catalogValues: Record<string, import("./catalog-introspection.js").NamedValueSchemaJson>,
151
+ shape: CapabilityInvokeShape,
152
+ entityIdField: string,
153
+ inputVar: string,
154
+ ): string {
155
+ void binding;
156
+ void cap;
157
+ void catalogValues;
158
+ void shape;
159
+ void entityIdField;
160
+ void inputVar;
161
+ return '""';
162
+ }
@@ -0,0 +1,82 @@
1
+ import {
2
+ createEngine,
3
+ type DryRunResult,
4
+ type HostTransportFn,
5
+ type PlasmEngine,
6
+ } from "../engine/napi-binding.js";
7
+ import { createDefaultHostTransport } from "../engine/host-transport.js";
8
+
9
+ export interface ProgramBuilderOptions {
10
+ entryId: string;
11
+ cgsHash: string;
12
+ engine?: PlasmEngine;
13
+ logicalSessionRef?: string;
14
+ agentSessionId?: string;
15
+ catalogRoot?: string;
16
+ /** Entity names to expose before dry-run / live execute. */
17
+ stubEntities?: string[];
18
+ }
19
+
20
+ export interface ProgramBuilderProvenance {
21
+ entryId: string;
22
+ catalogCgsHash: string;
23
+ logicalSessionRef?: string;
24
+ agentSessionId?: string;
25
+ }
26
+
27
+ export interface ProgramBuilder extends ProgramBuilderProvenance {
28
+ readonly catalogRoot?: string;
29
+ readonly stubEntities?: string[];
30
+ readonly engine?: PlasmEngine;
31
+ readonly programSource: string;
32
+ program(source: string): ProgramBuilder;
33
+ dryRun(): Promise<DryRunResult>;
34
+ run(planCommitRef: string, transport?: HostTransportFn): Promise<unknown>;
35
+ }
36
+
37
+ function cloneBuilder(
38
+ opts: ProgramBuilderOptions,
39
+ programSource: string,
40
+ ): ProgramBuilder {
41
+ const engine = opts.engine ?? createEngine();
42
+ const provenance: ProgramBuilderProvenance = {
43
+ entryId: opts.entryId,
44
+ catalogCgsHash: opts.cgsHash,
45
+ logicalSessionRef: opts.logicalSessionRef,
46
+ agentSessionId: opts.agentSessionId,
47
+ };
48
+
49
+ return {
50
+ ...provenance,
51
+ catalogRoot: opts.catalogRoot,
52
+ stubEntities: opts.stubEntities,
53
+ engine,
54
+ programSource,
55
+ program(source: string) {
56
+ return cloneBuilder(opts, source);
57
+ },
58
+ async dryRun() {
59
+ return engine.dryRun(programSource);
60
+ },
61
+ async run(planCommitRef: string, transport?: HostTransportFn) {
62
+ const hostTransport = transport ?? createDefaultHostTransport();
63
+ if (typeof engine.runPlanLive === "function") {
64
+ const live = await engine.runPlanLive(planCommitRef, hostTransport);
65
+ if (!live.ok) {
66
+ throw new Error(live.message);
67
+ }
68
+ return live;
69
+ }
70
+ const validation = await engine.runPlan(planCommitRef);
71
+ if (!validation.ok) {
72
+ throw new Error(validation.message);
73
+ }
74
+ return validation;
75
+ },
76
+ };
77
+ }
78
+
79
+ /** Typed program builder — dryRun/run delegate to NAPI PlasmEngine. */
80
+ export function createProgramBuilder(opts: ProgramBuilderOptions): ProgramBuilder {
81
+ return cloneBuilder(opts, "");
82
+ }
@@ -0,0 +1,89 @@
1
+ import type { CapabilityIntrospectionJson, CatalogIntrospectionJson } from "./catalog-introspection.js";
2
+ import {
3
+ classifyInvokeShape,
4
+ type CapabilityInvokeShape,
5
+ } from "./capability-invoke-shape.js";
6
+
7
+ export interface EntitySymbolBinding {
8
+ entity: string;
9
+ symbol: string;
10
+ }
11
+
12
+ export interface CapabilityBinding {
13
+ capability: string;
14
+ entitySymbol: string;
15
+ methodSymbol?: string;
16
+ methodWire: string;
17
+ invokeShape: CapabilityInvokeShape;
18
+ }
19
+
20
+ /** Entities with capabilities, stable lexicographic order → `e1`…`eN`. */
21
+ export function stubEntityNames(catalog: CatalogIntrospectionJson): string[] {
22
+ return catalog.entities
23
+ .filter((e) => catalog.capabilities.some((c) => c.entity === e.name))
24
+ .map((e) => e.name)
25
+ .sort((a, b) => a.localeCompare(b));
26
+ }
27
+
28
+ /** Deterministic `e#` from catalog entity order (no teaching session / intent). */
29
+ export function assignEntitySymbols(entityNames: string[]): Map<string, EntitySymbolBinding> {
30
+ const sorted = [...entityNames].sort((a, b) => a.localeCompare(b));
31
+ const out = new Map<string, EntitySymbolBinding>();
32
+ sorted.forEach((entity, index) => {
33
+ out.set(entity, { entity, symbol: `e${index + 1}` });
34
+ });
35
+ return out;
36
+ }
37
+
38
+ function methodSymbolForCapability(
39
+ cap: CapabilityIntrospectionJson,
40
+ entityCaps: CapabilityIntrospectionJson[],
41
+ ): string | undefined {
42
+ const shape = classifyInvokeShape(cap);
43
+ if (shape !== "MethodObject" && shape !== "MethodUnion") {
44
+ return undefined;
45
+ }
46
+ const dotted = entityCaps
47
+ .filter((c) => {
48
+ const s = classifyInvokeShape(c);
49
+ return s === "MethodObject" || s === "MethodUnion";
50
+ })
51
+ .sort((a, b) => a.name.localeCompare(b.name));
52
+ const index = dotted.findIndex((c) => c.name === cap.name);
53
+ if (index < 0) return undefined;
54
+ return `m${index + 1}`;
55
+ }
56
+
57
+ /** Deterministic invoke bindings from introspection only (program API, not agent teaching). */
58
+ export function assignCapabilityBindings(
59
+ catalog: CatalogIntrospectionJson,
60
+ ): Map<string, CapabilityBinding> {
61
+ const entityNames = stubEntityNames(catalog);
62
+ const entitySymbols = assignEntitySymbols(entityNames);
63
+ const capsByEntity = new Map<string, CapabilityIntrospectionJson[]>();
64
+ for (const cap of catalog.capabilities) {
65
+ const list = capsByEntity.get(cap.entity) ?? [];
66
+ list.push(cap);
67
+ capsByEntity.set(cap.entity, list);
68
+ }
69
+
70
+ const out = new Map<string, CapabilityBinding>();
71
+ for (const cap of catalog.capabilities) {
72
+ const entityBinding = entitySymbols.get(cap.entity);
73
+ if (!entityBinding) continue;
74
+ const entityCaps = capsByEntity.get(cap.entity) ?? [];
75
+ const invokeShape = classifyInvokeShape(cap);
76
+ out.set(cap.name, {
77
+ capability: cap.name,
78
+ entitySymbol: entityBinding.symbol,
79
+ methodSymbol: methodSymbolForCapability(cap, entityCaps),
80
+ methodWire: cap.invoke_wire_name,
81
+ invokeShape,
82
+ });
83
+ }
84
+ return out;
85
+ }
86
+
87
+ export function capabilityReturnTypeName(entityName: string): string {
88
+ return entityName.replace(/[^a-zA-Z0-9_]/g, "_");
89
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Agent-global append-only symbol registry (e#/m#/p#/r#).
3
+ * Teaching waves disclose symbols; this mirror supports operator UI and persistence.
4
+ */
5
+
6
+ export type SymbolKind = "entity" | "method" | "param" | "relation";
7
+
8
+ export interface SymbolBinding {
9
+ symbol: string;
10
+ kind: SymbolKind;
11
+ entryId: string;
12
+ wire: string;
13
+ entity?: string;
14
+ tombstoned?: boolean;
15
+ }
16
+
17
+ export interface SymbolRegistrySnapshot {
18
+ bindings: SymbolBinding[];
19
+ nextEntity: number;
20
+ nextMethod: number;
21
+ nextParam: number;
22
+ nextRelation: number;
23
+ }
24
+
25
+ export class SymbolRegistry {
26
+ private bindings: SymbolBinding[] = [];
27
+ private counters = { entity: 1, method: 1, param: 1, relation: 1 };
28
+
29
+ mint(kind: SymbolKind): string {
30
+ const prefix =
31
+ kind === "entity"
32
+ ? "e"
33
+ : kind === "method"
34
+ ? "m"
35
+ : kind === "param"
36
+ ? "p"
37
+ : "r";
38
+ const key = `${prefix}${this.counters[kind]}` as keyof typeof this.counters;
39
+ this.counters[kind] += 1;
40
+ return `${prefix}${this.counters[kind] - 1}`;
41
+ }
42
+
43
+ bind(binding: Omit<SymbolBinding, "symbol"> & { symbol?: string }): SymbolBinding {
44
+ const symbol = binding.symbol ?? this.mint(binding.kind);
45
+ const row: SymbolBinding = { ...binding, symbol };
46
+ this.bindings.push(row);
47
+ return row;
48
+ }
49
+
50
+ tombstone(symbol: string): void {
51
+ const row = this.bindings.find((b) => b.symbol === symbol);
52
+ if (row) row.tombstoned = true;
53
+ }
54
+
55
+ snapshot(): SymbolRegistrySnapshot {
56
+ return {
57
+ bindings: [...this.bindings],
58
+ nextEntity: this.counters.entity,
59
+ nextMethod: this.counters.method,
60
+ nextParam: this.counters.param,
61
+ nextRelation: this.counters.relation,
62
+ };
63
+ }
64
+
65
+ restore(snapshot: SymbolRegistrySnapshot): void {
66
+ this.bindings = [...snapshot.bindings];
67
+ this.counters = {
68
+ entity: snapshot.nextEntity,
69
+ method: snapshot.nextMethod,
70
+ param: snapshot.nextParam,
71
+ relation: snapshot.nextRelation,
72
+ };
73
+ }
74
+ }
@@ -0,0 +1,83 @@
1
+ import { SpanStatusCode, trace, type Span } from "@opentelemetry/api";
2
+
3
+ import { PlasmSpanAttributes } from "../instrumentation.js";
4
+
5
+ export interface PlasmSpanContext {
6
+ sessionId?: string;
7
+ intent?: string;
8
+ logicalSessionRef?: string;
9
+ catalogCgsHash?: string;
10
+ entryId?: string;
11
+ planCommitRef?: string;
12
+ runId?: string;
13
+ toolName?: string;
14
+ transportHost?: string;
15
+ transportStatus?: number;
16
+ }
17
+
18
+ const tracer = trace.getTracer("plasm-agent");
19
+
20
+ function applyPlasmAttributes(span: Span, ctx: PlasmSpanContext): void {
21
+ if (ctx.sessionId) span.setAttribute(PlasmSpanAttributes.SESSION_ID, ctx.sessionId);
22
+ if (ctx.intent) span.setAttribute(PlasmSpanAttributes.INTENT, ctx.intent);
23
+ if (ctx.logicalSessionRef) {
24
+ span.setAttribute(PlasmSpanAttributes.LOGICAL_SESSION_REF, ctx.logicalSessionRef);
25
+ }
26
+ if (ctx.catalogCgsHash) {
27
+ span.setAttribute(PlasmSpanAttributes.CATALOG_CGS_HASH, ctx.catalogCgsHash);
28
+ }
29
+ if (ctx.planCommitRef) {
30
+ span.setAttribute(PlasmSpanAttributes.PLAN_COMMIT_REF, ctx.planCommitRef);
31
+ }
32
+ if (ctx.runId) span.setAttribute(PlasmSpanAttributes.RUN_ID, ctx.runId);
33
+ if (ctx.entryId) span.setAttribute(PlasmSpanAttributes.ENTRY_ID, ctx.entryId);
34
+ if (ctx.toolName) span.setAttribute(PlasmSpanAttributes.TOOL_NAME, ctx.toolName);
35
+ if (ctx.transportHost) {
36
+ span.setAttribute(PlasmSpanAttributes.TRANSPORT_HOST, ctx.transportHost);
37
+ }
38
+ if (ctx.transportStatus !== undefined) {
39
+ span.setAttribute(PlasmSpanAttributes.TRANSPORT_STATUS, ctx.transportStatus);
40
+ }
41
+ }
42
+
43
+ export async function withPlasmSpan<T>(
44
+ name: string,
45
+ ctx: PlasmSpanContext,
46
+ fn: (span: Span) => Promise<T>,
47
+ ): Promise<T> {
48
+ return tracer.startActiveSpan(name, async (span) => {
49
+ applyPlasmAttributes(span, ctx);
50
+ try {
51
+ const result = await fn(span);
52
+ span.setStatus({ code: SpanStatusCode.OK });
53
+ return result;
54
+ } catch (error) {
55
+ span.recordException(error as Error);
56
+ span.setStatus({ code: SpanStatusCode.ERROR });
57
+ throw error;
58
+ } finally {
59
+ span.end();
60
+ }
61
+ });
62
+ }
63
+
64
+ export const plasmSpans = {
65
+ toolDiscover: <T>(ctx: PlasmSpanContext, fn: (span: Span) => Promise<T>) =>
66
+ withPlasmSpan("tool.discover_capabilities", { ...ctx, toolName: "discover_capabilities" }, fn),
67
+
68
+ toolContext: <T>(ctx: PlasmSpanContext, fn: (span: Span) => Promise<T>) =>
69
+ withPlasmSpan("tool.plasm_context", { ...ctx, toolName: "plasm_context" }, fn),
70
+
71
+ dryRun: <T>(ctx: PlasmSpanContext, fn: (span: Span) => Promise<T>) =>
72
+ withPlasmSpan("plasm.dry_run", ctx, fn),
73
+
74
+ liveRun: <T>(ctx: PlasmSpanContext, fn: (span: Span) => Promise<T>) =>
75
+ withPlasmSpan("plasm.live_run", ctx, fn),
76
+
77
+ transportHttp: <T>(ctx: PlasmSpanContext, fn: (span: Span) => Promise<T>) =>
78
+ withPlasmSpan("plasm.transport.http", ctx, fn),
79
+ };
80
+
81
+ export function activeTraceId(): string | undefined {
82
+ return trace.getActiveSpan()?.spanContext().traceId;
83
+ }
@@ -0,0 +1,94 @@
1
+ /** MCP-aligned tool descriptions (from plasm-core prompt_render assets). */
2
+
3
+ export const DISCOVER_TOOL_DESCRIPTION = `Plasm is a source language. Pick catalogs/entities for one user goal — this tool does **not** produce program symbols. Tool order: optional \`discover_capabilities\` → \`plasm_context\` → \`plasm\` (dry-run) → \`plasm_run\` (live).
4
+ **Next:** copy TSV \`api\`/\`entity\` rows into one **\`plasm_context\`** **\`seeds\`** array on the same **\`intent\`**, then write **\`plasm.program\`** from teaching TSV (get \`e#(p#)\` vs search \`e#~"…"\` when exposed). Skip when you already know every \`api\`/\`entity\`. No alternate JSON discovery mode.`;
5
+
6
+ export const PLASM_CONTEXT_TOOL_DESCRIPTION = `Tool order: optional \`discover_capabilities\` → \`plasm_context\` → \`plasm\` (dry-run) → \`plasm_run\` (live).
7
+ **Call before \`plasm\` / \`plasm_run\`.** **One goal → one stable \`intent\` → one \`logical_session_ref\`.** Stable = same \`intent\` string on every turn for that goal (not per message/API). Bad: \`intent: "msg 3: sort moves"\` each turn — breaks reuse and fragments \`e#\`/\`p#\`. Multi-API: one **\`seeds\`** array on the same intent.
8
+ **Session:** one goal → one **\`intent\`** → one **\`logical_session_ref\`**; use **\`e#\`/\`m#\`/\`p#\`/\`r#\` from this session's teaching TSV only** (contract examples are shapes; substitute from your table).
9
+
10
+ **Federated homonyms:** when multiple catalogs expose the same wire entity, method, relation, or field name, programs must use opaque **\`e#\` / \`m#\` / \`r#\` / \`p#\`** from this table — not bare wire names and not \`entry_id:Entity\` syntax. Each symbol is catalog-scoped; copying the wrong row's symbol is a compile error.
11
+
12
+ **Open or extend:** **\`intent\`** + **\`seeds\`** (\`{api, entity}\`).
13
+
14
+ Returns **\`logical_session_ref\`** + fenced teaching TSV. TSV is the active symbol table: copy left cells into **\`plasm.program\`** (Plasm source, not JSON). Delta waves assign new **\`e#\`** monotonically.
15
+
16
+ **Extend picks:** same **\`intent\`**, expanded **\`seeds\`** — delta TSV or reuse cheat sheet when already exposed.
17
+
18
+ **\`_meta.plasm\`:** \`logical_session_ref\`, \`continuity\`, \`domain_revision\`, optional **\`relations\`**.`;
19
+
20
+ export const PLASM_TOOL_DESCRIPTION = `**Plan Plasm** (dry-run): **\`logical_session_ref\`** + **\`program\`**. Returns reviewable plan topology and executable **\`plan_commit_ref\`** (\`pcN\`). Pass that token to **\`plasm_run\`**; do **not** echo the program.
21
+
22
+ **\`program\` is Plasm source text, not JSON data.** Write one raw expression (e.g. \`e3(p15="electric").r2[p4,p5]\`) or multiline bindings with final roots. If a plan merely echoes an object/array/string literal, that is a literal no-op; rewrite as Plasm source.
23
+
24
+ Grammar below; symbols from \`plasm_context\` TSV. Reply with one valid plasm_program:
25
+
26
+ Output:
27
+ - Emit only code: one \`plasm_expr\`, or bindings then final roots. No prose, JSON, \`return\`, fences, or table rows.
28
+ - Prefer bind → narrow/project/transform → few final roots.
29
+
30
+ TSV table semantics:
31
+ - Header: \`plasm_expr<TAB>Meaning\`; one tab per row. Left = syntax/metadata; right = guidance only (never copy \`Meaning\`).
32
+ - Executable rows start with \`e#\`/Entity; metadata-only \`p#\`/\`v#\` rows are never roots.
33
+
34
+ Symbol and fill rules:
35
+ - \`e#\` entity; \`m#\` method; \`p#\` field/param/filter; \`r#\` relation nav; \`v#\` value-domain metadata only.
36
+ - Relation hops: \`.wire\` or \`.r#\` on row producers — never bare \`p#\` after \`.\` (\`p#\` in \`e#{…}\` is filter/param).
37
+ - Never write \`v#\` in code; use \`p#\` keys and read \`v#\` rows for allowed values.
38
+ - \`e#(p#)\` identity rows: substitute real wire ids for \`<id>\`, \`<value>\`, \`<receiver>\`, \`elem\`.
39
+ - Remove \`..\` ellipses or replace with real \`p#=\` assignments before output.
40
+ - Optional keys (\`opt:\` in \`Meaning\`): add only as keyed assignments with real values.
41
+ - Projection rows ending \`[p#,…]\` teach a valid field set. Reuse that suffix only on another expression returning the same entity or list type.
42
+
43
+ Core surface:
44
+ - Program shape: one \`plasm_expr\`, or \`label = …\` bindings then comma-separated final roots (no \`return\`).
45
+ - Postfix on row producers: \`.limit(N)\` | \`.page_size(N)\` | \`.sort(p#, desc)\` / \`.sort(p#,dir)\` | \`.filter{…}\` | \`.filter(…)\` | \`.aggregate(specs)\` | \`.group_by(p#)\` then \`.aggregate(specs)\` | \`.group_by(p#, specs)\` (comma sugar) | \`.dedupe(p#[, …])\` | \`.distinct(p#[, …])\` | \`.distinct()\` | \`.singleton()\` | \`[p#,…]\`. Row postfix fields use teaching \`rows:\` \`p#\` symbols; wire field names are sugar.
46
+ - Large list reads return the first **25 rows** in \`plasm_run\`; continue with \`page(l_<token>_pgN)\` from the tool result — not \`resources/read\`.
47
+ - Run gate: pass \`pcN\` from a prior dry-run to \`plasm_run\`; MCP \`plasm_run\` does not accept program continuations.
48
+ - Copy teaching TSV left cells; substitute placeholders; compose via bindings.
49
+ - Search when exposed: \`e#~$\` or \`e#~"text"\` (bare \`e#~\` is invalid); optional scoped filters \`e#~"text"[{p#=…}]\`.
50
+ - Field projection suffix: \`[p#,…]\`.
51
+
52
+ Composition rules:
53
+ - One binding per line; final roots last (preferred). Single-line bindings coerced; default return is first binding.
54
+ - Postfix/\`[fields]\` chain on any row-producing binding or expression.
55
+ - Row text: \`label = source <<TAG\` … \`TAG\` (equals required — never \`label source <<TAG\`); optional column list: \`label = source[p#,…] <<TAG\`. Minijinja \`rows\` = that source's projected rows only — not \`\${}\`.
56
+ - Pass \`binding.content\` to string params; compose with \`\${report.content}\` in later heredocs/strings (never bare \`\${}\` in prose; \`$$\` escapes \`$\`).
57
+ - Minijinja \`{{ }}\` / \`{% %}\` only inside row-to-text heredoc bodies — not in capability heredocs that use \`\${binding.path}\`.
58
+ - Do not use \`report.content\` as a final root or relation receiver.
59
+ - Action/create roots: return the action row or follow with \`e#(p#=…)\` get — not \`created.p#\` as a program root.
60
+ - Relation embed fan-out (\`Type.pokemon\` / \`.r#\`): prefer explicit Gets until embed path is warm; \`.limit\` on relation hops may need hydrate when graph targets are missing.
61
+ - Heredoc: \`<<TAG\` + newline; first trimmed \`TAG\` line closes; pick a tag absent from the body.
62
+ - Examples: array argument \`p#=[e#(<id>), …]\`. Quoted strings use only \`\\"\` and \`\\\\\` escapes.
63
+ - Worked row-compute program (shape only — substitute \`e#\` / \`p#\` from your table below):
64
+ items = e_source
65
+ filtered = items.filter{p_field>=300} # prefer bind; bare \`e_source.filter{…}\` list-alls first
66
+ sorted = filtered.sort(p_field, desc)
67
+ limited = sorted.limit(10)
68
+ limited[p_a,p_field]
69
+ - Worked federated relation + row compute (shape only — copy \`e#\` / \`.r#\` from your table's left column):
70
+ parent = e2(p_key=<val>)
71
+ children = parent.rN
72
+ limited = children.limit(5)
73
+ limited[p_a,p_b]
74
+ - \`markdown\`/\`html\`/\`document\`/\`json_text\`/\`blob\` values (per \`Meaning\`): \`<<TAG\` … \`TAG\` only; e.g. \`e2.mN(..., p#=<<TXT\` + newline body + \`TXT\` newline \`)\`.
75
+
76
+ Common pitfalls:
77
+ - \`[fields]\`/\`.group_by\`/\`.sort\`/\`.dedupe\`/row \`.filter\` use \`rows:\` fields only — not \`inputs:\`/\`opt:\` filter keys.
78
+ - \`e#{…}\` filters at HTTP; \`binding.filter{…}\` filters materialized rows. Prefer \`label = e#\` then filter; bare \`e#.filter{…}\` list-alls first.
79
+ - Primary group/agg: \`group_by(p_key).aggregate(n=count)\`; bare \`group_by(p_key)\` ≡ \`group_by(p_key, count=count)\`; multi-key comma sugar: \`group_by(k1, k2, n=count)\`.
80
+ - \`=>\` only for derive \`{ k: _.field }\` or \`for_each\` updates — not relation reads (\`labels = issues.r#\`).
81
+ - Homograph: relation fanout via \`.r#\`/wire; \`labels = issues.p#\` forgiven when binding name matches.
82
+ - Never emit \`$\` from teaching rows; substitute real ids and \`e#~"text"\` search terms.
83
+ - Search operand: \`e#~$\` or \`e#~"text"\` required — bare \`e#~\` is a parse error.
84
+ - Federated sessions: when the same wire entity/method/relation/field name appears in multiple catalogs, bare wire tokens are compile errors — copy the session \`e#\` / \`m#\` / \`r#\` / \`p#\` from the teaching row for that catalog. Never write \`entry_id:Entity\` or \`catalog.Entity\` in programs.
85
+ - Cross-catalog stitch (shape only — substitute symbols from your TSV): pokeapi \`Type\` get → row-to-text bindings → proof \`ShareLink\` document param with \`\${binding.content}\` inside \`<<TAG\` … \`TAG\`.
86
+ - Search-only entities (no query): no \`e#{}\` list-all — scoped \`e#{p#=…}\` and/or \`e#~"text"\`.`;
87
+
88
+ export const PLASM_RUN_TOOL_DESCRIPTION = `**Run Plasm** (live): **\`logical_session_ref\`** + **\`plan_commit_ref\`** (\`pcN\`) from a prior **\`plasm\`** dry-run. Real HTTP/API; may return **\`resource_link\`** / \`_meta.plasm\` snapshots.
89
+
90
+ **Review gate:** \`plasm_run\` executes exactly the reviewed plan stored under the **\`plan_commit_ref\`**. If the token is missing, expired, or from another plan, call **\`plasm\`** again.
91
+
92
+ **Live execute:** server spawns one async operation and awaits terminal rows in the tool response. Progress uses standard \`notifications/plasm/op\` on the registered handle.
93
+
94
+ **Live results:** \`## {return_label} ({n} rows)\` + capped TSV (25 rows inline); multi-return programs use \`# Results\` with \`### {label} ({n} rows)\` per root. When more rows exist, continue with \`page(l_<token>_pgN)\` from the tool result or \`_meta.plasm.paging\`. Snapshot URI lines point at full JSON via MCP **\`resources/read\`** when needed. Run Explorer is supplemental.`;
@@ -0,0 +1,29 @@
1
+ export function formatPlasmContextMarkdown(
2
+ logicalSessionRef: string,
3
+ tsv: string,
4
+ reused: boolean,
5
+ ): string {
6
+ const delta = tsv.trim();
7
+ if (!delta) {
8
+ if (reused) {
9
+ return `\`${logicalSessionRef}\`\n\nUnchanged — seeds already exposed. Next: \`plasm\` / \`plasm_run\`.\n`;
10
+ }
11
+ return `\`${logicalSessionRef}\`\n`;
12
+ }
13
+ return `\`${logicalSessionRef}\`\n\n\`\`\`tsv\n${delta}\n\`\`\`\n`;
14
+ }
15
+
16
+ export function formatPlasmDryRunMarkdown(summary: string, planCommitRef: string): string {
17
+ return `\`\`\`text\n${summary.trim()}\n\`\`\`\n\n**Run:** pass \`plan_commit_ref\`: \`${planCommitRef}\` to **\`plasm_run\`**. Do not echo the program.`;
18
+ }
19
+
20
+ export function formatPlasmRunMarkdown(
21
+ message: string,
22
+ ok: boolean,
23
+ rowsJson?: string,
24
+ ): string {
25
+ if (!ok) return `**plasm_run** (pending transport)\n\n${message}`;
26
+ const rows = rowsJson?.trim();
27
+ if (!rows) return message;
28
+ return `${message}\n\n\`\`\`json\n${rows}\n\`\`\``;
29
+ }
@@ -0,0 +1,65 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+
4
+ import type { SkillDefinition } from "../authoring/define-skill.js";
5
+ import type { SubagentRegistry } from "../authoring/subagent-loader.js";
6
+
7
+ export function createHarnessTools(options: {
8
+ skills?: SkillDefinition[];
9
+ subagents?: SubagentRegistry;
10
+ }) {
11
+ const skillByName = new Map((options.skills ?? []).map((s) => [s.name, s]));
12
+
13
+ const readSkill =
14
+ skillByName.size > 0
15
+ ? {
16
+ read_skill: tool({
17
+ description:
18
+ "Load full text for a filesystem skill by name. Use when the skill index in the system prompt is not enough.",
19
+ inputSchema: z.object({
20
+ name: z.string().min(1).describe("Skill name from the index"),
21
+ }),
22
+ execute: async ({ name }: { name: string }) => {
23
+ const skill = skillByName.get(name);
24
+ if (!skill) {
25
+ return `Unknown skill "${name}". Available: ${[...skillByName.keys()].join(", ")}`;
26
+ }
27
+ return skill.body.trim();
28
+ },
29
+ }),
30
+ }
31
+ : {};
32
+
33
+ const subagentNames = options.subagents?.list().map((s) => s.name) ?? [];
34
+ const delegateSubagent =
35
+ subagentNames.length > 0 && options.subagents
36
+ ? {
37
+ delegate_subagent: tool({
38
+ description:
39
+ "Delegate a sub-task to a filesystem-isolated child agent. Each subagent has its own catalogs and session scope.",
40
+ inputSchema: z.object({
41
+ name: z
42
+ .string()
43
+ .min(1)
44
+ .describe(`Subagent name. One of: ${subagentNames.join(", ")}`),
45
+ message: z.string().min(1).describe("User message for the child agent turn"),
46
+ }),
47
+ execute: async ({ name, message }: { name: string; message: string }) => {
48
+ const result = await options.subagents!.delegate(name, message);
49
+ return `${result.text}\n\n(steps: ${result.steps})`;
50
+ },
51
+ }),
52
+ }
53
+ : {};
54
+
55
+ return { ...readSkill, ...delegateSubagent };
56
+ }
57
+
58
+ export function renderSkillIndex(skills: SkillDefinition[]): string {
59
+ if (!skills.length) return "";
60
+ const lines = skills.map((skill) => {
61
+ const desc = skill.description ?? "Filesystem skill";
62
+ return `- **${skill.name}** — ${desc} (use read_skill to load)`;
63
+ });
64
+ return ["## Skill index", ...lines].join("\n");
65
+ }