@nifrajs/coding-agent 3.2.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.
Files changed (211) hide show
  1. package/EXTENSIONS.md +25 -0
  2. package/PERFORMANCE.md +19 -0
  3. package/README.md +69 -0
  4. package/REGISTRY.md +15 -0
  5. package/SECURITY.md +32 -0
  6. package/dist/approvals.d.ts +70 -0
  7. package/dist/approvals.d.ts.map +1 -0
  8. package/dist/approvals.js +174 -0
  9. package/dist/approvals.js.map +1 -0
  10. package/dist/capabilities.d.ts +20 -0
  11. package/dist/capabilities.d.ts.map +1 -0
  12. package/dist/capabilities.js +49 -0
  13. package/dist/capabilities.js.map +1 -0
  14. package/dist/cli.d.ts +24 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +380 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/context.d.ts +17 -0
  19. package/dist/context.d.ts.map +1 -0
  20. package/dist/context.js +66 -0
  21. package/dist/context.js.map +1 -0
  22. package/dist/deployment-adapters.d.ts +40 -0
  23. package/dist/deployment-adapters.d.ts.map +1 -0
  24. package/dist/deployment-adapters.js +147 -0
  25. package/dist/deployment-adapters.js.map +1 -0
  26. package/dist/diff.d.ts +17 -0
  27. package/dist/diff.d.ts.map +1 -0
  28. package/dist/diff.js +61 -0
  29. package/dist/diff.js.map +1 -0
  30. package/dist/errors.d.ts +9 -0
  31. package/dist/errors.d.ts.map +1 -0
  32. package/dist/errors.js +35 -0
  33. package/dist/errors.js.map +1 -0
  34. package/dist/extensions.d.ts +104 -0
  35. package/dist/extensions.d.ts.map +1 -0
  36. package/dist/extensions.js +284 -0
  37. package/dist/extensions.js.map +1 -0
  38. package/dist/handoffs.d.ts +97 -0
  39. package/dist/handoffs.d.ts.map +1 -0
  40. package/dist/handoffs.js +199 -0
  41. package/dist/handoffs.js.map +1 -0
  42. package/dist/healing.d.ts +56 -0
  43. package/dist/healing.d.ts.map +1 -0
  44. package/dist/healing.js +54 -0
  45. package/dist/healing.js.map +1 -0
  46. package/dist/host.d.ts +61 -0
  47. package/dist/host.d.ts.map +1 -0
  48. package/dist/host.js +369 -0
  49. package/dist/host.js.map +1 -0
  50. package/dist/index.d.ts +26 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +26 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/isolated-worker.d.ts +2 -0
  55. package/dist/isolated-worker.d.ts.map +1 -0
  56. package/dist/isolated-worker.js +155 -0
  57. package/dist/isolated-worker.js.map +1 -0
  58. package/dist/isolated.d.ts +52 -0
  59. package/dist/isolated.d.ts.map +1 -0
  60. package/dist/isolated.js +293 -0
  61. package/dist/isolated.js.map +1 -0
  62. package/dist/native.d.ts +92 -0
  63. package/dist/native.d.ts.map +1 -0
  64. package/dist/native.js +339 -0
  65. package/dist/native.js.map +1 -0
  66. package/dist/orchestration/artifact-port.d.ts +30 -0
  67. package/dist/orchestration/artifact-port.d.ts.map +1 -0
  68. package/dist/orchestration/artifact-port.js +59 -0
  69. package/dist/orchestration/artifact-port.js.map +1 -0
  70. package/dist/orchestration/catalog.d.ts +71 -0
  71. package/dist/orchestration/catalog.d.ts.map +1 -0
  72. package/dist/orchestration/catalog.js +46 -0
  73. package/dist/orchestration/catalog.js.map +1 -0
  74. package/dist/orchestration/compile.d.ts +59 -0
  75. package/dist/orchestration/compile.d.ts.map +1 -0
  76. package/dist/orchestration/compile.js +268 -0
  77. package/dist/orchestration/compile.js.map +1 -0
  78. package/dist/orchestration/dispatch.d.ts +115 -0
  79. package/dist/orchestration/dispatch.d.ts.map +1 -0
  80. package/dist/orchestration/dispatch.js +220 -0
  81. package/dist/orchestration/dispatch.js.map +1 -0
  82. package/dist/orchestration/durable-jobs.d.ts +35 -0
  83. package/dist/orchestration/durable-jobs.d.ts.map +1 -0
  84. package/dist/orchestration/durable-jobs.js +273 -0
  85. package/dist/orchestration/durable-jobs.js.map +1 -0
  86. package/dist/orchestration/effect-key.d.ts +23 -0
  87. package/dist/orchestration/effect-key.d.ts.map +1 -0
  88. package/dist/orchestration/effect-key.js +24 -0
  89. package/dist/orchestration/effect-key.js.map +1 -0
  90. package/dist/orchestration/evidence-store.d.ts +75 -0
  91. package/dist/orchestration/evidence-store.d.ts.map +1 -0
  92. package/dist/orchestration/evidence-store.js +183 -0
  93. package/dist/orchestration/evidence-store.js.map +1 -0
  94. package/dist/orchestration/hash.d.ts +12 -0
  95. package/dist/orchestration/hash.d.ts.map +1 -0
  96. package/dist/orchestration/hash.js +43 -0
  97. package/dist/orchestration/hash.js.map +1 -0
  98. package/dist/orchestration/host.d.ts +121 -0
  99. package/dist/orchestration/host.d.ts.map +1 -0
  100. package/dist/orchestration/host.js +334 -0
  101. package/dist/orchestration/host.js.map +1 -0
  102. package/dist/orchestration/index.d.ts +20 -0
  103. package/dist/orchestration/index.d.ts.map +1 -0
  104. package/dist/orchestration/index.js +19 -0
  105. package/dist/orchestration/index.js.map +1 -0
  106. package/dist/orchestration/policy.d.ts +63 -0
  107. package/dist/orchestration/policy.d.ts.map +1 -0
  108. package/dist/orchestration/policy.js +80 -0
  109. package/dist/orchestration/policy.js.map +1 -0
  110. package/dist/orchestration/recovery.d.ts +87 -0
  111. package/dist/orchestration/recovery.d.ts.map +1 -0
  112. package/dist/orchestration/recovery.js +197 -0
  113. package/dist/orchestration/recovery.js.map +1 -0
  114. package/dist/orchestration/tracer.d.ts +31 -0
  115. package/dist/orchestration/tracer.d.ts.map +1 -0
  116. package/dist/orchestration/tracer.js +141 -0
  117. package/dist/orchestration/tracer.js.map +1 -0
  118. package/dist/plans.d.ts +35 -0
  119. package/dist/plans.d.ts.map +1 -0
  120. package/dist/plans.js +63 -0
  121. package/dist/plans.js.map +1 -0
  122. package/dist/presets.d.ts +10 -0
  123. package/dist/presets.d.ts.map +1 -0
  124. package/dist/presets.js +70 -0
  125. package/dist/presets.js.map +1 -0
  126. package/dist/process.d.ts +6 -0
  127. package/dist/process.d.ts.map +1 -0
  128. package/dist/process.js +42 -0
  129. package/dist/process.js.map +1 -0
  130. package/dist/registry.d.ts +37 -0
  131. package/dist/registry.d.ts.map +1 -0
  132. package/dist/registry.js +51 -0
  133. package/dist/registry.js.map +1 -0
  134. package/dist/replay.d.ts +24 -0
  135. package/dist/replay.d.ts.map +1 -0
  136. package/dist/replay.js +154 -0
  137. package/dist/replay.js.map +1 -0
  138. package/dist/rpc.d.ts +51 -0
  139. package/dist/rpc.d.ts.map +1 -0
  140. package/dist/rpc.js +412 -0
  141. package/dist/rpc.js.map +1 -0
  142. package/dist/session-migration.d.ts +59 -0
  143. package/dist/session-migration.d.ts.map +1 -0
  144. package/dist/session-migration.js +315 -0
  145. package/dist/session-migration.js.map +1 -0
  146. package/dist/sessions.d.ts +74 -0
  147. package/dist/sessions.d.ts.map +1 -0
  148. package/dist/sessions.js +222 -0
  149. package/dist/sessions.js.map +1 -0
  150. package/dist/subagents.d.ts +51 -0
  151. package/dist/subagents.d.ts.map +1 -0
  152. package/dist/subagents.js +141 -0
  153. package/dist/subagents.js.map +1 -0
  154. package/dist/tools.d.ts +13 -0
  155. package/dist/tools.d.ts.map +1 -0
  156. package/dist/tools.js +37 -0
  157. package/dist/tools.js.map +1 -0
  158. package/dist/ui.d.ts +42 -0
  159. package/dist/ui.d.ts.map +1 -0
  160. package/dist/ui.js +93 -0
  161. package/dist/ui.js.map +1 -0
  162. package/dist/verification.d.ts +34 -0
  163. package/dist/verification.d.ts.map +1 -0
  164. package/dist/verification.js +91 -0
  165. package/dist/verification.js.map +1 -0
  166. package/dist/workflows.d.ts +98 -0
  167. package/dist/workflows.d.ts.map +1 -0
  168. package/dist/workflows.js +174 -0
  169. package/dist/workflows.js.map +1 -0
  170. package/package.json +84 -0
  171. package/src/approvals.ts +249 -0
  172. package/src/capabilities.ts +74 -0
  173. package/src/cli.ts +405 -0
  174. package/src/context.ts +81 -0
  175. package/src/deployment-adapters.ts +181 -0
  176. package/src/diff.ts +80 -0
  177. package/src/errors.ts +46 -0
  178. package/src/extensions.ts +402 -0
  179. package/src/handoffs.ts +299 -0
  180. package/src/healing.ts +93 -0
  181. package/src/host.ts +465 -0
  182. package/src/index.ts +165 -0
  183. package/src/isolated-worker.ts +177 -0
  184. package/src/isolated.ts +344 -0
  185. package/src/native.ts +478 -0
  186. package/src/orchestration/artifact-port.ts +75 -0
  187. package/src/orchestration/catalog.ts +102 -0
  188. package/src/orchestration/compile.ts +373 -0
  189. package/src/orchestration/dispatch.ts +383 -0
  190. package/src/orchestration/durable-jobs.ts +374 -0
  191. package/src/orchestration/effect-key.ts +35 -0
  192. package/src/orchestration/evidence-store.ts +233 -0
  193. package/src/orchestration/hash.ts +43 -0
  194. package/src/orchestration/host.ts +445 -0
  195. package/src/orchestration/index.ts +131 -0
  196. package/src/orchestration/policy.ts +116 -0
  197. package/src/orchestration/recovery.ts +271 -0
  198. package/src/orchestration/tracer.ts +177 -0
  199. package/src/plans.ts +105 -0
  200. package/src/presets.ts +90 -0
  201. package/src/process.ts +47 -0
  202. package/src/registry.ts +80 -0
  203. package/src/replay.ts +190 -0
  204. package/src/rpc.ts +598 -0
  205. package/src/session-migration.ts +401 -0
  206. package/src/sessions.ts +303 -0
  207. package/src/subagents.ts +210 -0
  208. package/src/tools.ts +52 -0
  209. package/src/ui.ts +137 -0
  210. package/src/verification.ts +133 -0
  211. package/src/workflows.ts +279 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Capability-descriptor adapter for coding-agent extensions.
