@zq-silk/yui 2.0.0 → 2.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.
- package/README.md +4 -0
- package/dist/cli/commandCatalog.js +318 -289
- package/dist/cli/commandDiscovery.js +72 -0
- package/dist/cli/completion.js +22 -227
- package/dist/cli/dynamicCompletion.js +12 -6
- package/dist/cli/invocationAuthority.js +2 -2
- package/dist/cli/invocationRouter.js +18 -7
- package/dist/cli.js +23 -3030
- package/dist/commands/capabilityCommands.js +5 -5
- package/dist/commands/globalRoleCommands.js +2 -1
- package/dist/commands/taskCommands.js +90 -21
- package/dist/commands/taskIntegrationCommands.js +3 -1
- package/dist/commands/taskUpstreamCommands.js +3 -1
- package/dist/context/runContextPack.js +132 -5
- package/dist/context/sourceRunContext.js +4 -2
- package/dist/context/taskContext.js +12 -3
- package/dist/controlPlaneCli.js +3092 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +19 -6
- package/dist/controller/jobControl.js +21 -0
- package/dist/executor/agentExecutor.js +1 -1
- package/dist/executor/effectiveLaunch.js +18 -5
- package/dist/executor/fileRoleLaunchPlanner.js +15 -19
- package/dist/integration/gitIntegrationService.js +50 -15
- package/dist/kernel/accessAssessment.js +1 -0
- package/dist/kernel/builtinCapabilities.js +28 -1
- package/dist/kernel/capabilityRegistry.js +64 -19
- package/dist/message/messageContinuation.js +7 -2
- package/dist/nativeAgent/agent.js +275 -0
- package/dist/nativeAgent/cliDemo.js +37 -0
- package/dist/nativeAgent/codingTools.js +11 -0
- package/dist/nativeAgent/commandTool.js +215 -0
- package/dist/nativeAgent/compactionDemo.js +158 -0
- package/dist/nativeAgent/composition.js +71 -0
- package/dist/nativeAgent/context/budget.js +44 -0
- package/dist/nativeAgent/context/index.js +339 -0
- package/dist/nativeAgent/context/providerCompressor.js +103 -0
- package/dist/nativeAgent/contracts.js +1 -0
- package/dist/nativeAgent/demo.js +40 -0
- package/dist/nativeAgent/evaluation/cases.js +38 -0
- package/dist/nativeAgent/evaluation/checks.js +91 -0
- package/dist/nativeAgent/evaluation/demo.js +19 -0
- package/dist/nativeAgent/evaluation/files.js +54 -0
- package/dist/nativeAgent/evaluation/fixture.js +36 -0
- package/dist/nativeAgent/evaluation/index.js +239 -0
- package/dist/nativeAgent/executionOwner.js +209 -0
- package/dist/nativeAgent/filePatterns.js +170 -0
- package/dist/nativeAgent/fileToolsSupport.js +202 -0
- package/dist/nativeAgent/index.js +16 -0
- package/dist/nativeAgent/interaction/cli.js +358 -0
- package/dist/nativeAgent/interaction/contracts.js +1 -0
- package/dist/nativeAgent/interaction/index.js +3 -0
- package/dist/nativeAgent/interaction/memoryDemo.js +115 -0
- package/dist/nativeAgent/interaction/renderer.js +34 -0
- package/dist/nativeAgent/localSafety.js +258 -0
- package/dist/nativeAgent/mockProvider.js +43 -0
- package/dist/nativeAgent/model/anthropicMessages.js +204 -0
- package/dist/nativeAgent/model/chatCompletions.js +210 -0
- package/dist/nativeAgent/model/errors.js +47 -0
- package/dist/nativeAgent/model/gateway.js +423 -0
- package/dist/nativeAgent/model/index.js +7 -0
- package/dist/nativeAgent/model/observationAdapter.js +21 -0
- package/dist/nativeAgent/model/protocols.js +19 -0
- package/dist/nativeAgent/model/responses.js +263 -0
- package/dist/nativeAgent/model/types.js +1 -0
- package/dist/nativeAgent/model/wire.js +73 -0
- package/dist/nativeAgent/observability/index.js +220 -0
- package/dist/nativeAgent/product/catalog.js +82 -0
- package/dist/nativeAgent/product/config.js +295 -0
- package/dist/nativeAgent/product/facts.js +30 -0
- package/dist/nativeAgent/product/index.js +62 -0
- package/dist/nativeAgent/product/location.js +44 -0
- package/dist/nativeAgent/product/runtime.js +276 -0
- package/dist/nativeAgent/product/storage.js +49 -0
- package/dist/nativeAgent/product/tools.js +47 -0
- package/dist/nativeAgent/product/transport.js +54 -0
- package/dist/nativeAgent/projectGuidance/index.js +425 -0
- package/dist/nativeAgent/searchTools.js +305 -0
- package/dist/nativeAgent/session/backends.js +293 -0
- package/dist/nativeAgent/session/catalog.js +97 -0
- package/dist/nativeAgent/session/catalogDemo.js +87 -0
- package/dist/nativeAgent/session/contracts.js +1 -0
- package/dist/nativeAgent/session/format.js +269 -0
- package/dist/nativeAgent/session/index.js +5 -0
- package/dist/nativeAgent/session/location.js +36 -0
- package/dist/nativeAgent/session/sqliteFormat.js +134 -0
- package/dist/nativeAgent/session/store.js +248 -0
- package/dist/nativeAgent/textTools.js +293 -0
- package/dist/nativeAgent/toolManager/executor.js +290 -0
- package/dist/nativeAgent/toolManager/index.js +4 -0
- package/dist/nativeAgent/validation.js +95 -0
- package/dist/plugins/pluginService.js +98 -8
- package/dist/resources/projectResourceService.js +23 -1
- package/dist/runtime/managedIdentity.js +6 -0
- package/dist/surface/surfaceContributions.js +3 -0
- package/dist/task/taskAuthority.js +61 -10
- package/dist/web/assets/assetManifest.js +38 -22
- package/dist/web/assets/client/api.js +115 -0
- package/dist/web/assets/client/app.js +615 -862
- package/dist/web/assets/client/components.js +323 -897
- package/dist/web/assets/client/detail.js +333 -0
- package/dist/web/assets/client/dock.js +225 -0
- package/dist/web/assets/client/dom.js +55 -3
- package/dist/web/assets/client/evidence.js +172 -0
- package/dist/web/assets/client/format.js +25 -14
- package/dist/web/assets/client/forms.js +230 -0
- package/dist/web/assets/client/i18n.js +1024 -791
- package/dist/web/assets/client/overview.js +118 -0
- package/dist/web/assets/client/records.js +63 -0
- package/dist/web/assets/client/sections.js +313 -0
- package/dist/web/assets/client/sidebar.js +146 -0
- package/dist/web/assets/client/theme.js +61 -22
- package/dist/web/assets/icons.js +39 -0
- package/dist/web/assets/shell.js +125 -118
- package/dist/web/assets/styles/base.js +41 -0
- package/dist/web/assets/styles/components.js +169 -0
- package/dist/web/assets/styles/layout.js +56 -77
- package/dist/web/assets/styles/markdown.js +16 -24
- package/dist/web/assets/styles/responsive.js +41 -44
- package/dist/web/assets/styles/tokens.js +65 -89
- package/dist/web/assets/styles/views.js +292 -0
- package/dist/web/webServer.js +2 -1
- package/docs/agent-result-consumption.md +16 -0
- package/docs/agent-result-consumption.zh-CN.md +13 -0
- package/docs/examples/agent-offline.mjs +194 -0
- package/docs/native-agent.md +283 -0
- package/docs/release-workflow.md +61 -0
- package/docs/release-workflow.zh-CN.md +46 -0
- package/docs/roles-and-configuration.md +32 -0
- package/docs/roles-and-configuration.zh-CN.md +26 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +9 -0
- package/skills/yui-reviewer/SKILL.md +5 -0
- package/skills/yui-runtime/SKILL.md +33 -0
- package/dist/web/assets/client/taskSummary.js +0 -350
- package/dist/web/assets/client/taskSurface.js +0 -616
- package/dist/web/assets/client/view.js +0 -608
- package/dist/web/assets/styles/cards.js +0 -247
- package/dist/web/assets/styles/widgets.js +0 -168
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { ContextBuildError } from './context/index.js';
|
|
3
|
+
/** One local execution owner per Session. Storage remains the sole history
|
|
4
|
+
* authority; this object owns only live handles and current-process receipts.
|
|
5
|
+
* The explicit catalog is a selection view, not persistent Session discovery. */
|
|
6
|
+
export function createExecutionOwner(options) {
|
|
7
|
+
if (!Number.isSafeInteger(options.maxSteps) || options.maxSteps < 1)
|
|
8
|
+
throw new Error('Positive maxSteps required');
|
|
9
|
+
const catalog = new Map((options.sessions ?? []).map(item => [item.id, { ...item }]));
|
|
10
|
+
const active = new Map();
|
|
11
|
+
const completed = new Map();
|
|
12
|
+
const compacting = new Map();
|
|
13
|
+
const listeners = new Map();
|
|
14
|
+
const subscriptions = new Set();
|
|
15
|
+
let closed = false;
|
|
16
|
+
const check = () => { if (closed)
|
|
17
|
+
throw new Error('Execution owner closed'); };
|
|
18
|
+
const notify = (id) => {
|
|
19
|
+
for (const listener of listeners.get(id) ?? []) {
|
|
20
|
+
try {
|
|
21
|
+
void Promise.resolve(listener()).catch(() => { });
|
|
22
|
+
}
|
|
23
|
+
catch { /* Invalidation is optional. */ }
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const bounds = (offset, limit, length) => {
|
|
27
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || (length !== undefined && offset > length)
|
|
28
|
+
|| !Number.isInteger(limit) || limit < 1 || limit > 20)
|
|
29
|
+
throw new Error('Invalid page bounds');
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
async create(title, location) {
|
|
33
|
+
check();
|
|
34
|
+
const summary = { id: randomUUID(), title: title.slice(0, 200) };
|
|
35
|
+
await options.store.create(summary.id, location);
|
|
36
|
+
catalog.set(summary.id, summary);
|
|
37
|
+
return { ...summary };
|
|
38
|
+
},
|
|
39
|
+
async list(offset, limit) {
|
|
40
|
+
check();
|
|
41
|
+
bounds(offset, limit, catalog.size);
|
|
42
|
+
return { sessions: [...catalog.values()].slice(offset, offset + limit).map(item => ({ ...item })),
|
|
43
|
+
nextOffset: offset + limit < catalog.size ? offset + limit : null };
|
|
44
|
+
},
|
|
45
|
+
async submit(sessionId, input) {
|
|
46
|
+
check();
|
|
47
|
+
if (active.has(sessionId) || compacting.has(sessionId))
|
|
48
|
+
throw new Error('Session already running or compacting');
|
|
49
|
+
if (completed.get(sessionId)?.result?.recording.status === 'failed' || completed.get(sessionId)?.failure)
|
|
50
|
+
throw new Error('Prior execution requires explicit inspection; no automatic retry');
|
|
51
|
+
const scope = Object.freeze({ sessionId, turnId: randomUUID() });
|
|
52
|
+
const abort = new AbortController();
|
|
53
|
+
let accept, reject;
|
|
54
|
+
const accepted = new Promise((resolve, fail) => { accept = resolve; reject = fail; });
|
|
55
|
+
// Install the handle before loading history or acquiring a recorder.
|
|
56
|
+
const done = Promise.resolve().then(async () => {
|
|
57
|
+
let recording;
|
|
58
|
+
let evidence = { scope };
|
|
59
|
+
try {
|
|
60
|
+
const saved = await options.store.load(sessionId);
|
|
61
|
+
recording = await options.store.recorder(sessionId);
|
|
62
|
+
// A concurrent outside writer is unsupported; never mix two revisions.
|
|
63
|
+
if (recording.lastReceipt.revision !== saved.revision)
|
|
64
|
+
throw new Error('Session changed during admission');
|
|
65
|
+
check();
|
|
66
|
+
const agent = await options.agent(recording);
|
|
67
|
+
check(); // Async admission must not acknowledge execution after close.
|
|
68
|
+
accept();
|
|
69
|
+
const result = await agent.runTurn({ ...scope, input, history: saved.messages,
|
|
70
|
+
maxSteps: options.maxSteps, signal: abort.signal,
|
|
71
|
+
context: { ...options.context?.retention, baseReceipt: { sessionId, revision: saved.revision, digest: saved.digest,
|
|
72
|
+
messageCount: saved.messages.length } } });
|
|
73
|
+
evidence = { scope, result, receipt: recording.lastReceipt,
|
|
74
|
+
...(recording.failure ? { failure: recording.failure } : {}) };
|
|
75
|
+
// A returned result is live evidence, not a reconstituted persisted result.
|
|
76
|
+
try {
|
|
77
|
+
options.observer?.observeSnapshot(result);
|
|
78
|
+
}
|
|
79
|
+
catch { /* Optional diagnostics. */ }
|
|
80
|
+
}
|
|
81
|
+
catch (failure) {
|
|
82
|
+
reject(failure);
|
|
83
|
+
evidence = { scope, ...(recording ? { receipt: recording.lastReceipt } : {}), failure };
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
completed.set(sessionId, evidence);
|
|
87
|
+
active.delete(sessionId);
|
|
88
|
+
notify(sessionId);
|
|
89
|
+
}
|
|
90
|
+
return evidence;
|
|
91
|
+
});
|
|
92
|
+
active.set(sessionId, { scope, abort, done });
|
|
93
|
+
notify(sessionId);
|
|
94
|
+
await accepted;
|
|
95
|
+
return scope;
|
|
96
|
+
},
|
|
97
|
+
async cancel(scope) {
|
|
98
|
+
const turn = active.get(scope.sessionId);
|
|
99
|
+
if (!turn || turn.scope.turnId !== scope.turnId)
|
|
100
|
+
return false;
|
|
101
|
+
turn.abort.abort();
|
|
102
|
+
return true;
|
|
103
|
+
},
|
|
104
|
+
async read(id, after, limit) {
|
|
105
|
+
check();
|
|
106
|
+
bounds(after, limit);
|
|
107
|
+
const page = await options.store.query(id, { after, limit });
|
|
108
|
+
const diagnostic = completed.get(id)?.failure ? 'Recording/execution failed; inspect owner evidence and saved facts; no automatic retry'
|
|
109
|
+
: (await options.store.load(id)).recovery.disposition !== 'ready' && !active.has(id)
|
|
110
|
+
? 'Saved execution needs explicit recovery; not evidence of a live Turn' : undefined;
|
|
111
|
+
return {
|
|
112
|
+
records: page.records.map(({ revision, event }) => ({ cursor: revision, kind: 'event', event })),
|
|
113
|
+
cursor: page.records.at(-1)?.revision ?? after, hasMore: page.nextCursor !== null,
|
|
114
|
+
activeTurnId: active.get(id)?.scope.turnId ?? null, ...(diagnostic ? { diagnostic } : {}),
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
async history(id, offset, limit) {
|
|
118
|
+
check();
|
|
119
|
+
const saved = await options.store.load(id);
|
|
120
|
+
bounds(offset, limit, saved.messages.length);
|
|
121
|
+
return { messages: saved.messages.slice(offset, offset + limit),
|
|
122
|
+
nextOffset: offset + limit < saved.messages.length ? offset + limit : null };
|
|
123
|
+
},
|
|
124
|
+
subscribe(id, listener) {
|
|
125
|
+
check();
|
|
126
|
+
let group = listeners.get(id);
|
|
127
|
+
if (!group) {
|
|
128
|
+
group = new Set();
|
|
129
|
+
listeners.set(id, group);
|
|
130
|
+
}
|
|
131
|
+
group.add(listener);
|
|
132
|
+
const detach = options.store.subscribe(id, () => {
|
|
133
|
+
try {
|
|
134
|
+
void Promise.resolve(listener()).catch(() => { });
|
|
135
|
+
}
|
|
136
|
+
catch { /* Optional invalidation. */ }
|
|
137
|
+
});
|
|
138
|
+
const unsubscribe = () => { detach(); group.delete(listener); subscriptions.delete(unsubscribe); };
|
|
139
|
+
subscriptions.add(unsubscribe);
|
|
140
|
+
return unsubscribe;
|
|
141
|
+
},
|
|
142
|
+
async settle(id) { return active.get(id)?.done ?? completed.get(id); },
|
|
143
|
+
async compact(sessionId, signal) {
|
|
144
|
+
check();
|
|
145
|
+
if (!options.context)
|
|
146
|
+
throw new ContextBuildError('invalid_options', 'Manual context builder is not configured');
|
|
147
|
+
if (active.has(sessionId) || compacting.has(sessionId))
|
|
148
|
+
throw new ContextBuildError('session_busy', 'Settle the active Turn/compaction before a manual projection');
|
|
149
|
+
if (completed.get(sessionId)?.failure || completed.get(sessionId)?.result?.recording.status === 'failed')
|
|
150
|
+
throw new ContextBuildError('storage_failed', 'Inspect prior owner/storage evidence before compacting');
|
|
151
|
+
const config = options.context;
|
|
152
|
+
const abort = new AbortController();
|
|
153
|
+
const cancel = () => abort.abort();
|
|
154
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
155
|
+
if (signal?.aborted)
|
|
156
|
+
cancel();
|
|
157
|
+
const load = async () => {
|
|
158
|
+
try {
|
|
159
|
+
return await options.store.load(sessionId);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
const failure = new ContextBuildError('storage_failed', 'Manual projection could not read/verify the authoritative SessionStore');
|
|
163
|
+
failure.cause = error;
|
|
164
|
+
throw failure;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const done = Promise.resolve().then(async () => {
|
|
168
|
+
const saved = await load();
|
|
169
|
+
if (saved.recovery.disposition !== 'ready')
|
|
170
|
+
throw new ContextBuildError('session_recovery_required', 'Inspect unresolved effects, cleanup and interrupted facts before compacting');
|
|
171
|
+
const built = await config.builder.build({ ...config.retention, mode: 'manual', budget: config.budget,
|
|
172
|
+
baseReceipt: { sessionId, revision: saved.revision, digest: saved.digest, messageCount: saved.messages.length },
|
|
173
|
+
request: { sessionId, turnId: `projection:${randomUUID()}`, step: 1, tools: config.tools,
|
|
174
|
+
messages: saved.messages } }, abort.signal);
|
|
175
|
+
const current = await load();
|
|
176
|
+
if (current.digest !== saved.digest || current.revision !== saved.revision)
|
|
177
|
+
throw new ContextBuildError('history_changed', 'Stored history changed during manual projection; reload exact current facts');
|
|
178
|
+
if (abort.signal.aborted)
|
|
179
|
+
throw new ContextBuildError('cancelled', 'Manual projection cancelled; no history changed');
|
|
180
|
+
return { ...built, receipt: { sessionId, revision: saved.revision, digest: saved.digest, source: saved.source } };
|
|
181
|
+
}).catch(error => {
|
|
182
|
+
config.builder.discard?.(sessionId);
|
|
183
|
+
throw error;
|
|
184
|
+
}).finally(() => {
|
|
185
|
+
compacting.delete(sessionId);
|
|
186
|
+
signal?.removeEventListener('abort', cancel);
|
|
187
|
+
});
|
|
188
|
+
compacting.set(sessionId, { abort, done });
|
|
189
|
+
return done;
|
|
190
|
+
},
|
|
191
|
+
getSettledEvidence(scope) {
|
|
192
|
+
const evidence = completed.get(scope.sessionId);
|
|
193
|
+
return evidence?.scope.turnId === scope.turnId ? evidence : undefined;
|
|
194
|
+
},
|
|
195
|
+
async close() {
|
|
196
|
+
closed = true;
|
|
197
|
+
const owned = [...active.values()];
|
|
198
|
+
const projections = [...compacting.values()];
|
|
199
|
+
for (const turn of owned)
|
|
200
|
+
turn.abort.abort();
|
|
201
|
+
for (const projection of projections)
|
|
202
|
+
projection.abort.abort();
|
|
203
|
+
await Promise.allSettled([...owned.map(turn => turn.done), ...projections.map(projection => projection.done)]);
|
|
204
|
+
for (const unsubscribe of subscriptions)
|
|
205
|
+
unsubscribe();
|
|
206
|
+
listeners.clear();
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { FileFault } from './fileToolsSupport.js';
|
|
2
|
+
const tick = (budget) => {
|
|
3
|
+
if (--budget.remaining < 0)
|
|
4
|
+
throw new FileFault('pattern_limit', 'Pattern matching work budget exceeded; narrow scope/pattern');
|
|
5
|
+
};
|
|
6
|
+
const unsupported = () => { throw new FileFault('unsupported_pattern', 'Unsupported pattern syntax; see repository-tool grammar'); };
|
|
7
|
+
function characterClass(pattern, start, globClass = false) {
|
|
8
|
+
let end = start + 1;
|
|
9
|
+
const negative = pattern[end] === '^' || (globClass && pattern[end] === '!');
|
|
10
|
+
if (negative)
|
|
11
|
+
end++;
|
|
12
|
+
const ranges = [];
|
|
13
|
+
while (end < pattern.length && pattern[end] !== ']') {
|
|
14
|
+
const first = String.fromCodePoint(pattern.codePointAt(end));
|
|
15
|
+
end += first.length;
|
|
16
|
+
if (first === '\\' || first === '[')
|
|
17
|
+
unsupported();
|
|
18
|
+
let last = first;
|
|
19
|
+
if (pattern[end] === '-' && pattern[end + 1] !== ']') {
|
|
20
|
+
end++;
|
|
21
|
+
if (end >= pattern.length)
|
|
22
|
+
unsupported();
|
|
23
|
+
last = String.fromCodePoint(pattern.codePointAt(end));
|
|
24
|
+
end += last.length;
|
|
25
|
+
if (last.codePointAt(0) < first.codePointAt(0))
|
|
26
|
+
unsupported();
|
|
27
|
+
}
|
|
28
|
+
ranges.push([first.codePointAt(0), last.codePointAt(0)]);
|
|
29
|
+
}
|
|
30
|
+
if (pattern[end] !== ']' || !ranges.length)
|
|
31
|
+
unsupported();
|
|
32
|
+
return { test: ch => negative !== ranges.some(([first, last]) => ch.codePointAt(0) >= first && ch.codePointAt(0) <= last), end };
|
|
33
|
+
}
|
|
34
|
+
function boundedPattern(pattern) {
|
|
35
|
+
if (!pattern || pattern.length > 256 || /[\0\r\n]/.test(pattern))
|
|
36
|
+
unsupported();
|
|
37
|
+
}
|
|
38
|
+
function sequence(atoms, text, budget, startAnchor = true, endAnchor = true) {
|
|
39
|
+
let active = new Uint8Array(atoms.length + 1);
|
|
40
|
+
active[0] = 1;
|
|
41
|
+
const closure = (states) => {
|
|
42
|
+
for (let i = 0; i < atoms.length; i++) {
|
|
43
|
+
tick(budget);
|
|
44
|
+
if (states[i] && atoms[i].optional)
|
|
45
|
+
states[i + 1] = 1;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
closure(active);
|
|
49
|
+
if (!endAnchor && active[atoms.length])
|
|
50
|
+
return true;
|
|
51
|
+
for (const ch of text) {
|
|
52
|
+
const next = new Uint8Array(atoms.length + 1);
|
|
53
|
+
if (!startAnchor)
|
|
54
|
+
next[0] = 1;
|
|
55
|
+
for (let i = 0; i < atoms.length; i++) {
|
|
56
|
+
tick(budget);
|
|
57
|
+
if (active[i] && atoms[i].test(ch)) {
|
|
58
|
+
next[i + 1] = 1;
|
|
59
|
+
if (atoms[i].repeat)
|
|
60
|
+
next[i] = 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
closure(next);
|
|
64
|
+
active = next;
|
|
65
|
+
if (!endAnchor && active[atoms.length])
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return !!active[atoms.length];
|
|
69
|
+
}
|
|
70
|
+
/** *, ?, classes, backslash literal escapes; ** is only a complete path segment. */
|
|
71
|
+
export function glob(pattern) {
|
|
72
|
+
boundedPattern(pattern);
|
|
73
|
+
const segments = pattern.split('/').map(segment => {
|
|
74
|
+
if (segment === '**')
|
|
75
|
+
return null;
|
|
76
|
+
const atoms = [];
|
|
77
|
+
for (let i = 0; i < segment.length; i++) {
|
|
78
|
+
const ch = String.fromCodePoint(segment.codePointAt(i));
|
|
79
|
+
if ('@+?!*'.includes(ch) && segment[i + 1] === '(')
|
|
80
|
+
unsupported();
|
|
81
|
+
if (ch === '*' && segment[i + 1] === '*')
|
|
82
|
+
unsupported();
|
|
83
|
+
if (ch === '*')
|
|
84
|
+
atoms.push({ test: () => true, repeat: true, optional: true });
|
|
85
|
+
else if (ch === '?')
|
|
86
|
+
atoms.push({ test: () => true, repeat: false, optional: false });
|
|
87
|
+
else if (ch === '[') {
|
|
88
|
+
const cls = characterClass(segment, i, true);
|
|
89
|
+
i = cls.end;
|
|
90
|
+
atoms.push({ test: cls.test, repeat: false, optional: false });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
if (ch === '\\' && ++i >= segment.length)
|
|
94
|
+
unsupported();
|
|
95
|
+
const literal = ch === '\\' ? String.fromCodePoint(segment.codePointAt(i)) : ch;
|
|
96
|
+
if (ch !== '\\' && '{}]'.includes(literal))
|
|
97
|
+
unsupported();
|
|
98
|
+
i += literal.length - 1;
|
|
99
|
+
atoms.push({ test: value => value === literal, repeat: false, optional: false });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return atoms;
|
|
103
|
+
});
|
|
104
|
+
return (text, budget) => {
|
|
105
|
+
const parts = text.split('/');
|
|
106
|
+
let active = new Uint8Array(parts.length + 1);
|
|
107
|
+
active[0] = 1;
|
|
108
|
+
for (const segment of segments) {
|
|
109
|
+
const next = new Uint8Array(parts.length + 1);
|
|
110
|
+
for (let j = 0; j <= parts.length; j++) {
|
|
111
|
+
tick(budget);
|
|
112
|
+
if (segment === null) {
|
|
113
|
+
if (active[j] || (j > 0 && next[j - 1]))
|
|
114
|
+
next[j] = 1;
|
|
115
|
+
}
|
|
116
|
+
else if (j < parts.length && active[j] && sequence(segment, parts[j], budget))
|
|
117
|
+
next[j + 1] = 1;
|
|
118
|
+
}
|
|
119
|
+
active = next;
|
|
120
|
+
}
|
|
121
|
+
return !!active[parts.length];
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** Single-line regex: literals, . classes, ^ $, escapes, * + ?. No groups/alternation/counts/lookaround/backrefs. */
|
|
125
|
+
export function regex(pattern) {
|
|
126
|
+
boundedPattern(pattern);
|
|
127
|
+
const startAnchor = pattern.startsWith('^');
|
|
128
|
+
let endAnchor = false;
|
|
129
|
+
const atoms = [];
|
|
130
|
+
for (let i = startAnchor ? 1 : 0; i < pattern.length; i++) {
|
|
131
|
+
const ch = String.fromCodePoint(pattern.codePointAt(i));
|
|
132
|
+
if (ch === '$' && i === pattern.length - 1) {
|
|
133
|
+
endAnchor = true;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
let test = () => false;
|
|
137
|
+
if (ch === '.')
|
|
138
|
+
test = () => true;
|
|
139
|
+
else if (ch === '[') {
|
|
140
|
+
const cls = characterClass(pattern, i);
|
|
141
|
+
test = cls.test;
|
|
142
|
+
i = cls.end;
|
|
143
|
+
}
|
|
144
|
+
else if (ch === '\\') {
|
|
145
|
+
const escaped = pattern[++i] ?? unsupported();
|
|
146
|
+
if (/[0-9]/.test(escaped))
|
|
147
|
+
unsupported();
|
|
148
|
+
if (escaped === 'd')
|
|
149
|
+
test = value => value >= '0' && value <= '9';
|
|
150
|
+
else if (escaped === 'w')
|
|
151
|
+
test = value => /^[A-Za-z0-9_]$/.test(value);
|
|
152
|
+
else if (escaped === 's')
|
|
153
|
+
test = value => /^\s$/.test(value);
|
|
154
|
+
else if ('\\.^$*+?[](){}|'.includes(escaped))
|
|
155
|
+
test = value => value === escaped;
|
|
156
|
+
else
|
|
157
|
+
unsupported();
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
if ('^$*+?(){}|]'.includes(ch))
|
|
161
|
+
unsupported();
|
|
162
|
+
test = value => value === ch;
|
|
163
|
+
}
|
|
164
|
+
if (ch.length > 1)
|
|
165
|
+
i += ch.length - 1;
|
|
166
|
+
const quantifier = '*+?'.includes(pattern[i + 1] ?? '\0') ? pattern[++i] : '';
|
|
167
|
+
atoms.push({ test, repeat: quantifier === '*' || quantifier === '+', optional: quantifier === '*' || quantifier === '?' });
|
|
168
|
+
}
|
|
169
|
+
return (text, budget) => sequence(atoms, text, budget, startAnchor, endAnchor);
|
|
170
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { constants, realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import { lstat, open } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
// Private repository-pack helpers. These checks are not an adversarial sandbox.
|
|
6
|
+
export class FileFault extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(code, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export const digest = (text) => createHash('sha256').update(text).digest('hex');
|
|
14
|
+
export function bounded(value, maximum, label) {
|
|
15
|
+
const result = value ?? maximum;
|
|
16
|
+
if (!Number.isSafeInteger(result) || result < 1 || result > maximum)
|
|
17
|
+
throw new Error(`${label} must be in [1, ${maximum}]`);
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
export function workspaceRoot(root) {
|
|
21
|
+
if (!path.isAbsolute(root))
|
|
22
|
+
throw new Error('An explicit absolute workspace root is required');
|
|
23
|
+
const result = realpathSync(root);
|
|
24
|
+
if (!statSync(result).isDirectory())
|
|
25
|
+
throw new Error('Workspace must be a directory');
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
export function validPath(relative) {
|
|
29
|
+
return typeof relative === 'string' && !!relative.trim() && Buffer.byteLength(relative) <= 4096
|
|
30
|
+
&& !/[\0\r\n]/.test(relative) && !path.isAbsolute(relative) && !relative.split(/[\\/]/).includes('..');
|
|
31
|
+
}
|
|
32
|
+
export async function inspect(target) {
|
|
33
|
+
const info = await lstat(target);
|
|
34
|
+
if (info.isSymbolicLink())
|
|
35
|
+
throw new FileFault('symlink_denied', 'Symlinks are not allowed');
|
|
36
|
+
if (!info.isDirectory() && (!info.isFile() || info.nlink !== 1))
|
|
37
|
+
throw new FileFault('not_regular_file', 'Expected directories or single-link files');
|
|
38
|
+
return info;
|
|
39
|
+
}
|
|
40
|
+
export async function checkPath(root, relative, allowMissing = false) {
|
|
41
|
+
if (!validPath(relative))
|
|
42
|
+
throw new FileFault('path_out_of_scope', 'Expected a relative path without parent traversal');
|
|
43
|
+
const target = path.resolve(root, relative);
|
|
44
|
+
const parts = path.relative(root, target).split(path.sep).filter(Boolean);
|
|
45
|
+
let current = root;
|
|
46
|
+
if (!(await inspect(root)).isDirectory())
|
|
47
|
+
throw new FileFault('not_directory', 'Workspace is not a directory');
|
|
48
|
+
for (let i = 0; i < parts.length; i++) {
|
|
49
|
+
current = path.join(current, parts[i]);
|
|
50
|
+
let info;
|
|
51
|
+
try {
|
|
52
|
+
info = await inspect(current);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (allowMissing && i === parts.length - 1 && error.code === 'ENOENT')
|
|
56
|
+
return target;
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
if (i < parts.length - 1 && !info.isDirectory())
|
|
60
|
+
throw new FileFault('not_directory', 'Expected ordinary directories');
|
|
61
|
+
}
|
|
62
|
+
return target;
|
|
63
|
+
}
|
|
64
|
+
// Shared, deliberately narrow binary classification; not general format detection.
|
|
65
|
+
export const isBinary = (text) => text.includes('\0');
|
|
66
|
+
export function assertText(text) {
|
|
67
|
+
if (isBinary(text))
|
|
68
|
+
throw new FileFault('binary_file', 'NUL-bearing binary content is not supported by text tools');
|
|
69
|
+
}
|
|
70
|
+
export async function readSnapshot(target, maxBytes, signal) {
|
|
71
|
+
const file = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
72
|
+
try {
|
|
73
|
+
const before = await file.stat({ bigint: true });
|
|
74
|
+
const identity = (info) => `${info.dev}:${info.ino}:${info.size}:${info.mtimeNs}:${info.ctimeNs}`;
|
|
75
|
+
if (!before.isFile() || before.nlink !== 1n)
|
|
76
|
+
throw new FileFault('not_regular_file', 'Expected single-link regular file');
|
|
77
|
+
if (before.size > maxBytes)
|
|
78
|
+
throw new FileFault('too_large', 'File exceeds scan byte limit');
|
|
79
|
+
const buffer = Buffer.alloc(Math.min(Number(before.size) + 1, maxBytes + 1));
|
|
80
|
+
let bytes = 0;
|
|
81
|
+
while (bytes < buffer.length) {
|
|
82
|
+
signal.throwIfAborted();
|
|
83
|
+
const read = await file.read(buffer, bytes, buffer.length - bytes, bytes);
|
|
84
|
+
if (!read.bytesRead)
|
|
85
|
+
break;
|
|
86
|
+
bytes += read.bytesRead;
|
|
87
|
+
}
|
|
88
|
+
const after = await file.stat({ bigint: true });
|
|
89
|
+
if (bytes > maxBytes)
|
|
90
|
+
throw new FileFault('too_large', 'File exceeds scan byte limit');
|
|
91
|
+
if (identity(before) !== identity(after) || BigInt(bytes) !== before.size)
|
|
92
|
+
throw new FileFault('edit_conflict', 'File changed while reading');
|
|
93
|
+
const raw = buffer.subarray(0, bytes);
|
|
94
|
+
let text;
|
|
95
|
+
try {
|
|
96
|
+
text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new FileFault('invalid_utf8', 'File is not valid UTF-8');
|
|
100
|
+
}
|
|
101
|
+
return { text, bytes, sha256: digest(raw), identity: identity(after) };
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
await file.close();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Include the escaped inner JSON in the outer outcome and ToolManager's message.
|
|
108
|
+
export function fits(outcome, maximum, name, toolCallId) {
|
|
109
|
+
return Buffer.byteLength(JSON.stringify(outcome)) <= maximum
|
|
110
|
+
&& Buffer.byteLength(JSON.stringify({ role: 'tool', name, toolCallId, outcome })) <= 512 * 1024;
|
|
111
|
+
}
|
|
112
|
+
export function cursor(binding, fingerprint, offset) {
|
|
113
|
+
return Buffer.from(JSON.stringify({ v: 1, binding, fingerprint, offset })).toString('base64url');
|
|
114
|
+
}
|
|
115
|
+
export function readCursor(value, binding, fingerprint, maximum) {
|
|
116
|
+
if (!value)
|
|
117
|
+
return 0;
|
|
118
|
+
let parsed;
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
throw new FileFault('invalid_cursor', 'Cursor cannot be decoded');
|
|
124
|
+
}
|
|
125
|
+
if (!parsed || parsed.v !== 1 || parsed.binding !== binding || !Number.isSafeInteger(parsed.offset)
|
|
126
|
+
|| parsed.offset < 0 || parsed.offset > maximum)
|
|
127
|
+
throw new FileFault('invalid_cursor', 'Cursor does not match request');
|
|
128
|
+
if (parsed.fingerprint !== fingerprint)
|
|
129
|
+
throw new FileFault('stale_cursor', 'Files or coverage changed; restart query');
|
|
130
|
+
return parsed.offset;
|
|
131
|
+
}
|
|
132
|
+
export const lines = (text) => text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
133
|
+
export function replace(text, edits) {
|
|
134
|
+
let result = text;
|
|
135
|
+
for (const edit of [...edits].reverse())
|
|
136
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
// Git's quoted paths use C escapes, not JSON's \uXXXX. Keep UTF-8 literal;
|
|
140
|
+
// three-digit octal escapes avoid ambiguity with following filename digits.
|
|
141
|
+
function quoteDiffPath(value) {
|
|
142
|
+
return '"' + value.replace(/[\x00-\x1f\x7f"\\]/g, character => {
|
|
143
|
+
if (character === '"' || character === '\\')
|
|
144
|
+
return '\\' + character;
|
|
145
|
+
return '\\' + character.charCodeAt(0).toString(8).padStart(3, '0');
|
|
146
|
+
}) + '"';
|
|
147
|
+
}
|
|
148
|
+
/** Edit-derived hunks, not a quadratic whole-file diff or a Git HEAD diff. */
|
|
149
|
+
export function diff(relative, before, edits, next, creating = false) {
|
|
150
|
+
if (before === next && !creating)
|
|
151
|
+
return '';
|
|
152
|
+
if (creating && !next)
|
|
153
|
+
return `diff --git ${quoteDiffPath(`a/${relative}`)} ${quoteDiffPath(`b/${relative}`)}\nnew file mode 100600\n`;
|
|
154
|
+
const oldLines = lines(before);
|
|
155
|
+
const starts = [0];
|
|
156
|
+
for (const line of oldLines)
|
|
157
|
+
starts.push(starts[starts.length - 1] + line.length);
|
|
158
|
+
const groups = [];
|
|
159
|
+
for (const edit of edits) {
|
|
160
|
+
let first = 0;
|
|
161
|
+
while (first < oldLines.length && starts[first + 1] <= edit.start)
|
|
162
|
+
first++;
|
|
163
|
+
let last = first;
|
|
164
|
+
while (last < oldLines.length && starts[last] < edit.end)
|
|
165
|
+
last++;
|
|
166
|
+
const group = { start: Math.max(0, first - 3), end: Math.min(oldLines.length, Math.max(first + 1, last) + 3) };
|
|
167
|
+
const previous = groups[groups.length - 1];
|
|
168
|
+
if (previous && group.start <= previous.end)
|
|
169
|
+
previous.end = Math.max(previous.end, group.end);
|
|
170
|
+
else
|
|
171
|
+
groups.push(group);
|
|
172
|
+
}
|
|
173
|
+
let output = `--- ${creating ? '/dev/null' : quoteDiffPath(`a/${relative}`)}\n+++ ${quoteDiffPath(`b/${relative}`)}\n`;
|
|
174
|
+
let lineDelta = 0;
|
|
175
|
+
const print = (prefix, line) => prefix + line + (line.endsWith('\n') ? '' : '\n\\n');
|
|
176
|
+
for (const group of groups) {
|
|
177
|
+
const start = starts[group.start];
|
|
178
|
+
const end = starts[group.end];
|
|
179
|
+
const local = edits.filter(edit => edit.start >= start && edit.end <= end)
|
|
180
|
+
.map(edit => ({ ...edit, start: edit.start - start, end: edit.end - start }));
|
|
181
|
+
const old = oldLines.slice(group.start, group.end);
|
|
182
|
+
const updated = lines(replace(before.slice(start, end), local));
|
|
183
|
+
let prefix = 0;
|
|
184
|
+
while (prefix < old.length && prefix < updated.length && old[prefix] === updated[prefix])
|
|
185
|
+
prefix++;
|
|
186
|
+
let suffix = 0;
|
|
187
|
+
while (suffix < old.length - prefix && suffix < updated.length - prefix
|
|
188
|
+
&& old[old.length - 1 - suffix] === updated[updated.length - 1 - suffix])
|
|
189
|
+
suffix++;
|
|
190
|
+
output += `@@ -${old.length ? group.start + 1 : group.start},${old.length} +${updated.length ? group.start + lineDelta + 1 : group.start + lineDelta},${updated.length} @@\n`;
|
|
191
|
+
for (const line of old.slice(0, prefix))
|
|
192
|
+
output += print(' ', line);
|
|
193
|
+
for (const line of old.slice(prefix, old.length - suffix))
|
|
194
|
+
output += print('-', line);
|
|
195
|
+
for (const line of updated.slice(prefix, updated.length - suffix))
|
|
196
|
+
output += print('+', line);
|
|
197
|
+
for (const line of old.slice(old.length - suffix))
|
|
198
|
+
output += print(' ', line);
|
|
199
|
+
lineDelta += updated.length - old.length;
|
|
200
|
+
}
|
|
201
|
+
return output;
|
|
202
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { createAgent } from './agent.js';
|
|
2
|
+
export { createToolExecutor } from './toolManager/index.js';
|
|
3
|
+
export { createContextBuilder, createProviderCompressor, ContextBuildError, jsonByteEstimator } from './context/index.js';
|
|
4
|
+
export { createProjectGuidance } from './projectGuidance/index.js';
|
|
5
|
+
export { createLocalObserver } from './observability/index.js';
|
|
6
|
+
export { createModelGateway, createChatCompletionsAdapter, createResponsesAdapter, createAnthropicMessagesAdapter, createProtocolAdapter, getProtocolCapabilities, createModelObservationAdapter, ModelGatewayError } from './model/index.js';
|
|
7
|
+
export { openCli, createTextRenderer } from './interaction/index.js';
|
|
8
|
+
export { connectModelObservations, createInteractionDiagnostics, createInteractionProgress } from './composition.js';
|
|
9
|
+
export { createSessionStore, createMemorySessionBackend, createSqliteSessionBackend, SessionError, SessionMetadataSaveError, catalogLimits, locationLimits, } from './session/index.js';
|
|
10
|
+
export { createExecutionOwner } from './executionOwner.js';
|
|
11
|
+
export { createMockProvider } from './mockProvider.js';
|
|
12
|
+
export { createTextTools } from './textTools.js';
|
|
13
|
+
export { createSearchTools } from './searchTools.js';
|
|
14
|
+
export { createCommandTool } from './commandTool.js';
|
|
15
|
+
export { createCodingTools } from './codingTools.js';
|
|
16
|
+
export { createLocalToolBinding } from './localSafety.js';
|