dna-sdk 0.4.0 → 0.6.0
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/dist/adapters/source-url.d.ts +3 -0
- package/dist/adapters/source-url.js +28 -1
- package/dist/extensions/helix/kinds/tool.kind.yaml +240 -0
- package/dist/extensions/helix.js +54 -99
- package/dist/extensions/sdlc.js +162 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/kernel/errors.d.ts +35 -0
- package/dist/kernel/errors.js +53 -0
- package/dist/kernel/kind-registry.js +3 -1
- package/dist/kernel/kind_base.d.ts +13 -0
- package/dist/kernel/kind_base.js +17 -0
- package/dist/kernel/models.d.ts +85 -191
- package/dist/kernel/models.js +19 -32
- package/dist/kernel/prompt-builder.js +15 -4
- package/dist/package-scope.d.ts +18 -0
- package/dist/package-scope.js +87 -0
- package/dist/prompts.d.ts +27 -4
- package/dist/prompts.js +21 -6
- package/dist/tools.d.ts +41 -0
- package/dist/tools.js +88 -0
- package/package.json +1 -1
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* file:// <path> → FilesystemSource (read/write on disk)
|
|
11
11
|
* fs:// <path> → alias of file://
|
|
12
12
|
* <plain path> → treated as file://<path>
|
|
13
|
+
* pkg://<pkg>[/sub] → FilesystemSource (READ-ONLY) over a scope embedded
|
|
14
|
+
* as PACKAGE DATA of <pkg> (sub defaults to .dna);
|
|
15
|
+
* travels with the app (tarball / Docker)
|
|
13
16
|
* postgresql:// … → PostgresSource (node-postgres)
|
|
14
17
|
* postgres:// … → alias of postgresql://
|
|
15
18
|
* sqlite:// <path> → NOT SUPPORTED in the TS runtime (Python-only; the
|
|
@@ -16,6 +16,22 @@ function schemeOf(url) {
|
|
|
16
16
|
const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(url);
|
|
17
17
|
return (m ? m[1] : "file").toLowerCase();
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Split `pkg://<package>[/<subpath>]` into `{ pkg, subpath }`. Parity with the
|
|
21
|
+
* python `_parse_pkg_url`: `pkg://app` → `{pkg:"app", subpath:""}`;
|
|
22
|
+
* `pkg://app/.dna` → `{pkg:"app", subpath:".dna"}`. The package is the netloc
|
|
23
|
+
* (a dotted `pkg://my.app` stays `my.app`).
|
|
24
|
+
*/
|
|
25
|
+
function parsePkgUrl(url) {
|
|
26
|
+
const m = /^pkg:\/\/([^/]*)(?:\/(.*))?$/.exec(url);
|
|
27
|
+
const pkg = m?.[1] ?? "";
|
|
28
|
+
if (!pkg) {
|
|
29
|
+
throw new UnsupportedSourceScheme(`pkg:// source URL is missing a package name — use ` +
|
|
30
|
+
`pkg://<package>[/<subpath>] (e.g. pkg://app or pkg://app/.dna). ` +
|
|
31
|
+
`Got: ${url}`);
|
|
32
|
+
}
|
|
33
|
+
return { pkg, subpath: m?.[2] ?? "" };
|
|
34
|
+
}
|
|
19
35
|
/**
|
|
20
36
|
* Build a source from a scheme URL (see module docstring).
|
|
21
37
|
*
|
|
@@ -29,6 +45,16 @@ export async function sourceFromUrl(url, opts = {}) {
|
|
|
29
45
|
const { FilesystemSource } = await import("./filesystem/source.js");
|
|
30
46
|
return new FilesystemSource(urlToFsPath(url) || url);
|
|
31
47
|
}
|
|
48
|
+
if (scheme === "pkg") {
|
|
49
|
+
// A scope embedded as PACKAGE DATA — resolve it from inside the installed
|
|
50
|
+
// package so it travels with the app (tarball / Docker), no path
|
|
51
|
+
// navigation and no manual copy. READ-ONLY: FilesystemSource has no write
|
|
52
|
+
// surface here (to write, use file:// or postgresql://).
|
|
53
|
+
const { FilesystemSource } = await import("./filesystem/source.js");
|
|
54
|
+
const { anchorScopesRoot, DEFAULT_SUBPATH } = await import("../package-scope.js");
|
|
55
|
+
const { pkg, subpath } = parsePkgUrl(url);
|
|
56
|
+
return new FilesystemSource(anchorScopesRoot(pkg, subpath || DEFAULT_SUBPATH));
|
|
57
|
+
}
|
|
32
58
|
if (base === "postgresql" || base === "postgres") {
|
|
33
59
|
const { PostgresSource } = await import("./postgres/source.js");
|
|
34
60
|
const src = new PostgresSource({ connectionString: url, schema: opts.schema });
|
|
@@ -41,7 +67,8 @@ export async function sourceFromUrl(url, opts = {}) {
|
|
|
41
67
|
`(filesystem) or postgresql:// here. Got: ${url}`);
|
|
42
68
|
}
|
|
43
69
|
throw new UnsupportedSourceScheme(`unsupported source URL scheme '${scheme}://' — the TS runtime ships ` +
|
|
44
|
-
`file:// (filesystem)
|
|
70
|
+
`file:// (filesystem), pkg:// (read-only package-data scope) and ` +
|
|
71
|
+
`postgresql:// adapters. Got: ${url}`);
|
|
45
72
|
}
|
|
46
73
|
/**
|
|
47
74
|
* The `file://` URL the SDK falls back to with no explicit config.
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Tool — a declarative, invocable capability an agent can call (record plane).
|
|
2
|
+
#
|
|
3
|
+
# "Tools as data" (f-dna-tools-as-data). The DNA already governs persona,
|
|
4
|
+
# instruction and guardrails declaratively; a Tool moves the AGENT-FACING
|
|
5
|
+
# surface of a tool into the same declarative plane — the ``description`` the
|
|
6
|
+
# model reads to decide whether to call it (``metadata.description``) and the
|
|
7
|
+
# ``parameters`` JSON Schema of its arguments (``spec.input_schema``). One
|
|
8
|
+
# source of truth, versioned, testable, and overridable per tenant, served
|
|
9
|
+
# identically to a Python backend (`@tool`) and a TypeScript frontend
|
|
10
|
+
# (CopilotKit `useCopilotAction`) via ``dna.load_tools`` / ``loadTools``.
|
|
11
|
+
#
|
|
12
|
+
# F3 migration (s-tool-kind-descriptor): this WAS a hand-written ``ToolKind``
|
|
13
|
+
# class (helix/__init__.py + helix.ts) with a ``TypedTool``/``ToolSpec``
|
|
14
|
+
# model. It is now a ``kinds/tool.kind.yaml`` descriptor — a record Kind
|
|
15
|
+
# expressed as data, per the repo's own ratchet (record Kinds are
|
|
16
|
+
# descriptors, never classes). The invocation surface (type/endpoint/mcp/
|
|
17
|
+
# python/shell + input/output schema + auth + read_only/requires_confirmation)
|
|
18
|
+
# is preserved verbatim; only the IMPLEMENTATION moved class → descriptor and
|
|
19
|
+
# the plane moved composition → record (a Tool is not a prompt target and
|
|
20
|
+
# never composes into an agent prompt, so it carries no composition signal —
|
|
21
|
+
# writing a Tool no longer invalidates the composition schema cache).
|
|
22
|
+
#
|
|
23
|
+
# Agents reference Tools by name via ``dep_filters.tools`` (unchanged — the
|
|
24
|
+
# alias ``helix-tool`` is stable). Stored as ``tools/<name>.yaml`` —
|
|
25
|
+
# marketplace-shareable as standalone bundles.
|
|
26
|
+
#
|
|
27
|
+
# tenant_scope intentionally NOT declared — permissive (base Tool + optional
|
|
28
|
+
# per-tenant overlay). A tenant may legitimately override a tool's
|
|
29
|
+
# description/parameters (the SaaS value hook); the base stays intact and the
|
|
30
|
+
# máxima "inheritable ⇒ never tenanted" holds.
|
|
31
|
+
#
|
|
32
|
+
# PARITY-CRITICAL package data: byte-identical mirror under both runtimes'
|
|
33
|
+
# helix/kinds/ (tests/test_descriptor_hash_parity.py enforces).
|
|
34
|
+
apiVersion: github.com/ruinosus/dna/core/v1
|
|
35
|
+
kind: KindDefinition
|
|
36
|
+
metadata:
|
|
37
|
+
name: tool
|
|
38
|
+
spec:
|
|
39
|
+
target_api_version: github.com/ruinosus/dna/v1
|
|
40
|
+
target_kind: Tool
|
|
41
|
+
alias: helix-tool
|
|
42
|
+
origin: github.com/ruinosus/dna/tool
|
|
43
|
+
plane: record
|
|
44
|
+
prompt_target: false
|
|
45
|
+
flatten_in_context: false
|
|
46
|
+
prompt_target_priority: 0
|
|
47
|
+
storage:
|
|
48
|
+
type: yaml
|
|
49
|
+
container: tools
|
|
50
|
+
schema:
|
|
51
|
+
type: object
|
|
52
|
+
properties:
|
|
53
|
+
type:
|
|
54
|
+
type: string
|
|
55
|
+
enum:
|
|
56
|
+
- http
|
|
57
|
+
- mcp
|
|
58
|
+
- python
|
|
59
|
+
- shell
|
|
60
|
+
- builtin
|
|
61
|
+
description: How the tool is executed. builtin | http | mcp | python
|
|
62
|
+
| shell.
|
|
63
|
+
endpoint:
|
|
64
|
+
type: string
|
|
65
|
+
description: URL called when type=http. Supports {placeholder}
|
|
66
|
+
templating.
|
|
67
|
+
method:
|
|
68
|
+
type: string
|
|
69
|
+
description: HTTP method when type=http (default POST).
|
|
70
|
+
mcp_server:
|
|
71
|
+
type: string
|
|
72
|
+
description: MCP server name when type=mcp.
|
|
73
|
+
mcp_tool:
|
|
74
|
+
type: string
|
|
75
|
+
description: Tool name on the MCP server when type=mcp.
|
|
76
|
+
python_module:
|
|
77
|
+
type: string
|
|
78
|
+
description: Dotted import path when type=python.
|
|
79
|
+
python_callable:
|
|
80
|
+
type: string
|
|
81
|
+
description: Attribute on the module (function or class) when
|
|
82
|
+
type=python.
|
|
83
|
+
shell_command:
|
|
84
|
+
type: string
|
|
85
|
+
description: Command template when type=shell. Never executed without
|
|
86
|
+
confirmation.
|
|
87
|
+
input_schema:
|
|
88
|
+
type: object
|
|
89
|
+
description: JSON Schema of the arguments the agent passes when
|
|
90
|
+
invoking the tool — the "parameters" the model fills in. Surfaced
|
|
91
|
+
as ``parameters`` by ``dna.load_tools`` / ``loadTools``.
|
|
92
|
+
output_schema:
|
|
93
|
+
type: object
|
|
94
|
+
description: JSON Schema describing the shape of the tool's response.
|
|
95
|
+
auth_type:
|
|
96
|
+
type: string
|
|
97
|
+
enum:
|
|
98
|
+
- none
|
|
99
|
+
- api_key
|
|
100
|
+
- bearer
|
|
101
|
+
- oauth2
|
|
102
|
+
description: Credential strategy for the invocation.
|
|
103
|
+
auth_env_var:
|
|
104
|
+
type: string
|
|
105
|
+
description: Environment variable holding the credential (e.g.
|
|
106
|
+
GITHUB_TOKEN).
|
|
107
|
+
read_only:
|
|
108
|
+
type: boolean
|
|
109
|
+
description: False = the tool may mutate state (DB writes, file
|
|
110
|
+
changes, external side effects).
|
|
111
|
+
requires_confirmation:
|
|
112
|
+
type: boolean
|
|
113
|
+
description: Force user approval before each invocation.
|
|
114
|
+
tags:
|
|
115
|
+
type: array
|
|
116
|
+
items:
|
|
117
|
+
type: string
|
|
118
|
+
description: Free-form labels for filtering and search.
|
|
119
|
+
examples:
|
|
120
|
+
type: array
|
|
121
|
+
items:
|
|
122
|
+
type: object
|
|
123
|
+
description: Usage examples ([{input, output}]).
|
|
124
|
+
ui:
|
|
125
|
+
mode: build
|
|
126
|
+
label:
|
|
127
|
+
en: Tools
|
|
128
|
+
pt-BR: Ferramentas
|
|
129
|
+
description:
|
|
130
|
+
en: '@tool functions available to agents.'
|
|
131
|
+
pt-BR: Ferramentas (@tool) disponíveis aos agentes.
|
|
132
|
+
routes:
|
|
133
|
+
list: docs/Tool
|
|
134
|
+
detail: docs/Tool/:name
|
|
135
|
+
permissions:
|
|
136
|
+
list: any
|
|
137
|
+
detail: any
|
|
138
|
+
in_sidebar: true
|
|
139
|
+
display_order: 53
|
|
140
|
+
ui_schema:
|
|
141
|
+
type:
|
|
142
|
+
widget: select
|
|
143
|
+
label: Invocation type
|
|
144
|
+
help: 'How the tool is executed: http | mcp | python | shell | builtin.'
|
|
145
|
+
order: 10
|
|
146
|
+
endpoint:
|
|
147
|
+
widget: text
|
|
148
|
+
label: HTTP endpoint
|
|
149
|
+
help: URL called when type=http. Supports {placeholder} templating.
|
|
150
|
+
order: 20
|
|
151
|
+
method:
|
|
152
|
+
widget: select
|
|
153
|
+
label: HTTP method
|
|
154
|
+
order: 25
|
|
155
|
+
mcp_server:
|
|
156
|
+
widget: text
|
|
157
|
+
label: MCP server
|
|
158
|
+
help: Server name when type=mcp.
|
|
159
|
+
order: 30
|
|
160
|
+
mcp_tool:
|
|
161
|
+
widget: text
|
|
162
|
+
label: MCP tool name
|
|
163
|
+
order: 35
|
|
164
|
+
python_module:
|
|
165
|
+
widget: text
|
|
166
|
+
label: Python module
|
|
167
|
+
help: Dotted import path when type=python.
|
|
168
|
+
order: 40
|
|
169
|
+
python_callable:
|
|
170
|
+
widget: text
|
|
171
|
+
label: Python callable
|
|
172
|
+
help: Attribute on the module (function or class).
|
|
173
|
+
order: 45
|
|
174
|
+
shell_command:
|
|
175
|
+
widget: textarea
|
|
176
|
+
label: Shell command
|
|
177
|
+
help: Command template when type=shell. Never executed without
|
|
178
|
+
confirmation.
|
|
179
|
+
order: 50
|
|
180
|
+
input_schema:
|
|
181
|
+
widget: code
|
|
182
|
+
language: yaml
|
|
183
|
+
label: Input schema (JSON Schema)
|
|
184
|
+
help: Validates the arguments the agent passes when invoking the tool.
|
|
185
|
+
height: 260
|
|
186
|
+
order: 60
|
|
187
|
+
output_schema:
|
|
188
|
+
widget: code
|
|
189
|
+
language: yaml
|
|
190
|
+
label: Output schema (JSON Schema)
|
|
191
|
+
help: Describes the shape of the tool's response.
|
|
192
|
+
height: 220
|
|
193
|
+
order: 70
|
|
194
|
+
auth_type:
|
|
195
|
+
widget: select
|
|
196
|
+
label: Auth type
|
|
197
|
+
help: none | api_key | bearer | oauth2.
|
|
198
|
+
order: 80
|
|
199
|
+
auth_env_var:
|
|
200
|
+
widget: text
|
|
201
|
+
label: Auth env var
|
|
202
|
+
help: Environment variable holding the credential.
|
|
203
|
+
order: 85
|
|
204
|
+
read_only:
|
|
205
|
+
widget: checkbox
|
|
206
|
+
label: Read-only
|
|
207
|
+
help: Uncheck if the tool mutates state (database writes, file changes,
|
|
208
|
+
external side effects).
|
|
209
|
+
order: 90
|
|
210
|
+
requires_confirmation:
|
|
211
|
+
widget: checkbox
|
|
212
|
+
label: Requires confirmation
|
|
213
|
+
help: Force user approval before each invocation.
|
|
214
|
+
order: 95
|
|
215
|
+
tags:
|
|
216
|
+
widget: tags
|
|
217
|
+
label: Tags
|
|
218
|
+
order: 100
|
|
219
|
+
examples:
|
|
220
|
+
widget: readonly
|
|
221
|
+
label: Examples
|
|
222
|
+
help: Usage examples. Nested list — edit via YAML for now.
|
|
223
|
+
order: 110
|
|
224
|
+
graph_style:
|
|
225
|
+
fill: '#14B8A6'
|
|
226
|
+
stroke: '#0D9488'
|
|
227
|
+
text_color: '#fff'
|
|
228
|
+
ascii_icon: 🔧
|
|
229
|
+
display_label: Tools
|
|
230
|
+
docs: A Tool is a declarative, invocable capability an agent can call — an
|
|
231
|
+
HTTP endpoint, an MCP server tool, a Python callable, a shell command, or
|
|
232
|
+
a builtin. It bridges DNA with OpenAI/Anthropic tool-calling conventions.
|
|
233
|
+
The agent-facing surface is its ``metadata.description`` (the text the
|
|
234
|
+
model reads to decide to call it) and its ``spec.input_schema`` (the
|
|
235
|
+
"parameters" JSON Schema of the arguments); ``dna.load_tools`` /
|
|
236
|
+
``loadTools`` serve exactly that surface, identically to Python and
|
|
237
|
+
TypeScript consumers from this one source. It also declares an auth
|
|
238
|
+
strategy and read_only / requires_confirmation flags the host honors at
|
|
239
|
+
runtime. Agents reference Tools via ``dep_filters.tools``. Stored as
|
|
240
|
+
``tools/<name>.yaml`` — marketplace-shareable as standalone bundles.
|
package/dist/extensions/helix.js
CHANGED
|
@@ -7,12 +7,40 @@ import yaml from "js-yaml";
|
|
|
7
7
|
import { join as pathJoin } from "node:path";
|
|
8
8
|
import { nodeFS, readTextSafe, collectDir } from "../kernel/fs.js";
|
|
9
9
|
import { KindBase } from "../kernel/kind_base.js";
|
|
10
|
-
import { AgentSchema, ActorSchema, UseCaseSchema,
|
|
10
|
+
import { AgentSchema, ActorSchema, UseCaseSchema, AgentSpecSchema, ActorSpecSchema, UseCaseSpecSchema, GenomeSchema, GenomeSpecSchema, LayerPolicySchema, LayerPolicySpecSchema, zodSpecToJsonSchema } from "../kernel/models.js";
|
|
11
|
+
import { loadDescriptors } from "../kernel/descriptor-loader.js";
|
|
11
12
|
import { SD } from "../kernel/protocols.js";
|
|
12
13
|
import { readSpecString, readSpecStringArray } from "../kernel/spec-access.js";
|
|
13
14
|
import { SettingKind, ThemeKind, UserProfileKind, CanvasKind } from "./helix_extras.js";
|
|
14
15
|
import { registerWriteGuards } from "./helix/write-guards.js";
|
|
15
16
|
const MOD_URL = import.meta.url;
|
|
17
|
+
// ── Named composition layouts (s-dx-named-layouts) ─────────────────────
|
|
18
|
+
//
|
|
19
|
+
// An author orders persona-vs-instruction by NAME (`layout:` in the Agent
|
|
20
|
+
// spec) instead of hand-writing raw Mustache with internal section names.
|
|
21
|
+
// Each preset resolves to one of these embedded templates via
|
|
22
|
+
// `AgentKind.layoutTemplate()`. 1:1 with Python `dna.extensions.helix`.
|
|
23
|
+
//
|
|
24
|
+
// The guardrails block is shared verbatim — guardrails are hard policy and
|
|
25
|
+
// always land LAST, after both the instruction and the soul, regardless of
|
|
26
|
+
// their relative order. (Aligns TS composition to Python, closing the
|
|
27
|
+
// pre-existing i-213/i-011 divergence where TS omitted the guardrails block.)
|
|
28
|
+
const GUARDRAILS_BLOCK = "{{#guardrails-guardrail}}" +
|
|
29
|
+
"## Guardrail: {{name}} ({{severity}})\n" +
|
|
30
|
+
"{{#description}}_{{description}}_\n\n{{/description}}" +
|
|
31
|
+
"{{#rules}}- {{{.}}}\n{{/rules}}\n" +
|
|
32
|
+
"{{/guardrails-guardrail}}";
|
|
33
|
+
// instruction-first (a.k.a. "default") — historic order: instruction, soul,
|
|
34
|
+
// guardrails. IS the kind default template.
|
|
35
|
+
const LAYOUT_INSTRUCTION_FIRST = "{{{agent.instruction}}}\n\n{{{soul_content}}}\n\n" + GUARDRAILS_BLOCK;
|
|
36
|
+
// persona-first — Soul leads, then instruction, then guardrails.
|
|
37
|
+
const LAYOUT_PERSONA_FIRST = "{{{soul_content}}}\n\n{{{agent.instruction}}}\n\n" + GUARDRAILS_BLOCK;
|
|
38
|
+
const AGENT_LAYOUTS = {
|
|
39
|
+
default: LAYOUT_INSTRUCTION_FIRST,
|
|
40
|
+
"instruction-first": LAYOUT_INSTRUCTION_FIRST,
|
|
41
|
+
"persona-first": LAYOUT_PERSONA_FIRST,
|
|
42
|
+
};
|
|
43
|
+
const AGENT_LAYOUT_NAMES = ["default", "instruction-first", "persona-first"];
|
|
16
44
|
// GenomeKind — Phase 16 (scope segregation)
|
|
17
45
|
//
|
|
18
46
|
// Replaces ModuleKind as the scope-root identity Kind. Carries catalog
|
|
@@ -253,6 +281,13 @@ class AgentKind extends KindBase {
|
|
|
253
281
|
},
|
|
254
282
|
objective: { widget: "textarea", label: "Objective", order: 15 },
|
|
255
283
|
model: { widget: "text", label: "Model", order: 20 },
|
|
284
|
+
layout: {
|
|
285
|
+
widget: "select",
|
|
286
|
+
label: "Layout",
|
|
287
|
+
options: ["default", "instruction-first", "persona-first"],
|
|
288
|
+
help: "Named composition order — 'persona-first' puts the Soul before the instruction. Leave empty for the default. A raw promptTemplate, if set, overrides this.",
|
|
289
|
+
order: 25,
|
|
290
|
+
},
|
|
256
291
|
soul: { widget: "text", label: "Soul", help: "Name of the Soul doc to flatten into the prompt.", order: 30 },
|
|
257
292
|
skills: { widget: "tags", label: "Skills", order: 40 },
|
|
258
293
|
actors: { widget: "tags", label: "Actors this agent serves", order: 50 },
|
|
@@ -305,7 +340,17 @@ class AgentKind extends KindBase {
|
|
|
305
340
|
return { skills: skills.length, soul: readSpecString(doc, "soul") ?? null };
|
|
306
341
|
}
|
|
307
342
|
promptTemplate() {
|
|
308
|
-
|
|
343
|
+
// IS the `instruction-first` / `default` named layout — the kind default
|
|
344
|
+
// template and the `default` layout are one string, so an agent with no
|
|
345
|
+
// `layout:` composes identically. (Now includes the guardrails block,
|
|
346
|
+
// matching Python — see the layout-constants comment above.)
|
|
347
|
+
return LAYOUT_INSTRUCTION_FIRST;
|
|
348
|
+
}
|
|
349
|
+
layoutTemplate(name) {
|
|
350
|
+
return AGENT_LAYOUTS[name] ?? null;
|
|
351
|
+
}
|
|
352
|
+
layoutNames() {
|
|
353
|
+
return [...AGENT_LAYOUT_NAMES];
|
|
309
354
|
}
|
|
310
355
|
preview(doc) {
|
|
311
356
|
const spec = (doc.spec ?? {});
|
|
@@ -485,102 +530,6 @@ class UseCaseKind extends KindBase {
|
|
|
485
530
|
}
|
|
486
531
|
}
|
|
487
532
|
// ---------------------------------------------------------------------------
|
|
488
|
-
// ToolKind
|
|
489
|
-
// ---------------------------------------------------------------------------
|
|
490
|
-
class ToolKind extends KindBase {
|
|
491
|
-
apiVersion = "github.com/ruinosus/dna/v1";
|
|
492
|
-
kind = "Tool";
|
|
493
|
-
alias = "helix-tool";
|
|
494
|
-
isSchemaAffecting = true;
|
|
495
|
-
origin = "github.com/ruinosus/dna/tool";
|
|
496
|
-
isPromptTarget = false;
|
|
497
|
-
promptTargetPriority = 0;
|
|
498
|
-
flattenInContext = false;
|
|
499
|
-
storage = SD.yaml("tools");
|
|
500
|
-
graphStyle = { fill: "#14B8A6", stroke: "#0D9488", textColor: "#fff" };
|
|
501
|
-
asciiIcon = "🔧";
|
|
502
|
-
displayLabel = "Tools";
|
|
503
|
-
_sourceUrl = MOD_URL;
|
|
504
|
-
docs = "A Tool is a declarative, invocable capability an agent can call: an " +
|
|
505
|
-
"HTTP endpoint, an MCP server tool, a Python callable, a shell command, " +
|
|
506
|
-
"or a builtin. Bridges helix with OpenAI/Anthropic tool-calling " +
|
|
507
|
-
"conventions. Each Tool declares an input/output JSON Schema, an auth " +
|
|
508
|
-
"strategy, and read_only / requires_confirmation flags that the harness " +
|
|
509
|
-
"honors at runtime. Agents reference Tools via dep_filters.tools. " +
|
|
510
|
-
"Stored as tools/<name>.yaml — marketplace-shareable as standalone bundles.";
|
|
511
|
-
uiSchema = {
|
|
512
|
-
type: { widget: "select", label: "Invocation type", help: "How the tool is executed: http | mcp | python | shell | builtin.", order: 10 },
|
|
513
|
-
endpoint: { widget: "text", label: "HTTP endpoint", help: "URL called when type=http. Supports {placeholder} templating.", order: 20 },
|
|
514
|
-
method: { widget: "select", label: "HTTP method", order: 25 },
|
|
515
|
-
mcp_server: { widget: "text", label: "MCP server", help: "Server name when type=mcp.", order: 30 },
|
|
516
|
-
mcp_tool: { widget: "text", label: "MCP tool name", order: 35 },
|
|
517
|
-
python_module: { widget: "text", label: "Python module", help: "Dotted import path when type=python.", order: 40 },
|
|
518
|
-
python_callable: { widget: "text", label: "Python callable", help: "Attribute on the module (function or class).", order: 45 },
|
|
519
|
-
shell_command: { widget: "textarea", label: "Shell command", help: "Command template when type=shell. Never executed without confirmation.", order: 50 },
|
|
520
|
-
input_schema: {
|
|
521
|
-
widget: "code",
|
|
522
|
-
language: "yaml",
|
|
523
|
-
label: "Input schema (JSON Schema)",
|
|
524
|
-
help: "Validates the arguments the agent passes when invoking the tool.",
|
|
525
|
-
height: 260,
|
|
526
|
-
order: 60,
|
|
527
|
-
},
|
|
528
|
-
output_schema: {
|
|
529
|
-
widget: "code",
|
|
530
|
-
language: "yaml",
|
|
531
|
-
label: "Output schema (JSON Schema)",
|
|
532
|
-
help: "Describes the shape of the tool's response.",
|
|
533
|
-
height: 220,
|
|
534
|
-
order: 70,
|
|
535
|
-
},
|
|
536
|
-
auth_type: { widget: "select", label: "Auth type", help: "none | api_key | bearer | oauth2.", order: 80 },
|
|
537
|
-
auth_env_var: { widget: "text", label: "Auth env var", help: "Environment variable holding the credential.", order: 85 },
|
|
538
|
-
read_only: { widget: "checkbox", label: "Read-only", help: "Uncheck if the tool mutates state.", order: 90 },
|
|
539
|
-
requires_confirmation: { widget: "checkbox", label: "Requires confirmation", help: "Force user approval before each invocation.", order: 95 },
|
|
540
|
-
tags: { widget: "tags", label: "Tags", order: 100 },
|
|
541
|
-
examples: { widget: "readonly", label: "Examples", help: "Usage examples. Edit via YAML for now.", order: 110 },
|
|
542
|
-
};
|
|
543
|
-
schema() { return zodSpecToJsonSchema(ToolSpecSchema); }
|
|
544
|
-
parse(raw) {
|
|
545
|
-
return ToolSchema.parse(raw);
|
|
546
|
-
}
|
|
547
|
-
summary() { return null; }
|
|
548
|
-
preview(doc) {
|
|
549
|
-
const spec = (doc.spec ?? {});
|
|
550
|
-
const fields = [];
|
|
551
|
-
if (typeof spec.type === "string")
|
|
552
|
-
fields.push({ label: "type", value: spec.type });
|
|
553
|
-
if (typeof spec.endpoint === "string")
|
|
554
|
-
fields.push({ label: "endpoint", value: spec.endpoint });
|
|
555
|
-
if (typeof spec.method === "string")
|
|
556
|
-
fields.push({ label: "method", value: spec.method });
|
|
557
|
-
if (typeof spec.mcp_server === "string")
|
|
558
|
-
fields.push({ label: "mcp_server", value: spec.mcp_server });
|
|
559
|
-
if (typeof spec.mcp_tool === "string")
|
|
560
|
-
fields.push({ label: "mcp_tool", value: spec.mcp_tool });
|
|
561
|
-
if (typeof spec.python_module === "string")
|
|
562
|
-
fields.push({ label: "python_module", value: spec.python_module });
|
|
563
|
-
if (typeof spec.python_callable === "string")
|
|
564
|
-
fields.push({ label: "python_callable", value: spec.python_callable });
|
|
565
|
-
if (typeof spec.shell_command === "string")
|
|
566
|
-
fields.push({ label: "shell_command", value: spec.shell_command });
|
|
567
|
-
if (spec.input_schema)
|
|
568
|
-
fields.push({ label: "input_schema", value: JSON.stringify(spec.input_schema, null, 2) });
|
|
569
|
-
if (spec.output_schema)
|
|
570
|
-
fields.push({ label: "output_schema", value: JSON.stringify(spec.output_schema, null, 2) });
|
|
571
|
-
if (typeof spec.auth_type === "string")
|
|
572
|
-
fields.push({ label: "auth", value: spec.auth_type });
|
|
573
|
-
if (spec.read_only != null)
|
|
574
|
-
fields.push({ label: "read_only", value: String(spec.read_only) });
|
|
575
|
-
if (spec.requires_confirmation != null)
|
|
576
|
-
fields.push({ label: "requires_confirmation", value: String(spec.requires_confirmation) });
|
|
577
|
-
if (fields.length === 0) {
|
|
578
|
-
return [{ kind: "empty", title: `Tool ${doc.name}` }];
|
|
579
|
-
}
|
|
580
|
-
return [{ kind: "fields", title: `Tool ${doc.name}`, fields }];
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
// ---------------------------------------------------------------------------
|
|
584
533
|
// AgentReader / AgentWriter
|
|
585
534
|
// ---------------------------------------------------------------------------
|
|
586
535
|
const KNOWN_DIRS = new Set(["scripts", "references", "assets"]);
|
|
@@ -941,9 +890,15 @@ export class HelixExtension {
|
|
|
941
890
|
kernel.kind(new GenomeKind());
|
|
942
891
|
kernel.kind(new LayerPolicyKind());
|
|
943
892
|
kernel.kind(new AgentKind());
|
|
944
|
-
kernel.kind(new ToolKind());
|
|
945
893
|
kernel.kind(new ActorKind());
|
|
946
894
|
kernel.kind(new UseCaseKind());
|
|
895
|
+
// Tool (helix-tool) ships as a descriptor — helix/kinds/tool.kind.yaml
|
|
896
|
+
// (f-dna-tools-as-data / s-tool-kind-descriptor). It WAS a hand-written
|
|
897
|
+
// ToolKind class; migrated to a record-plane descriptor per the repo's
|
|
898
|
+
// own ratchet (record Kinds are data, not classes).
|
|
899
|
+
for (const raw of loadDescriptors(import.meta.url, "helix/kinds")) {
|
|
900
|
+
kernel.kindFromDescriptor(raw);
|
|
901
|
+
}
|
|
947
902
|
// 2026-05-26 — absorbed from claude-code-templates catalog (MIT).
|
|
948
903
|
// Setting rounds out the Claude-Code-customization primitives that
|
|
949
904
|
// live alongside Skill / UA / Soul / Tool.
|
package/dist/extensions/sdlc.js
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import { KindBase } from "../kernel/kind_base.js";
|
|
28
28
|
import { SD, TenantScope } from "../kernel/protocols.js";
|
|
29
29
|
import { loadDescriptors } from "../kernel/descriptor-loader.js";
|
|
30
|
+
import { HtmlArtifactSchema } from "../kernel/models.js";
|
|
30
31
|
const API_VERSION = "github.com/ruinosus/dna/sdlc/v1";
|
|
31
32
|
// v1.3: MILESTONE_STATUSES → EPIC_STATUSES (Jira/ADO alignment).
|
|
32
33
|
const EPIC_STATUSES = ["planning", "in-progress", "done", "cancelled", "deprecated"];
|
|
@@ -1280,6 +1281,161 @@ const JOURNEY_METHODOLOGIES = [
|
|
|
1280
1281
|
// the old class is frozen in sdk-py tests/test_kaizen_descriptor_equivalence.py
|
|
1281
1282
|
// + tests/sdlc.test.ts.
|
|
1282
1283
|
// ---------------------------------------------------------------------------
|
|
1284
|
+
// ---------------------------------------------------------------------------
|
|
1285
|
+
// HtmlArtifact — an HTML page as a first-class work-item output (record plane)
|
|
1286
|
+
//
|
|
1287
|
+
// s-dx-html-artifact-kind. 1:1 parity with Python HtmlArtifactKind. Bundle
|
|
1288
|
+
// Kind: ARTIFACT.html holds the raw HTML VERBATIM (byte-faithful round-trip —
|
|
1289
|
+
// no frontmatter injection), plus an optional artifact.json companion with
|
|
1290
|
+
// structured metadata (title, description, source, created_at) — the same
|
|
1291
|
+
// mechanic as a Soul (SOUL.md + soul.json). Custom reader/writer (not the
|
|
1292
|
+
// generic marker-frontmatter path) because the marker is arbitrary HTML.
|
|
1293
|
+
// ---------------------------------------------------------------------------
|
|
1294
|
+
class HtmlArtifactKind extends KindBase {
|
|
1295
|
+
apiVersion = API_VERSION;
|
|
1296
|
+
kind = "HtmlArtifact";
|
|
1297
|
+
// alias GENERATED = "<owner>-<kebab(kind)>" = "sdlc-html-artifact".
|
|
1298
|
+
// s-alias-generated-not-typed: new Kinds must NOT type an explicit alias
|
|
1299
|
+
// (EXPLICIT_ALIAS_ALLOWLIST ratchet is shrink-only). Empty here + aliasOwner
|
|
1300
|
+
// triggers generation at load time (Py twin sets alias=None + alias_owner).
|
|
1301
|
+
alias = "";
|
|
1302
|
+
aliasOwner = "sdlc";
|
|
1303
|
+
origin = "github.com/ruinosus/dna/sdlc";
|
|
1304
|
+
isPromptTarget = false;
|
|
1305
|
+
promptTargetPriority = 0;
|
|
1306
|
+
flattenInContext = false;
|
|
1307
|
+
plane = "record";
|
|
1308
|
+
storage = SD.bundle("html-artifacts", "ARTIFACT.html");
|
|
1309
|
+
graphStyle = { fill: "#EA580C", stroke: "#C2410C", textColor: "#fff" };
|
|
1310
|
+
asciiIcon = "📄";
|
|
1311
|
+
displayLabel = "HTML Artifacts";
|
|
1312
|
+
uiSchema = {
|
|
1313
|
+
html: {
|
|
1314
|
+
widget: "code",
|
|
1315
|
+
language: "html",
|
|
1316
|
+
label: "ARTIFACT.html",
|
|
1317
|
+
help: "The raw HTML page (stored byte-faithful).",
|
|
1318
|
+
height: 520,
|
|
1319
|
+
order: 10,
|
|
1320
|
+
},
|
|
1321
|
+
artifact_json: {
|
|
1322
|
+
widget: "code",
|
|
1323
|
+
language: "json",
|
|
1324
|
+
label: "artifact.json",
|
|
1325
|
+
help: "Structured metadata (title, description, source, created_at).",
|
|
1326
|
+
height: 220,
|
|
1327
|
+
order: 20,
|
|
1328
|
+
},
|
|
1329
|
+
};
|
|
1330
|
+
docs = "An HtmlArtifact stores an HTML page as a first-class, linkable output " +
|
|
1331
|
+
"of a work item (Story/Feature/Epic/Spike). Bundle: ARTIFACT.html holds " +
|
|
1332
|
+
"the raw HTML verbatim (byte-faithful round-trip) plus an optional " +
|
|
1333
|
+
"artifact.json companion with structured metadata (title, description, " +
|
|
1334
|
+
"source, created_at) — the same shape as a Soul's SOUL.md + soul.json. " +
|
|
1335
|
+
"Attach one with `dna sdlc produces add <WiKind>/<wi> HtmlArtifact/<name>`.";
|
|
1336
|
+
schema() {
|
|
1337
|
+
return {
|
|
1338
|
+
type: "object",
|
|
1339
|
+
additionalProperties: false,
|
|
1340
|
+
properties: {
|
|
1341
|
+
html: { type: "string", description: "The raw HTML document (byte-faithful)." },
|
|
1342
|
+
artifact_json: {
|
|
1343
|
+
type: "object",
|
|
1344
|
+
additionalProperties: true,
|
|
1345
|
+
description: "Structured metadata: title, description, source, created_at.",
|
|
1346
|
+
},
|
|
1347
|
+
},
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
parse(raw) {
|
|
1351
|
+
return HtmlArtifactSchema.parse(raw);
|
|
1352
|
+
}
|
|
1353
|
+
summary(doc) {
|
|
1354
|
+
const spec = (doc.spec ?? {});
|
|
1355
|
+
const aj = spec.artifact_json ?? {};
|
|
1356
|
+
return {
|
|
1357
|
+
title: aj.title ?? null,
|
|
1358
|
+
source: aj.source ?? null,
|
|
1359
|
+
created_at: aj.created_at ?? null,
|
|
1360
|
+
html_bytes: (spec.html ?? "").length,
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
preview(doc) {
|
|
1364
|
+
const spec = (doc.spec ?? {});
|
|
1365
|
+
const blocks = [];
|
|
1366
|
+
if (typeof spec.html === "string" && spec.html) {
|
|
1367
|
+
blocks.push({ kind: "code", title: "ARTIFACT.html", body: spec.html, language: "html" });
|
|
1368
|
+
}
|
|
1369
|
+
if (spec.artifact_json && typeof spec.artifact_json === "object") {
|
|
1370
|
+
blocks.push({
|
|
1371
|
+
kind: "code",
|
|
1372
|
+
title: "artifact.json",
|
|
1373
|
+
body: JSON.stringify(spec.artifact_json, null, 2),
|
|
1374
|
+
language: "json",
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
if (blocks.length === 0) {
|
|
1378
|
+
return [{ kind: "empty", title: "HtmlArtifact (empty)" }];
|
|
1379
|
+
}
|
|
1380
|
+
return blocks;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
class HtmlArtifactReader {
|
|
1384
|
+
async detect(bundle) {
|
|
1385
|
+
return bundle.exists("ARTIFACT.html");
|
|
1386
|
+
}
|
|
1387
|
+
async read(bundle) {
|
|
1388
|
+
const spec = {};
|
|
1389
|
+
const metadata = {};
|
|
1390
|
+
// ARTIFACT.html — read verbatim (byte-faithful; no frontmatter parse).
|
|
1391
|
+
if (await bundle.exists("ARTIFACT.html")) {
|
|
1392
|
+
spec.html = await bundle.readText("ARTIFACT.html");
|
|
1393
|
+
}
|
|
1394
|
+
// artifact.json companion — structured metadata.
|
|
1395
|
+
if (await bundle.exists("artifact.json")) {
|
|
1396
|
+
const aj = JSON.parse(await bundle.readText("artifact.json"));
|
|
1397
|
+
if (aj && typeof aj === "object" && !Array.isArray(aj)) {
|
|
1398
|
+
spec.artifact_json = aj;
|
|
1399
|
+
const desc = aj.description;
|
|
1400
|
+
if (typeof desc === "string" && desc && !metadata.description) {
|
|
1401
|
+
metadata.description = desc;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
if (metadata.name == null)
|
|
1406
|
+
metadata.name = bundle.name;
|
|
1407
|
+
return {
|
|
1408
|
+
apiVersion: API_VERSION,
|
|
1409
|
+
kind: "HtmlArtifact",
|
|
1410
|
+
metadata,
|
|
1411
|
+
spec,
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
class HtmlArtifactWriter {
|
|
1416
|
+
canWrite(raw) {
|
|
1417
|
+
return raw.kind === "HtmlArtifact";
|
|
1418
|
+
}
|
|
1419
|
+
serialize(raw) {
|
|
1420
|
+
const files = [];
|
|
1421
|
+
const spec = raw.spec ?? {};
|
|
1422
|
+
// ARTIFACT.html — verbatim HTML (byte-faithful).
|
|
1423
|
+
files.push({ relativePath: "ARTIFACT.html", content: spec.html ?? "" });
|
|
1424
|
+
// artifact.json — canonical JSON companion. metadata.description is a
|
|
1425
|
+
// DERIVED promotion of artifact_json.description on read, so it is NOT
|
|
1426
|
+
// re-emitted separately (no phantom frontmatter — F3 market-fidelity).
|
|
1427
|
+
const aj = spec.artifact_json;
|
|
1428
|
+
if (aj && typeof aj === "object") {
|
|
1429
|
+
files.push({ relativePath: "artifact.json", content: JSON.stringify(aj, null, 2) });
|
|
1430
|
+
}
|
|
1431
|
+
return files;
|
|
1432
|
+
}
|
|
1433
|
+
async write(bundle, raw) {
|
|
1434
|
+
for (const f of this.serialize(raw)) {
|
|
1435
|
+
await bundle.writeText(f.relativePath, f.content ?? "");
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1283
1439
|
export class SdlcExtension {
|
|
1284
1440
|
name = "sdlc";
|
|
1285
1441
|
// v1.14.0 — s-consolidate-cognitive-policies: the 9 cognitive policy
|
|
@@ -1302,6 +1458,12 @@ export class SdlcExtension {
|
|
|
1302
1458
|
// v1.10.0 — f-reference-citation-kind (ported from Python for
|
|
1303
1459
|
// s-alias-generated-not-typed: Spike.depFilters → "sdlc-reference").
|
|
1304
1460
|
kernel.kind(new ReferenceKind());
|
|
1461
|
+
// s-dx-html-artifact-kind — HTML page as a first-class work-item output.
|
|
1462
|
+
// Bundle Kind with a custom reader/writer for byte-faithful HTML,
|
|
1463
|
+
// mirroring the Soul (SOUL.md + soul.json) mechanic.
|
|
1464
|
+
kernel.kind(new HtmlArtifactKind());
|
|
1465
|
+
kernel.reader(new HtmlArtifactReader());
|
|
1466
|
+
kernel.writer(new HtmlArtifactWriter());
|
|
1305
1467
|
// expr batch B: PromptTemplate is a descriptor now — registered via the
|
|
1306
1468
|
// loadDescriptors loop below. s-consolidate-cognitive-policies: the
|
|
1307
1469
|
// cognitive policy family is ONE descriptor (cognitive-policy.kind.yaml).
|