3
+ *
4
+ * An extension is host-supplied code that runs with granted capabilities, so its descriptor is
5
+ * projected under the `extension` kind and, unlike a typed tool, admits an opaque input. The adapter
6
+ * fails closed on capability escalation: an extension must declare its capabilities explicitly (an
7
+ * empty array asserts "none"), and every declared capability must appear in the host's trusted
8
+ * allowlist. An omitted declaration or any capability outside the allowlist raises
9
+ * `capability_escalation` rather than silently narrowing or widening the grant.
10
+ *
11
+ * This module is the only place `@nifrajs/coding-agent` reaches `@nifrajs/agent`; the edge is
12
+ * `coding-agent -> agent`, never the reverse, so the agent package stays free of coding-agent code.
13
+ */
14
+
15
+ import {
16
+ type ApprovalClass,
17
+ type CapabilityDescriptor,
18
+ composeDescriptor,
19
+ type IsolationClass,
20
+ RegistryError,
21
+ } from "@nifrajs/agent/registry"
22
+
23
+ /** Opaque extension input. Extensions do not publish a typed schema, so identity rests on name and grant. */
24
+ const EXTENSION_INPUT_SCHEMA: Record<string, unknown> = Object.freeze({
25
+ type: "object",
26
+ additionalProperties: true,
27
+ })
28
+
29
+ /** The minimal shape an extension exposes for projection: an identity and an explicit capability grant. */
30
+ export interface ExtensionDescriptorSource {
31
+ readonly name: string
32
+ readonly capabilities?: readonly string[]
33
+ }
34
+
35
+ export interface ExtensionDescriptorOptions {
36
+ /** The host's trusted capability allowlist. A declared capability outside it fails closed. */
37
+ readonly trustedCapabilities: readonly string[]
38
+ readonly version?: string
39
+ readonly approval?: ApprovalClass
40
+ readonly isolation?: IsolationClass
41
+ }
42
+
43
+ /**
44
+ * Project one extension into an `extension` capability descriptor, enforcing the grant. The declared
45
+ * capabilities become the descriptor's required capabilities only after every one is confirmed
46
+ * trusted; an undeclared grant or an escalated capability throws before any descriptor is built.
47
+ * Approval defaults to required and isolation to process, matching how untyped host code is admitted.
48
+ */
49
+ export function extensionDescriptor(
50
+ extension: ExtensionDescriptorSource,
51
+ options: ExtensionDescriptorOptions,
52
+ ): Promise<CapabilityDescriptor> {
53
+ const requested = extension.capabilities
54
+ if (requested === undefined) throw new RegistryError("capability_escalation")
55
+ const trusted = new Set(options.trustedCapabilities)
56
+ for (const capability of requested) {
57
+ if (!trusted.has(capability)) throw new RegistryError("capability_escalation")
58
+ }
59
+ return composeDescriptor({
60
+ kind: "extension",
61
+ name: extension.name,
62
+ ...(options.version === undefined ? {} : { version: options.version }),
63
+ inputSchema: EXTENSION_INPUT_SCHEMA,
64
+ requiredCapabilities: requested,
65
+ approval: options.approval ?? { kind: "required" },
66
+ retry: "none",
67
+ idempotency: "none",
68
+ isolation: options.isolation ?? "process",
69
+ })
70
+ }
71
+
72
+ /** Project a set of extensions under a single trusted allowlist, preserving order. */
73
+ export function extensionDescriptors(
74
+ extensions: readonly ExtensionDescriptorSource[],
75
+ options: ExtensionDescriptorOptions,
76
+ ): Promise<readonly CapabilityDescriptor[]> {
77
+ return Promise.all(extensions.map((extension) => extensionDescriptor(extension, options)))
78
+ }
79
+
80
+ export type { CapabilityDescriptor }
package/src/replay.ts ADDED
@@ -0,0 +1,190 @@
1
+ import {
2
+ type AgentBackend,
3
+ type AgentBackendInfo,
4
+ type AgentEvent,
5
+ type AgentEventStream,
6
+ type AgentSessionSnapshot,
7
+ agentError,
8
+ type CreateSessionInput,
9
+ createAgentEventStream,
10
+ type ReloadResult,
11
+ type SendMessageInput,
12
+ } from "@nifrajs/agent-protocol"
13
+
14
+ export interface ReplayBackendOptions {
15
+ readonly events: readonly AgentEvent[]
16
+ readonly delayMs?: number
17
+ }
18
+
19
+ interface ReplaySession {
20
+ readonly id: string
21
+ snapshot: AgentSessionSnapshot
22
+ active: AgentEventStream | undefined
23
+ closed: boolean
24
+ seq: number
25
+ }
26
+
27
+ /** Deterministic protocol backend for demos, CI, and UI regression tests. */
28
+ export class ReplayBackend implements AgentBackend {
29
+ readonly info: AgentBackendInfo = Object.freeze({
30
+ name: "replay",
31
+ capabilities: Object.freeze(["sessions", "streaming", "replay"]),
32
+ })
33
+ private readonly events: readonly AgentEvent[]
34
+ private readonly delayMs: number
35
+ private readonly sessions = new Map<string, ReplaySession>()
36
+
37
+ constructor(options: ReplayBackendOptions) {
38
+ this.events = Object.freeze(options.events.map((event) => Object.freeze({ ...event })))
39
+ this.delayMs = options.delayMs ?? 0
40
+ if (!Number.isSafeInteger(this.delayMs) || this.delayMs < 0 || this.delayMs > 60_000)
41
+ throw new RangeError("replay backend: delayMs must be between 0 and 60000")
42
+ }
43
+
44
+ async createSession(input: CreateSessionInput): Promise<AgentSessionSnapshot> {
45
+ const id = input.sessionId ?? crypto.randomUUID()
46
+ if (!/^[a-zA-Z0-9._:-]{1,128}$/.test(id))
47
+ throw new TypeError("replay backend: invalid sessionId")
48
+ if (this.sessions.has(id)) throw new Error(`replay backend: session already exists: ${id}`)
49
+ const now = Date.now()
50
+ const snapshot: AgentSessionSnapshot = {
51
+ version: 1,
52
+ id,
53
+ backend: this.info.name,
54
+ cwd: input.cwd,
55
+ status: "idle",
56
+ createdAt: now,
57
+ updatedAt: now,
58
+ lastSeq: 0,
59
+ capabilities: Object.freeze([...(input.capabilities ?? this.info.capabilities)]),
60
+ }
61
+ this.sessions.set(id, { id, snapshot, active: undefined, closed: false, seq: 0 })
62
+ return snapshot
63
+ }
64
+
65
+ send(input: SendMessageInput): AsyncIterable<AgentEvent> {
66
+ const session = this.requireSession(input.sessionId)
67
+ if (session.closed)
68
+ return failedStream(agentError("SESSION_CLOSED", "replay session is closed"))
69
+ if (session.active !== undefined)
70
+ return failedStream(agentError("SESSION_BUSY", "replay session is busy"))
71
+ const stream = createAgentEventStream()
72
+ session.active = stream
73
+ void this.replay(session, stream)
74
+ return stream
75
+ }
76
+
77
+ async cancel(sessionId: string, reason = "cancelled"): Promise<void> {
78
+ const session = this.requireSession(sessionId)
79
+ session.active?.complete()
80
+ session.active = undefined
81
+ session.snapshot = Object.freeze({
82
+ ...session.snapshot,
83
+ status: "stopped",
84
+ updatedAt: Date.now(),
85
+ })
86
+ if (session.seq === 0) this.push(session, { type: "session.stopped", reason })
87
+ }
88
+
89
+ async snapshot(sessionId: string): Promise<AgentSessionSnapshot> {
90
+ return this.requireSession(sessionId).snapshot
91
+ }
92
+
93
+ async reload(sessionId: string): Promise<ReloadResult> {
94
+ const session = this.requireSession(sessionId)
95
+ const revision = `replay:${this.events.length}`
96
+ session.snapshot = Object.freeze({
97
+ ...session.snapshot,
98
+ extensionRevision: revision,
99
+ updatedAt: Date.now(),
100
+ })
101
+ return { revision, loaded: [], disabled: [], rolledBack: false }
102
+ }
103
+
104
+ async close(sessionId: string): Promise<void> {
105
+ const session = this.requireSession(sessionId)
106
+ session.closed = true
107
+ session.active?.complete()
108
+ session.active = undefined
109
+ this.sessions.delete(sessionId)
110
+ }
111
+
112
+ private async replay(session: ReplaySession, stream: AgentEventStream): Promise<void> {
113
+ session.snapshot = Object.freeze({
114
+ ...session.snapshot,
115
+ status: "running",
116
+ updatedAt: Date.now(),
117
+ })
118
+ try {
119
+ for (const source of this.events) {
120
+ if (this.delayMs > 0) await new Promise((resolve) => setTimeout(resolve, this.delayMs))
121
+ this.push(session, remap(source, session.id, session.seq))
122
+ }
123
+ session.snapshot = Object.freeze({
124
+ ...session.snapshot,
125
+ status: "idle",
126
+ updatedAt: Date.now(),
127
+ })
128
+ this.push(session, { type: "session.completed", snapshot: session.snapshot })
129
+ } catch (error) {
130
+ this.push(session, {
131
+ type: "session.failed",
132
+ error: agentError("REPLAY_FAILED", error instanceof Error ? error.message : String(error)),
133
+ recoverable: true,
134
+ })
135
+ } finally {
136
+ stream.complete()
137
+ session.active = undefined
138
+ }
139
+ }
140
+
141
+ private push(
142
+ session: ReplaySession,
143
+ event: AgentEvent | import("@nifrajs/agent-protocol").AgentEventPayload,
144
+ ): void {
145
+ const next =
146
+ "version" in event && "sessionId" in event
147
+ ? event
148
+ : { version: 1 as const, sessionId: session.id, seq: session.seq, at: Date.now(), ...event }
149
+ session.seq = next.seq + 1
150
+ session.snapshot = Object.freeze({ ...session.snapshot, lastSeq: next.seq, updatedAt: next.at })
151
+ session.active?.push(next as AgentEvent)
152
+ }
153
+
154
+ private requireSession(id: string): ReplaySession {
155
+ const session = this.sessions.get(id)
156
+ if (session === undefined) throw new Error(`replay backend: unknown session: ${id}`)
157
+ return session
158
+ }
159
+ }
160
+
161
+ export async function readReplayEvents(path: string): Promise<readonly AgentEvent[]> {
162
+ const text = await Bun.file(path).text()
163
+ const events: AgentEvent[] = []
164
+ for (const line of text.split("\n")) {
165
+ if (line.trim().length === 0) continue
166
+ const value: unknown = JSON.parse(line)
167
+ if (!isAgentEvent(value)) throw new Error(`replay: invalid event in ${path}`)
168
+ events.push(value)
169
+ }
170
+ return Object.freeze(events)
171
+ }
172
+
173
+ function remap(event: AgentEvent, sessionId: string, seq: number): AgentEvent {
174
+ return Object.freeze({ ...event, sessionId, seq, at: Date.now() })
175
+ }
176
+
177
+ function isAgentEvent(value: unknown): value is AgentEvent {
178
+ return (
179
+ value !== null &&
180
+ typeof value === "object" &&
181
+ (value as { version?: unknown }).version === 1 &&
182
+ typeof (value as { type?: unknown }).type === "string"
183
+ )
184
+ }
185
+
186
+ function failedStream(error: unknown): AgentEventStream {
187
+ const stream = createAgentEventStream()
188
+ stream.fail(error)
189
+ return stream
190
+ }