@arcforge/cognet 2.0.115 → 2.0.116

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.
@@ -1,51 +0,0 @@
1
- import type { GrammarT } from "../grammar"
2
- import type { AirMessage, AirRenderInput } from "../types"
3
- import {
4
- renderContract,
5
- renderMeta,
6
- renderScope,
7
- renderSystem,
8
- renderTimeline,
9
- } from "./blocks"
10
-
11
- type RenderOpts = {
12
- grammar: GrammarT
13
- }
14
-
15
- /**
16
- * Render — domain in, ordered messages out. Pure.
17
- *
18
- * The caller passes what it holds (base string, AxonTool[], AxonEntry[]);
19
- * the block renderers own every translation into protocol shape. System
20
- * sections (meta, scope, system, contract) become individual system messages;
21
- * the timeline becomes a single user message — proper conversation structure
22
- * rather than one monolithic system prompt.
23
- *
24
- * Section order: <meta> → <scope> → <system> → <contract> → timeline
25
- */
26
- export function Render(opts: RenderOpts) {
27
- const { grammar } = opts
28
-
29
- return {
30
- render(input: AirRenderInput): AirMessage[] {
31
- const messages: AirMessage[] = []
32
-
33
- const sys = (content: string) => {
34
- if (content) messages.push({ role: "system", content })
35
- }
36
-
37
- sys(renderMeta(grammar))
38
- if (input.scope) sys(renderScope(input.scope))
39
- sys(renderSystem(input.base))
40
- sys(renderContract(grammar))
41
-
42
- if (input.history && input.history.length > 0) {
43
- messages.push({ role: "user", content: renderTimeline(input.history, grammar) })
44
- }
45
-
46
- return messages
47
- },
48
- }
49
- }
50
-
51
- export type RenderT = ReturnType<typeof Render>
@@ -1,144 +0,0 @@
1
- import { formatBytes } from "./text"
2
-
3
- /**
4
- * Capsule output projection — how raw capsule REPL output looks to the model.
5
- *
6
- * The capsule REPL emits a JSONL op-record envelope for every module function
7
- * call, followed by the raw auto-logged return value. This detects that
8
- * envelope, extracts the meaningful content per op type, and discards the
9
- * noise.
10
- *
11
- * For content-returning ops (fs.read, fs.list, ...) — show the content.
12
- * For mutation ops (fs.write, fs.mkdir, ...) — show a compact tick line.
13
- * For anything else — pass through unchanged.
14
- *
15
- * The silent catch fall-throughs here are correct, not masked failures:
16
- * this is a best-effort display projection where "show the raw string"
17
- * is the honest fallback for anything that doesn't parse.
18
- */
19
- export function formatCapsuleOutput(content: string): string {
20
- const raw = content.trim()
21
-
22
- // Plain JSON array — e.g. fs.list() return value serialised directly
23
- if (raw.startsWith("[")) {
24
- try {
25
- const arr = JSON.parse(raw)
26
- if (Array.isArray(arr)) {
27
- if (arr.length === 0) return "(empty)"
28
- // DirEntry[] — render as a compact directory listing
29
- if (
30
- arr.length > 0 &&
31
- typeof arr[0] === "object" &&
32
- arr[0] !== null &&
33
- "name" in arr[0]
34
- ) {
35
- return arr
36
- .map((e: any) => `${e.type === "directory" ? "d" : "-"} ${e.name}`)
37
- .join("\n")
38
- }
39
- // Generic array of primitives or unknown objects
40
- return arr
41
- .map((e: any) => (typeof e === "object" ? JSON.stringify(e) : String(e)))
42
- .join("\n")
43
- }
44
- } catch {
45
- /* not JSON — fall through to raw */
46
- }
47
- return raw
48
- }
49
-
50
- if (!raw.startsWith("{")) return raw
51
-
52
- // Extract all leading JSON op-record objects.
53
- // Everything after the last op record (the raw auto-logged return value)
54
- // is discarded — the meaningful content comes from the op record's data field.
55
- const opRecords: any[] = []
56
- let i = 0
57
- while (i < raw.length && raw[i] === "{") {
58
- let depth = 0
59
- let j = i
60
- while (j < raw.length) {
61
- const ch = raw[j]
62
- if (ch === '"') {
63
- // skip over string contents, respecting escape sequences
64
- j++
65
- while (j < raw.length) {
66
- if (raw[j] === "\\") {
67
- j += 2
68
- continue
69
- }
70
- if (raw[j] === '"') {
71
- j++
72
- break
73
- }
74
- j++
75
- }
76
- continue
77
- }
78
- if (ch === "{") depth++
79
- else if (ch === "}") {
80
- depth--
81
- if (depth === 0) {
82
- j++
83
- break
84
- }
85
- }
86
- j++
87
- }
88
- try {
89
- const obj = JSON.parse(raw.slice(i, j))
90
- if ("op" in obj) opRecords.push(obj)
91
- else if ("procId" in obj && "command" in obj) {
92
- const tail = obj.tail ? `\n${obj.tail}` : ""
93
- return `spawned ${obj.command} procId=${obj.procId} pid=${obj.pid ?? "?"} status=${obj.status ?? "running"}${tail}`
94
- } else break
95
- } catch {
96
- break
97
- }
98
- i = j
99
- while (i < raw.length && (raw[i] === "\n" || raw[i] === "\r")) i++
100
- }
101
-
102
- if (opRecords.length === 0) return raw
103
-
104
- return opRecords
105
- .map(obj => {
106
- const op: string = obj.op ?? ""
107
- const d = obj.data ?? {}
108
-
109
- if (!obj.ok) {
110
- return `${op} ${d.path ?? ""} ✗ ${obj.error ?? d.message ?? "failed"}`
111
- }
112
-
113
- switch (op) {
114
- case "fs.read":
115
- case "fs.readLines":
116
- return `read ${d.path} ${formatBytes(d.bytes ?? 0)}\n${d.content ?? ""}`
117
- case "fs.list": {
118
- const entries: any[] = d.entries ?? []
119
- const lines = entries
120
- .map((e: any) => {
121
- const prefix = e.type === "directory" ? "d" : "-"
122
- return `${prefix} ${e.name}`
123
- })
124
- .join("\n")
125
- return `list ${d.path} ${entries.length} entries\n${lines}`
126
- }
127
- case "fs.write":
128
- return `write ${d.path} ${formatBytes(d.bytes ?? 0)} ✓`
129
- case "fs.mkdir":
130
- return `mkdir ${d.path} ✓`
131
- case "fs.delete":
132
- return `rm ${d.path} ✓`
133
- case "fs.move":
134
- return `mv ${d.src} → ${d.dest} ✓`
135
- case "fs.copy":
136
- return `cp ${d.src} → ${d.dest} ✓`
137
- case "fs.cd":
138
- return `cd ${d.path} ✓`
139
- default:
140
- return `${op} ${JSON.stringify(d)} ✓`
141
- }
142
- })
143
- .join("\n")
144
- }
@@ -1,85 +0,0 @@
1
- /** Pure string utilities for AIR rendering. */
2
-
3
- /** Escape XML text content (prose, stdout, user messages). */
4
- export function esc(s: string): string {
5
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
6
- }
7
-
8
- /** Escape a trusted value for a double-quoted XML attribute. */
9
- export function escAttr(s: string): string {
10
- return esc(s).replace(/"/g, "&quot;").replace(/'/g, "&apos;")
11
- }
12
-
13
- /** Escape XML inside code blocks — only & and <. Never > (breaks =>, generics). */
14
- export function escCode(s: string): string {
15
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;")
16
- }
17
-
18
- /** Indent every line of s by `spaces` spaces. */
19
- export function indent(s: string, spaces: number): string {
20
- const pad = " ".repeat(spaces)
21
- return s
22
- .split("\n")
23
- .map(line => pad + line)
24
- .join("\n")
25
- }
26
-
27
- export function formatBytes(n: number): string {
28
- return n >= 1024 ? `${(n / 1024).toFixed(1)}K` : `${n}B`
29
- }
30
-
31
- /**
32
- * Normalize code that may contain literal \n or \t escape sequences outside
33
- * of string literals (a common model mistake). Replaces them with real
34
- * whitespace so the timeline shows clean, executable code.
35
- *
36
- * Best-effort heuristic: only acts on \n/\t outside single-quoted,
37
- * double-quoted, or template-literal strings.
38
- */
39
- export function normalizeCode(code: string): string {
40
- // Fast path: no escape sequences at all
41
- if (!code.includes("\\n") && !code.includes("\\t")) return code
42
-
43
- let result = ""
44
- let i = 0
45
- while (i < code.length) {
46
- const c = code[i]
47
- // Track string boundaries to avoid replacing inside strings
48
- if (c === '"' || c === "'" || c === "`") {
49
- const quote = c
50
- result += c
51
- i++
52
- while (i < code.length) {
53
- const sc = code[i]
54
- if (sc === "\\") {
55
- // Keep escape sequences inside strings as-is
56
- result += code[i] + (code[i + 1] ?? "")
57
- i += 2
58
- } else if (sc === quote) {
59
- result += sc
60
- i++
61
- break
62
- } else {
63
- result += sc
64
- i++
65
- }
66
- }
67
- } else if (c === "\\" && i + 1 < code.length) {
68
- const next = code[i + 1]
69
- if (next === "n") {
70
- result += "\n"
71
- i += 2
72
- } else if (next === "t") {
73
- result += "\t"
74
- i += 2
75
- } else {
76
- result += c + next
77
- i += 2
78
- }
79
- } else {
80
- result += c
81
- i++
82
- }
83
- }
84
- return result
85
- }
@@ -1 +0,0 @@
1
- export { repair } from "./repair"
@@ -1,33 +0,0 @@
1
- /**
2
- * Deterministic, pre-parse repair of raw model output.
3
- *
4
- * Scope is deliberately narrow: string-only fixes for mistakes that are
5
- * common and unambiguous to correct. No semantic guessing, no model calls.
6
- * If a fix isn't mechanically certain, leave the text alone — the parser's
7
- * own incomplete/error reporting is the honest fallback, and that goes back
8
- * to the model as an `agent:output:error` for it to correct itself.
9
- *
10
- * Applied to the full buffered response before it reaches the AIR parser.
11
- */
12
-
13
- const KNOWN_TAGS = ["text", "thinking", "typescript", "script", "template"] as const
14
-
15
- export function repair(raw: string): string {
16
- return normalizeTagCase(raw)
17
- }
18
-
19
- /**
20
- * Lowercase tag names the model emitted in the wrong case
21
- * (`<Text>`, `<TYPESCRIPT>`) — case is not semantically meaningful here,
22
- * so normalizing it can never change intent, only make it parseable.
23
- */
24
- function normalizeTagCase(raw: string): string {
25
- let out = raw
26
- for (const tag of KNOWN_TAGS) {
27
- const open = new RegExp(`<(${tag})(\\s[^>]*)?>`, "gi")
28
- const close = new RegExp(`</(${tag})>`, "gi")
29
- out = out.replace(open, (match, _name, attrs) => `<${tag}${attrs ?? ""}>`)
30
- out = out.replace(close, `</${tag}>`)
31
- }
32
- return out
33
- }
package/src/air/types.ts DELETED
@@ -1,88 +0,0 @@
1
- /**
2
- * AIR — Agent Intermediate Representation.
3
- *
4
- * A general-purpose LLM protocol: hand the renderer Axon's domain (base
5
- * context, declared tools, an event history) and it produces the ordered
6
- * messages a model sees. AIR owns BOTH halves — render (what the model
7
- * sees) and parse (what it emits back) — from one grammar, so they cannot
8
- * drift.
9
- *
10
- * The render boundary is DOMAIN in, messages out: callers pass AxonTool[]
11
- * and AxonEntry[], never AIR's internal render vocabulary. The
12
- * timeline item shapes below are private to render/ — the exhaustive
13
- * AxonEntry → item translation lives there, next to the parser it
14
- * must agree with.
15
- */
16
-
17
- import type { AxonEntry, AxonScope } from "@arcforge/types"
18
-
19
- // ── Messages ─────────────────────────────────────────────────────────────────
20
-
21
- export type AirMessage = {
22
- role: "system" | "user" | "assistant"
23
- content: string
24
- }
25
-
26
- // ── Protocols (output grammar) ───────────────────────────────────────────────
27
-
28
- /**
29
- * The named output grammars.
30
- *
31
- * classic — <typescript> acts, <text> speaks. Two independent blocks.
32
- * sfc — <script> computes, <template> speaks. One response, script first.
33
- * raw — no grammar; the whole reply is the message. For internal calls.
34
- */
35
- export type AirProtocolName = "classic" | "sfc" | "raw"
36
-
37
- export type AirModeType = "text" | "typescript" | "script" | "template"
38
-
39
- export type AirMode = {
40
- /** The output type this mode produces. */
41
- type: AirModeType
42
- /** Optional description override. Falls back to the default for the type. */
43
- description?: string
44
- }
45
-
46
- /** Template languages. Selects the interpolation serializer. */
47
- export type AirTemplateLang = "md" | "json"
48
-
49
- // ── Render input ───────────────────────────────────────────────────────────
50
- //
51
- // DOMAIN in — the caller passes what it already holds. AIR owns every
52
- // translation into protocol shape: tools → <scope> declarations, entries →
53
- // <timeline> items. A cognet curates (which entries, what order, elided how)
54
- // and hands the lists over; it never manufactures AIR-internal types.
55
-
56
- export type AirRenderInput = {
57
- /** Base context — the agent's identity contract. Rendered as <system>. */
58
- base?: string
59
- /** Capsule-implemented globals. Rendered as <scope lang="ts">. */
60
- scope?: AxonScope
61
- /** The event history to render, already curated by the cognet. Rendered as <timeline>. */
62
- history?: readonly AxonEntry[]
63
- }
64
-
65
- // ── Parser output ────────────────────────────────────────────────────────────
66
-
67
- /**
68
- * Events emitted by the streaming AIR parser.
69
- *
70
- * *:delta — tokens inside a streamable block (<text>, <thinking>), real time.
71
- * *:done — a block closed; content is the full inner text.
72
- * `incomplete: true` means the stream ended without the closing tag —
73
- * callers must treat these as format errors, never as valid actions.
74
- * done — a <done/> self-closing tag was encountered.
75
- */
76
- export type AirBlockEvent =
77
- | { type: "text:delta"; content: string }
78
- | { type: "text:done"; content: string; incomplete?: true }
79
- | { type: "thinking:delta"; content: string }
80
- | { type: "thinking:done"; content: string; incomplete?: true }
81
- | { type: "typescript:done"; content: string; incomplete?: true }
82
- // SFC. script:done is the suspension point — the consumer runs it and
83
- // resolves a scope before template deltas may be released.
84
- | { type: "script:done"; content: string; incomplete?: true }
85
- | { type: "template:open"; lang: AirTemplateLang }
86
- | { type: "template:delta"; content: string }
87
- | { type: "template:done"; content: string; incomplete?: true }
88
- | { type: "done" }