@arcforge/cognet 2.0.115 → 2.0.117
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/package.json +4 -5
- package/src/host.ts +6 -6
- package/src/air/air.ts +0 -32
- package/src/air/grammar.ts +0 -58
- package/src/air/index.ts +0 -37
- package/src/air/interpolate/index.ts +0 -1
- package/src/air/interpolate/interpolate.ts +0 -178
- package/src/air/parse/index.ts +0 -274
- package/src/air/parse/scan.ts +0 -107
- package/src/air/protocol/classic.ts +0 -49
- package/src/air/protocol/index.ts +0 -1
- package/src/air/protocol/protocol.ts +0 -147
- package/src/air/protocol/sfc.ts +0 -96
- package/src/air/render/blocks.ts +0 -243
- package/src/air/render/index.ts +0 -51
- package/src/air/render/output.ts +0 -144
- package/src/air/render/text.ts +0 -85
- package/src/air/repair/index.ts +0 -1
- package/src/air/repair/repair.ts +0 -33
- package/src/air/types.ts +0 -88
package/src/air/render/index.ts
DELETED
|
@@ -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>
|
package/src/air/render/output.ts
DELETED
|
@@ -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
|
-
}
|
package/src/air/render/text.ts
DELETED
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
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, """).replace(/'/g, "'")
|
|
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, "&").replace(/</g, "<")
|
|
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
|
-
}
|
package/src/air/repair/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { repair } from "./repair"
|
package/src/air/repair/repair.ts
DELETED
|
@@ -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" }
|