@agentex/agent 0.0.27 → 0.0.29
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/CHANGELOG.md +138 -0
- package/README.md +98 -8
- package/dist/derived.d.ts.map +1 -1
- package/dist/derived.js +99 -2
- package/dist/derived.js.map +1 -1
- package/dist/goals/controller.d.ts.map +1 -1
- package/dist/goals/controller.js +21 -0
- package/dist/goals/controller.js.map +1 -1
- package/dist/goals/normalize.d.ts +1 -0
- package/dist/goals/normalize.d.ts.map +1 -1
- package/dist/goals/normalize.js +5 -0
- package/dist/goals/normalize.js.map +1 -1
- package/dist/history/fs.d.ts +36 -0
- package/dist/history/fs.d.ts.map +1 -0
- package/dist/history/fs.js +266 -0
- package/dist/history/fs.js.map +1 -0
- package/dist/history/index.d.ts +3 -0
- package/dist/history/index.d.ts.map +1 -0
- package/dist/history/index.js +2 -0
- package/dist/history/index.js.map +1 -0
- package/dist/history/types.d.ts +75 -0
- package/dist/history/types.d.ts.map +1 -0
- package/dist/history/types.js +9 -0
- package/dist/history/types.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/claude/attach.d.ts.map +1 -1
- package/dist/providers/claude/attach.js +50 -9
- package/dist/providers/claude/attach.js.map +1 -1
- package/dist/providers/claude/history.d.ts +3 -0
- package/dist/providers/claude/history.d.ts.map +1 -0
- package/dist/providers/claude/history.js +248 -0
- package/dist/providers/claude/history.js.map +1 -0
- package/dist/providers/claude/index.d.ts.map +1 -1
- package/dist/providers/claude/index.js +26 -0
- package/dist/providers/claude/index.js.map +1 -1
- package/dist/providers/claude/session.d.ts +2 -0
- package/dist/providers/claude/session.d.ts.map +1 -1
- package/dist/providers/claude/session.js +17 -10
- package/dist/providers/claude/session.js.map +1 -1
- package/dist/providers/codex/attach.d.ts +2 -2
- package/dist/providers/codex/attach.d.ts.map +1 -1
- package/dist/providers/codex/attach.js +44 -7
- package/dist/providers/codex/attach.js.map +1 -1
- package/dist/providers/codex/history.d.ts +3 -0
- package/dist/providers/codex/history.d.ts.map +1 -0
- package/dist/providers/codex/history.js +350 -0
- package/dist/providers/codex/history.js.map +1 -0
- package/dist/providers/codex/index.d.ts.map +1 -1
- package/dist/providers/codex/index.js +26 -0
- package/dist/providers/codex/index.js.map +1 -1
- package/dist/providers/codex/transcript-normalize.d.ts +3 -2
- package/dist/providers/codex/transcript-normalize.d.ts.map +1 -1
- package/dist/providers/codex/transcript-normalize.js +4 -3
- package/dist/providers/codex/transcript-normalize.js.map +1 -1
- package/dist/providers/codex/transcript.d.ts +3 -1
- package/dist/providers/codex/transcript.d.ts.map +1 -1
- package/dist/providers/codex/transcript.js +3 -1
- package/dist/providers/codex/transcript.js.map +1 -1
- package/dist/providers/cursor/discovery.d.ts +6 -0
- package/dist/providers/cursor/discovery.d.ts.map +1 -0
- package/dist/providers/cursor/discovery.js +118 -0
- package/dist/providers/cursor/discovery.js.map +1 -0
- package/dist/providers/cursor/execute.d.ts.map +1 -1
- package/dist/providers/cursor/execute.js +83 -45
- package/dist/providers/cursor/execute.js.map +1 -1
- package/dist/providers/cursor/index.d.ts.map +1 -1
- package/dist/providers/cursor/index.js +17 -4
- package/dist/providers/cursor/index.js.map +1 -1
- package/dist/providers/cursor/parse.js +1 -1
- package/dist/providers/cursor/parse.js.map +1 -1
- package/dist/providers/cursor/probe.d.ts +3 -0
- package/dist/providers/cursor/probe.d.ts.map +1 -0
- package/dist/providers/cursor/probe.js +89 -0
- package/dist/providers/cursor/probe.js.map +1 -0
- package/dist/providers/cursor/runtime.d.ts +4 -0
- package/dist/providers/cursor/runtime.d.ts.map +1 -0
- package/dist/providers/cursor/runtime.js +12 -0
- package/dist/providers/cursor/runtime.js.map +1 -0
- package/dist/providers/cursor/session.d.ts +3 -0
- package/dist/providers/cursor/session.d.ts.map +1 -0
- package/dist/providers/cursor/session.js +14 -0
- package/dist/providers/cursor/session.js.map +1 -0
- package/dist/providers/opencode/client.d.ts +14 -0
- package/dist/providers/opencode/client.d.ts.map +1 -0
- package/dist/providers/opencode/client.js +40 -0
- package/dist/providers/opencode/client.js.map +1 -0
- package/dist/providers/opencode/discovery.d.ts +4 -0
- package/dist/providers/opencode/discovery.d.ts.map +1 -0
- package/dist/providers/opencode/discovery.js +68 -0
- package/dist/providers/opencode/discovery.js.map +1 -0
- package/dist/providers/opencode/execute.d.ts.map +1 -1
- package/dist/providers/opencode/execute.js +219 -164
- package/dist/providers/opencode/execute.js.map +1 -1
- package/dist/providers/opencode/history.d.ts +24 -0
- package/dist/providers/opencode/history.d.ts.map +1 -0
- package/dist/providers/opencode/history.js +242 -0
- package/dist/providers/opencode/history.js.map +1 -0
- package/dist/providers/opencode/http-session.d.ts.map +1 -1
- package/dist/providers/opencode/http-session.js +333 -49
- package/dist/providers/opencode/http-session.js.map +1 -1
- package/dist/providers/opencode/index.d.ts.map +1 -1
- package/dist/providers/opencode/index.js +26 -3
- package/dist/providers/opencode/index.js.map +1 -1
- package/dist/providers/opencode/manager.d.ts +11 -0
- package/dist/providers/opencode/manager.d.ts.map +1 -0
- package/dist/providers/opencode/manager.js +240 -0
- package/dist/providers/opencode/manager.js.map +1 -0
- package/dist/providers/opencode/modes.d.ts +3 -0
- package/dist/providers/opencode/modes.d.ts.map +1 -0
- package/dist/providers/opencode/modes.js +30 -0
- package/dist/providers/opencode/modes.js.map +1 -0
- package/dist/providers/opencode/parse.d.ts.map +1 -1
- package/dist/providers/opencode/parse.js +2 -22
- package/dist/providers/opencode/parse.js.map +1 -1
- package/dist/providers/opencode/probe.d.ts +3 -0
- package/dist/providers/opencode/probe.d.ts.map +1 -0
- package/dist/providers/opencode/probe.js +107 -0
- package/dist/providers/opencode/probe.js.map +1 -0
- package/dist/providers/opencode/runtime.d.ts +8 -0
- package/dist/providers/opencode/runtime.d.ts.map +1 -0
- package/dist/providers/opencode/runtime.js +12 -0
- package/dist/providers/opencode/runtime.js.map +1 -0
- package/dist/providers/opencode/server.d.ts +18 -3
- package/dist/providers/opencode/server.d.ts.map +1 -1
- package/dist/providers/opencode/server.js +0 -0
- package/dist/providers/opencode/server.js.map +1 -1
- package/dist/providers/opencode/skill-config.d.ts +11 -0
- package/dist/providers/opencode/skill-config.d.ts.map +1 -0
- package/dist/providers/opencode/skill-config.js +57 -0
- package/dist/providers/opencode/skill-config.js.map +1 -0
- package/dist/sessions/exec-backed.d.ts +10 -0
- package/dist/sessions/exec-backed.d.ts.map +1 -0
- package/dist/sessions/exec-backed.js +141 -0
- package/dist/sessions/exec-backed.js.map +1 -0
- package/dist/sessions/history.d.ts +4 -0
- package/dist/sessions/history.d.ts.map +1 -0
- package/dist/sessions/history.js +59 -0
- package/dist/sessions/history.js.map +1 -0
- package/dist/sessions/index.d.ts +3 -0
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +2 -0
- package/dist/sessions/index.js.map +1 -1
- package/dist/types.d.ts +152 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/auth.js +36 -6
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/binary.d.ts.map +1 -1
- package/dist/utils/binary.js +3 -0
- package/dist/utils/binary.js.map +1 -1
- package/dist/utils/endpoint.js +3 -3
- package/dist/utils/endpoint.js.map +1 -1
- package/package.json +10 -2
- package/src/derived.ts +126 -3
- package/src/goals/controller.ts +17 -0
- package/src/goals/normalize.ts +5 -0
- package/src/history/fs.ts +307 -0
- package/src/history/index.ts +18 -0
- package/src/history/types.ts +104 -0
- package/src/index.ts +31 -0
- package/src/providers/claude/attach.ts +48 -6
- package/src/providers/claude/history.ts +290 -0
- package/src/providers/claude/index.ts +26 -0
- package/src/providers/claude/session.ts +15 -10
- package/src/providers/codex/attach.ts +52 -7
- package/src/providers/codex/history.ts +399 -0
- package/src/providers/codex/index.ts +26 -0
- package/src/providers/codex/transcript-normalize.ts +4 -3
- package/src/providers/codex/transcript.ts +5 -1
- package/src/providers/cursor/discovery.ts +114 -0
- package/src/providers/cursor/execute.ts +67 -30
- package/src/providers/cursor/index.ts +19 -5
- package/src/providers/cursor/parse.ts +1 -1
- package/src/providers/cursor/probe.ts +90 -0
- package/src/providers/cursor/runtime.ts +11 -0
- package/src/providers/cursor/session.ts +15 -0
- package/src/providers/opencode/client.ts +44 -0
- package/src/providers/opencode/discovery.ts +69 -0
- package/src/providers/opencode/execute.ts +64 -12
- package/src/providers/opencode/history.ts +270 -0
- package/src/providers/opencode/http-session.ts +319 -50
- package/src/providers/opencode/index.ts +26 -3
- package/src/providers/opencode/manager.ts +269 -0
- package/src/providers/opencode/modes.ts +31 -0
- package/src/providers/opencode/parse.ts +2 -22
- package/src/providers/opencode/probe.ts +113 -0
- package/src/providers/opencode/runtime.ts +13 -0
- package/src/providers/opencode/server.ts +0 -0
- package/src/providers/opencode/skill-config.ts +64 -0
- package/src/sessions/exec-backed.ts +169 -0
- package/src/sessions/history.ts +76 -0
- package/src/sessions/index.ts +3 -0
- package/src/types.ts +163 -4
- package/src/utils/auth.ts +35 -6
- package/src/utils/binary.ts +3 -0
- package/src/utils/endpoint.ts +3 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentex/agent",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Programmatic execution of AI coding agents
|
|
3
|
+
"version": "0.0.29",
|
|
4
|
+
"description": "Programmatic execution of AI coding agents including Claude Code, Codex, Cursor, and OpenCode",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/dynamismlabs/agentex.git",
|
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
"import": "./dist/goals/index.js",
|
|
39
39
|
"default": "./dist/goals/index.js"
|
|
40
40
|
},
|
|
41
|
+
"./history": {
|
|
42
|
+
"types": "./dist/history/index.d.ts",
|
|
43
|
+
"import": "./dist/history/index.js",
|
|
44
|
+
"default": "./dist/history/index.js"
|
|
45
|
+
},
|
|
41
46
|
"./sessions": {
|
|
42
47
|
"types": "./dist/sessions/index.d.ts",
|
|
43
48
|
"import": "./dist/sessions/index.js",
|
|
@@ -84,6 +89,8 @@
|
|
|
84
89
|
"agent",
|
|
85
90
|
"claude",
|
|
86
91
|
"codex",
|
|
92
|
+
"cursor",
|
|
93
|
+
"opencode",
|
|
87
94
|
"openclaw",
|
|
88
95
|
"provider",
|
|
89
96
|
"execution"
|
|
@@ -114,6 +121,7 @@
|
|
|
114
121
|
"smoke:mock": "tsx scripts/smoke.ts --mock",
|
|
115
122
|
"smoke:concurrent": "tsx scripts/concurrent-smoke.ts",
|
|
116
123
|
"demo:durable": "tsx scripts/durable-session-demo.ts",
|
|
124
|
+
"diagnose:history": "tsx scripts/local-history-diagnostic.ts",
|
|
117
125
|
"list-models": "tsx scripts/list-models.ts"
|
|
118
126
|
}
|
|
119
127
|
}
|
package/src/derived.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AgentSession,
|
|
3
|
+
AttachOptions,
|
|
4
|
+
HistoryAttachment,
|
|
2
5
|
ProviderCapabilities,
|
|
3
6
|
ProviderConfig,
|
|
4
7
|
ProviderModel,
|
|
5
8
|
ProviderModule,
|
|
9
|
+
ProviderRuntimeContext,
|
|
10
|
+
SessionAttachment,
|
|
11
|
+
SessionRecord,
|
|
6
12
|
} from "./types.js";
|
|
7
13
|
import { getProvider, registerProvider } from "./registry.js";
|
|
8
14
|
import { acpProvider } from "./providers/acp/index.js";
|
|
15
|
+
import { MalformedSessionRecordError } from "./sessions/record.js";
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
18
|
* Declarative configuration for a *derived* provider — a new provider id that
|
|
@@ -76,6 +83,38 @@ export function getAcpFactory(): AcpFactory | null {
|
|
|
76
83
|
return acpFactory;
|
|
77
84
|
}
|
|
78
85
|
|
|
86
|
+
function withProviderType(record: SessionRecord | null, providerType: string): SessionRecord | null {
|
|
87
|
+
return record ? { ...record, providerType } : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function wrapDerivedSession(session: AgentSession, providerType: string): AgentSession {
|
|
91
|
+
return new Proxy(session, {
|
|
92
|
+
get(target, property) {
|
|
93
|
+
if (property === "describe") {
|
|
94
|
+
return target.describe
|
|
95
|
+
? () => withProviderType(target.describe!(), providerType)
|
|
96
|
+
: undefined;
|
|
97
|
+
}
|
|
98
|
+
if (property === "describeHistory") {
|
|
99
|
+
return target.describeHistory
|
|
100
|
+
? () => withProviderType(target.describeHistory!(), providerType)
|
|
101
|
+
: undefined;
|
|
102
|
+
}
|
|
103
|
+
const value = Reflect.get(target, property, target) as unknown;
|
|
104
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function assertDerivedRecord(record: SessionRecord, providerType: string): void {
|
|
110
|
+
if (record.providerType !== providerType) {
|
|
111
|
+
throw new MalformedSessionRecordError(
|
|
112
|
+
`${providerType} attach requires providerType ${JSON.stringify(providerType)}; got ${JSON.stringify(record.providerType)}`,
|
|
113
|
+
"providerType",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
79
118
|
/**
|
|
80
119
|
* Build a derived `ProviderModule` from a base provider plus an overlay. The
|
|
81
120
|
* returned module is NOT auto-registered — pass it to `registerProvider`, or
|
|
@@ -124,15 +163,21 @@ export function defineDerivedProvider(cfg: DerivedProviderConfig): ProviderModul
|
|
|
124
163
|
|
|
125
164
|
if (base.createSession) {
|
|
126
165
|
const baseCreate = base.createSession.bind(base);
|
|
127
|
-
derived.createSession = (ctx) =>
|
|
128
|
-
baseCreate({ ...ctx, env: overlayEnv(ctx.env), config: overlayConfig(ctx.config) })
|
|
166
|
+
derived.createSession = async (ctx) => wrapDerivedSession(
|
|
167
|
+
await baseCreate({ ...ctx, env: overlayEnv(ctx.env), config: overlayConfig(ctx.config) }),
|
|
168
|
+
cfg.id,
|
|
169
|
+
);
|
|
129
170
|
}
|
|
130
171
|
if (cfg.models) {
|
|
131
172
|
const models = cfg.models;
|
|
132
173
|
derived.listModels = async () => models;
|
|
133
174
|
} else if (base.listModels) {
|
|
134
175
|
const baseList = base.listModels.bind(base);
|
|
135
|
-
derived.listModels = (opts) => baseList(
|
|
176
|
+
derived.listModels = (opts) => baseList({
|
|
177
|
+
...opts,
|
|
178
|
+
env: overlayEnv(opts?.env),
|
|
179
|
+
config: overlayConfig(opts?.config),
|
|
180
|
+
});
|
|
136
181
|
}
|
|
137
182
|
if (base.listModes) {
|
|
138
183
|
const baseModes = base.listModes.bind(base);
|
|
@@ -144,10 +189,88 @@ export function defineDerivedProvider(cfg: DerivedProviderConfig): ProviderModul
|
|
|
144
189
|
derived.checkQuota = (qctx) =>
|
|
145
190
|
baseQuota({ ...qctx, env: overlayEnv(qctx?.env), config: overlayConfig(qctx?.config) });
|
|
146
191
|
}
|
|
192
|
+
if (base.probeCapabilities) {
|
|
193
|
+
const baseProbe = base.probeCapabilities.bind(base);
|
|
194
|
+
derived.probeCapabilities = (ctx) => baseProbe({
|
|
195
|
+
...ctx,
|
|
196
|
+
env: overlayEnv(ctx?.env),
|
|
197
|
+
config: overlayConfig(ctx?.config),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (base.upstreamProviders) {
|
|
201
|
+
const manager = base.upstreamProviders;
|
|
202
|
+
const runtime = (ctx?: ProviderRuntimeContext): ProviderRuntimeContext => ({
|
|
203
|
+
...ctx,
|
|
204
|
+
env: overlayEnv(ctx?.env),
|
|
205
|
+
config: overlayConfig(ctx?.config),
|
|
206
|
+
});
|
|
207
|
+
derived.upstreamProviders = {
|
|
208
|
+
list: (ctx) => manager.list(runtime(ctx)),
|
|
209
|
+
authMethods: (providerId, ctx) => manager.authMethods(providerId, runtime(ctx)),
|
|
210
|
+
setApiKey: (providerId, key, ctx) => manager.setApiKey(providerId, key, runtime(ctx)),
|
|
211
|
+
beginOAuth: (providerId, methodId, inputs, ctx) =>
|
|
212
|
+
manager.beginOAuth(providerId, methodId, inputs, runtime(ctx)),
|
|
213
|
+
completeOAuth: (flowId, code, ctx) => manager.completeOAuth(flowId, code, runtime(ctx)),
|
|
214
|
+
canDisconnect: (providerId, ctx) => manager.canDisconnect(providerId, runtime(ctx)),
|
|
215
|
+
disconnect: (providerId, ctx) => manager.disconnect(providerId, runtime(ctx)),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
if (base.attachSession && derived.createSession) {
|
|
219
|
+
const baseAttach = base.attachSession.bind(base);
|
|
220
|
+
derived.attachSession = async (record, opts): Promise<SessionAttachment> => {
|
|
221
|
+
assertDerivedRecord(record, cfg.id);
|
|
222
|
+
const attachment = await baseAttach(
|
|
223
|
+
{ ...record, providerType: base.type },
|
|
224
|
+
overlayAttachOptions(opts, overlayEnv, overlayConfig),
|
|
225
|
+
);
|
|
226
|
+
const derivedRecord = withProviderType(attachment.record, cfg.id)!;
|
|
227
|
+
return {
|
|
228
|
+
...attachment,
|
|
229
|
+
record: derivedRecord,
|
|
230
|
+
resume: (ctx) => derived.createSession!({
|
|
231
|
+
...ctx,
|
|
232
|
+
cwd: ctx?.cwd ?? derivedRecord.cwd ?? undefined,
|
|
233
|
+
sessionParams: derivedRecord.params,
|
|
234
|
+
}),
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (base.attachHistory && derived.createSession) {
|
|
239
|
+
const baseAttachHistory = base.attachHistory.bind(base);
|
|
240
|
+
derived.attachHistory = async (record, opts): Promise<HistoryAttachment> => {
|
|
241
|
+
assertDerivedRecord(record, cfg.id);
|
|
242
|
+
const attachment = await baseAttachHistory(
|
|
243
|
+
{ ...record, providerType: base.type },
|
|
244
|
+
overlayAttachOptions(opts, overlayEnv, overlayConfig),
|
|
245
|
+
);
|
|
246
|
+
const derivedRecord = withProviderType(attachment.record, cfg.id)!;
|
|
247
|
+
return {
|
|
248
|
+
...attachment,
|
|
249
|
+
record: derivedRecord,
|
|
250
|
+
resume: (ctx) => derived.createSession!({
|
|
251
|
+
...ctx,
|
|
252
|
+
cwd: ctx?.cwd ?? derivedRecord.cwd ?? undefined,
|
|
253
|
+
sessionParams: derivedRecord.params,
|
|
254
|
+
}),
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
}
|
|
147
258
|
|
|
148
259
|
return derived;
|
|
149
260
|
}
|
|
150
261
|
|
|
262
|
+
function overlayAttachOptions(
|
|
263
|
+
opts: AttachOptions | undefined,
|
|
264
|
+
overlayEnv: (env?: Record<string, string>) => Record<string, string>,
|
|
265
|
+
overlayConfig: (config?: ProviderConfig) => ProviderConfig,
|
|
266
|
+
): AttachOptions {
|
|
267
|
+
return {
|
|
268
|
+
...opts,
|
|
269
|
+
env: overlayEnv(opts?.env),
|
|
270
|
+
config: overlayConfig(opts?.config),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
151
274
|
// ---------------------------------------------------------------------------
|
|
152
275
|
// Config-file loader
|
|
153
276
|
// ---------------------------------------------------------------------------
|
package/src/goals/controller.ts
CHANGED
|
@@ -139,6 +139,9 @@ export class GoalController {
|
|
|
139
139
|
* sentinel/loop), call `setGoal` again; hydrate does not restart the loop.
|
|
140
140
|
*/
|
|
141
141
|
hydrate(state: GoalState): void {
|
|
142
|
+
// Hydration is historical initialization, never an overwrite. A live
|
|
143
|
+
// parser event or caller-set goal that won the race remains authoritative.
|
|
144
|
+
if (this.state !== null) return;
|
|
142
145
|
this.state = state;
|
|
143
146
|
this.mode = "idle";
|
|
144
147
|
}
|
|
@@ -387,6 +390,19 @@ export class GoalController {
|
|
|
387
390
|
// re-enters onTurnSettled and continues the loop.
|
|
388
391
|
const nudge = verdict.nudge ?? defaultNudge(objective, this.iterations);
|
|
389
392
|
void this.deps.send(nudge).catch(() => {});
|
|
393
|
+
} catch (error) {
|
|
394
|
+
if (gen !== this.generation) return;
|
|
395
|
+
this.transition({
|
|
396
|
+
objective,
|
|
397
|
+
status: "blocked",
|
|
398
|
+
met: false,
|
|
399
|
+
enforced: true,
|
|
400
|
+
source: "agentex",
|
|
401
|
+
blockedReason: "sentinel_error",
|
|
402
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
403
|
+
});
|
|
404
|
+
this.mode = "idle";
|
|
405
|
+
this.sentinel = null;
|
|
390
406
|
} finally {
|
|
391
407
|
this.evaluating = false;
|
|
392
408
|
}
|
|
@@ -433,6 +449,7 @@ export class GoalController {
|
|
|
433
449
|
raw: { synthetic: "goal", ...fields },
|
|
434
450
|
};
|
|
435
451
|
if (fields.blockedReason !== undefined) event.blockedReason = fields.blockedReason;
|
|
452
|
+
if (fields.errorMessage !== undefined) event.errorMessage = fields.errorMessage;
|
|
436
453
|
if (fields.tokensUsed !== undefined) event.tokensUsed = fields.tokensUsed;
|
|
437
454
|
if (fields.timeUsedSeconds !== undefined) event.timeUsedSeconds = fields.timeUsedSeconds;
|
|
438
455
|
if (fields.tokenBudget !== undefined) event.tokenBudget = fields.tokenBudget;
|
package/src/goals/normalize.ts
CHANGED
|
@@ -33,6 +33,7 @@ export interface NormalizedGoalFields {
|
|
|
33
33
|
enforced: boolean;
|
|
34
34
|
source: GoalSource;
|
|
35
35
|
blockedReason?: GoalBlockedReason;
|
|
36
|
+
errorMessage?: string;
|
|
36
37
|
tokensUsed?: number;
|
|
37
38
|
timeUsedSeconds?: number;
|
|
38
39
|
tokenBudget?: number;
|
|
@@ -90,6 +91,9 @@ export function normalizeCodexGoalStatus(raw: string | null | undefined): {
|
|
|
90
91
|
case "budget-limited":
|
|
91
92
|
case "budget_limited":
|
|
92
93
|
case "budgetlimited": // camelCase `budgetLimited`, lowercased
|
|
94
|
+
case "usage-limited":
|
|
95
|
+
case "usage_limited":
|
|
96
|
+
case "usagelimited": // camelCase `usageLimited`, lowercased
|
|
93
97
|
return { status: "blocked", met: false, blockedReason: "budget" };
|
|
94
98
|
case "blocked":
|
|
95
99
|
return { status: "blocked", met: false, blockedReason: "needs_input" };
|
|
@@ -165,6 +169,7 @@ export function goalStateFromEvent(event: GoalStatusEvent): GoalState {
|
|
|
165
169
|
updatedAt: event.timestamp,
|
|
166
170
|
};
|
|
167
171
|
if (event.blockedReason !== undefined) state.blockedReason = event.blockedReason;
|
|
172
|
+
if (event.errorMessage !== undefined) state.errorMessage = event.errorMessage;
|
|
168
173
|
if (event.tokensUsed !== undefined) state.tokensUsed = event.tokensUsed;
|
|
169
174
|
if (event.timeUsedSeconds !== undefined) state.timeUsedSeconds = event.timeUsedSeconds;
|
|
170
175
|
if (event.tokenBudget !== undefined) state.tokenBudget = event.tokenBudget;
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readdir, stat, open as openFile, type FileHandle } from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import * as readline from "node:readline";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
LocalHistoryError,
|
|
8
|
+
type LocalHistorySourceFingerprint,
|
|
9
|
+
} from "./types.js";
|
|
10
|
+
|
|
11
|
+
export const UUID_JSONL_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/i;
|
|
12
|
+
export const ROLLOUT_UUID_RE = /-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
13
|
+
|
|
14
|
+
export interface JsonlRecord {
|
|
15
|
+
raw: Record<string, unknown>;
|
|
16
|
+
text: string;
|
|
17
|
+
lineStartOffset: number;
|
|
18
|
+
nextOffset: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface StableReadOptions {
|
|
22
|
+
/** Reject a completed read when the opened source changed while streaming. */
|
|
23
|
+
rejectChanges?: boolean;
|
|
24
|
+
/** Internal deterministic test hook. Not part of the public history API. */
|
|
25
|
+
afterInitialStat?: () => void | Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface FileIdentity {
|
|
29
|
+
size: bigint;
|
|
30
|
+
modifiedAtNs: bigint;
|
|
31
|
+
device: bigint;
|
|
32
|
+
inode: bigint;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function asRecord(value: unknown): Record<string, unknown> | null {
|
|
36
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
37
|
+
? value as Record<string, unknown>
|
|
38
|
+
: null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function asString(value: unknown): string | null {
|
|
42
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function isoDate(value: unknown, fallback: string | null = null): string | null {
|
|
46
|
+
if (typeof value !== "string") return fallback;
|
|
47
|
+
const parsed = new Date(value);
|
|
48
|
+
return Number.isNaN(parsed.getTime()) ? fallback : parsed.toISOString();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function cleanTitle(value: string | null): string | null {
|
|
52
|
+
const cleaned = value
|
|
53
|
+
?.replace(/<[^>]+>/g, " ")
|
|
54
|
+
.replace(/\[\[[^\]]+\]\]/g, " ")
|
|
55
|
+
.replace(/^[#>*_`\s-]+/g, "")
|
|
56
|
+
.replace(/\s+/g, " ")
|
|
57
|
+
.trim();
|
|
58
|
+
if (!cleaned) return null;
|
|
59
|
+
return cleaned.length > 120 ? `${cleaned.slice(0, 117).trimEnd()}...` : cleaned;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function isDirectory(filePath: string): Promise<boolean> {
|
|
63
|
+
try {
|
|
64
|
+
return (await stat(filePath)).isDirectory();
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function listJsonlFiles(
|
|
71
|
+
root: string,
|
|
72
|
+
options: { maxDepth: number; limit?: number; accept?: (name: string) => boolean },
|
|
73
|
+
): Promise<string[]> {
|
|
74
|
+
const files: string[] = [];
|
|
75
|
+
const limit = Math.max(1, options.limit ?? Number.MAX_SAFE_INTEGER);
|
|
76
|
+
|
|
77
|
+
async function visit(dir: string, depth: number): Promise<void> {
|
|
78
|
+
if (depth > options.maxDepth || files.length >= limit) return;
|
|
79
|
+
let entries;
|
|
80
|
+
try {
|
|
81
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
82
|
+
} catch {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (files.length >= limit) break;
|
|
88
|
+
const child = path.join(dir, entry.name);
|
|
89
|
+
if (entry.isDirectory()) await visit(child, depth + 1);
|
|
90
|
+
else if (entry.isFile() && entry.name.endsWith(".jsonl") && (options.accept?.(entry.name) ?? true)) {
|
|
91
|
+
files.push(child);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await visit(root, 0);
|
|
97
|
+
return files;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function mapLimited<T, U>(
|
|
101
|
+
values: readonly T[],
|
|
102
|
+
concurrency: number,
|
|
103
|
+
mapper: (value: T) => Promise<U>,
|
|
104
|
+
): Promise<U[]> {
|
|
105
|
+
const output = new Array<U>(values.length);
|
|
106
|
+
let cursor = 0;
|
|
107
|
+
async function worker(): Promise<void> {
|
|
108
|
+
while (cursor < values.length) {
|
|
109
|
+
const index = cursor++;
|
|
110
|
+
output[index] = await mapper(values[index]!);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
await Promise.all(Array.from(
|
|
114
|
+
{ length: Math.min(Math.max(1, concurrency), values.length) },
|
|
115
|
+
() => worker(),
|
|
116
|
+
));
|
|
117
|
+
return output;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function fileIdentity(handle: FileHandle): Promise<FileIdentity> {
|
|
121
|
+
const info = await handle.stat({ bigint: true });
|
|
122
|
+
if (!info.isFile()) throw new LocalHistoryError("invalid_session", "Local history source is not a file");
|
|
123
|
+
return {
|
|
124
|
+
size: info.size,
|
|
125
|
+
modifiedAtNs: info.mtimeNs,
|
|
126
|
+
device: info.dev,
|
|
127
|
+
inode: info.ino,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function sameFileIdentity(left: FileIdentity, right: FileIdentity): boolean {
|
|
132
|
+
return left.size === right.size
|
|
133
|
+
&& left.modifiedAtNs === right.modifiedAtNs
|
|
134
|
+
&& left.device === right.device
|
|
135
|
+
&& left.inode === right.inode;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function sourceChangedError(): LocalHistoryError {
|
|
139
|
+
return new LocalHistoryError(
|
|
140
|
+
"source_changed_during_read",
|
|
141
|
+
"Local history source changed during the read",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function wrapFileError(error: unknown, action: "inspect" | "read" | "hash"): LocalHistoryError {
|
|
146
|
+
if (error instanceof LocalHistoryError) return error;
|
|
147
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
148
|
+
return new LocalHistoryError(
|
|
149
|
+
code === "ENOENT" ? "source_missing" : code === "EACCES" ? "permission_denied" : "io_error",
|
|
150
|
+
code === "ENOENT"
|
|
151
|
+
? "Local history source is missing"
|
|
152
|
+
: `Unable to ${action} local history source`,
|
|
153
|
+
{ cause: error },
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function recentEligible<T>(
|
|
158
|
+
filePaths: readonly string[],
|
|
159
|
+
options: { limit?: number; concurrency?: number },
|
|
160
|
+
inspect: (filePath: string) => Promise<T | null>,
|
|
161
|
+
): Promise<T[]> {
|
|
162
|
+
const limit = options.limit === undefined
|
|
163
|
+
? Number.MAX_SAFE_INTEGER
|
|
164
|
+
: Math.max(0, options.limit);
|
|
165
|
+
if (limit === 0 || filePaths.length === 0) return [];
|
|
166
|
+
|
|
167
|
+
const metadata = await mapLimited(filePaths, 32, async (filePath) => {
|
|
168
|
+
try {
|
|
169
|
+
const info = await stat(filePath, { bigint: true });
|
|
170
|
+
return info.isFile() ? { filePath, modifiedAtNs: info.mtimeNs } : null;
|
|
171
|
+
} catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
const ordered = metadata
|
|
176
|
+
.filter((value): value is { filePath: string; modifiedAtNs: bigint } => value !== null)
|
|
177
|
+
.sort((a, b) => a.modifiedAtNs === b.modifiedAtNs
|
|
178
|
+
? a.filePath.localeCompare(b.filePath)
|
|
179
|
+
: a.modifiedAtNs > b.modifiedAtNs ? -1 : 1);
|
|
180
|
+
|
|
181
|
+
const output: T[] = [];
|
|
182
|
+
const concurrency = Math.max(1, options.concurrency ?? 16);
|
|
183
|
+
let cursor = 0;
|
|
184
|
+
while (cursor < ordered.length && output.length < limit) {
|
|
185
|
+
// Never inspect more files than the number of results still requested in
|
|
186
|
+
// one batch. Excluded files cause another bounded batch to be pulled.
|
|
187
|
+
const batchSize = Math.min(concurrency, limit - output.length, ordered.length - cursor);
|
|
188
|
+
const batch = ordered.slice(cursor, cursor + batchSize);
|
|
189
|
+
cursor += batch.length;
|
|
190
|
+
const inspected = await mapLimited(batch, concurrency, ({ filePath }) => inspect(filePath));
|
|
191
|
+
for (const value of inspected) {
|
|
192
|
+
if (value !== null) output.push(value);
|
|
193
|
+
if (output.length >= limit) break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return output;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export async function sourceFingerprint(
|
|
200
|
+
filePath: string,
|
|
201
|
+
includeSha256 = false,
|
|
202
|
+
internalOptions: StableReadOptions = {},
|
|
203
|
+
): Promise<LocalHistorySourceFingerprint> {
|
|
204
|
+
let handle: FileHandle | null = null;
|
|
205
|
+
try {
|
|
206
|
+
handle = await openFile(filePath, "r");
|
|
207
|
+
const before = await fileIdentity(handle);
|
|
208
|
+
const fingerprint: LocalHistorySourceFingerprint = {
|
|
209
|
+
size: Number(before.size),
|
|
210
|
+
modifiedAtNs: before.modifiedAtNs.toString(),
|
|
211
|
+
};
|
|
212
|
+
if (!includeSha256) return fingerprint;
|
|
213
|
+
|
|
214
|
+
await internalOptions.afterInitialStat?.();
|
|
215
|
+
const hash = createHash("sha256");
|
|
216
|
+
const stream = handle.createReadStream({ autoClose: false });
|
|
217
|
+
let after: FileIdentity;
|
|
218
|
+
try {
|
|
219
|
+
for await (const chunk of stream) hash.update(chunk as Buffer);
|
|
220
|
+
after = await fileIdentity(handle);
|
|
221
|
+
} finally {
|
|
222
|
+
stream.destroy();
|
|
223
|
+
}
|
|
224
|
+
if (!sameFileIdentity(before, after)) throw sourceChangedError();
|
|
225
|
+
fingerprint.sha256 = hash.digest("hex");
|
|
226
|
+
return fingerprint;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
throw wrapFileError(error, includeSha256 ? "hash" : "inspect");
|
|
229
|
+
} finally {
|
|
230
|
+
await handle?.close();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function* readJsonlRecords(
|
|
235
|
+
filePath: string,
|
|
236
|
+
fromOffset = 0,
|
|
237
|
+
options: StableReadOptions = {},
|
|
238
|
+
): AsyncIterable<JsonlRecord> {
|
|
239
|
+
let handle: FileHandle | null = null;
|
|
240
|
+
let stream: ReturnType<FileHandle["createReadStream"]> | null = null;
|
|
241
|
+
let rl: readline.Interface | null = null;
|
|
242
|
+
let offset = fromOffset;
|
|
243
|
+
try {
|
|
244
|
+
handle = await openFile(filePath, "r");
|
|
245
|
+
const before = await fileIdentity(handle);
|
|
246
|
+
await options.afterInitialStat?.();
|
|
247
|
+
stream = handle.createReadStream({ start: fromOffset, encoding: "utf8", autoClose: false });
|
|
248
|
+
stream.on("error", () => {});
|
|
249
|
+
rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
250
|
+
for await (const line of rl) {
|
|
251
|
+
const lineStartOffset = offset;
|
|
252
|
+
offset += Buffer.byteLength(line, "utf8") + 1;
|
|
253
|
+
if (!line.trim()) continue;
|
|
254
|
+
try {
|
|
255
|
+
const raw = asRecord(JSON.parse(line));
|
|
256
|
+
if (raw) yield { raw, text: line, lineStartOffset, nextOffset: offset };
|
|
257
|
+
} catch {
|
|
258
|
+
// A damaged historical record must not hide later valid records.
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (options.rejectChanges) {
|
|
262
|
+
const after = await fileIdentity(handle);
|
|
263
|
+
if (!sameFileIdentity(before, after)) throw sourceChangedError();
|
|
264
|
+
let pathInfo;
|
|
265
|
+
try {
|
|
266
|
+
pathInfo = await stat(filePath, { bigint: true });
|
|
267
|
+
} catch (error) {
|
|
268
|
+
throw wrapFileError(error, "inspect");
|
|
269
|
+
}
|
|
270
|
+
if (pathInfo.dev !== after.device || pathInfo.ino !== after.inode) throw sourceChangedError();
|
|
271
|
+
}
|
|
272
|
+
} catch (error) {
|
|
273
|
+
throw wrapFileError(error, "read");
|
|
274
|
+
} finally {
|
|
275
|
+
rl?.close();
|
|
276
|
+
stream?.destroy();
|
|
277
|
+
await handle?.close();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function textFromContent(content: unknown, acceptedTypes: ReadonlySet<string>): string | null {
|
|
282
|
+
if (typeof content === "string") return content.trim() || null;
|
|
283
|
+
if (!Array.isArray(content)) return null;
|
|
284
|
+
const parts: string[] = [];
|
|
285
|
+
for (const value of content) {
|
|
286
|
+
const block = asRecord(value);
|
|
287
|
+
if (!block) continue;
|
|
288
|
+
const type = asString(block["type"]);
|
|
289
|
+
if (type && !acceptedTypes.has(type)) continue;
|
|
290
|
+
const text = asString(block["text"]) ?? asString(block["content"]);
|
|
291
|
+
if (text) parts.push(text);
|
|
292
|
+
}
|
|
293
|
+
return parts.join("\n\n").trim() || null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function meaningfulHumanText(text: string | null): string | null {
|
|
297
|
+
if (!text) return null;
|
|
298
|
+
const trimmed = text.trim();
|
|
299
|
+
if (!trimmed) return null;
|
|
300
|
+
if (/^<(?:environment_context|user_instructions|developer_instructions|system-reminder)[\s>]/i.test(trimmed)) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
if (/^<(?:local-command-caveat|command-name|command-message|command-args)[\s>]/i.test(trimmed)) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
return trimmed;
|
|
307
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
LocalHistoryError,
|
|
3
|
+
} from "./types.js";
|
|
4
|
+
export type {
|
|
5
|
+
LocalHistoryArchiveState,
|
|
6
|
+
LocalHistoryDiscoverOptions,
|
|
7
|
+
LocalHistoryErrorCode,
|
|
8
|
+
LocalHistoryEvent,
|
|
9
|
+
LocalHistoryFingerprintOptions,
|
|
10
|
+
LocalHistoryOps,
|
|
11
|
+
LocalHistoryProbeOptions,
|
|
12
|
+
LocalHistoryProbeResult,
|
|
13
|
+
LocalHistoryReadOptions,
|
|
14
|
+
LocalHistorySession,
|
|
15
|
+
LocalHistorySourceFingerprint,
|
|
16
|
+
LocalHistoryUserEvent,
|
|
17
|
+
LocalHistoryYield,
|
|
18
|
+
} from "./types.js";
|