@agent-spaces/server 0.3.61 → 0.3.62
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/dist/adapters/claude-code-runtime/index.js +19 -0
- package/dist/adapters/claude-code-runtime/sdk-config.js +34 -10
- package/dist/adapters/git.js +29 -13
- package/dist/agents/agent-message-parts.js +27 -3
- package/dist/package.json +1 -1
- package/dist/routes/channel.js +21 -4
- package/dist/routes/file.js +87 -1
- package/dist/routes/skill.js +26 -3
- package/dist/services/agent.js +45 -16
- package/dist/services/persistent-agent-context.js +37 -4
- package/dist/services/skill.js +163 -41
- package/dist/web/404.html +1 -1
- package/dist/web/__next.__PAGE__.txt +4 -4
- package/dist/web/__next._full.txt +23 -22
- package/dist/web/__next._head.txt +4 -4
- package/dist/web/__next._index.txt +13 -13
- package/dist/web/__next._tree.txt +2 -2
- package/dist/web/_next/static/chunks/{0_-c.y2qq4ax4.js → 0-81wi7xr2nd-.js} +56 -56
- package/dist/web/_next/static/chunks/00lca9lsasdm7.js +1 -0
- package/dist/web/_next/static/chunks/{0sz8_nijfiz0p.js → 02shswpmybici.js} +1 -1
- package/dist/web/_next/static/chunks/05f8cnmzqd5wb.js +1 -0
- package/dist/web/_next/static/chunks/05tx6hzdtkgi9.js +2 -0
- package/dist/web/_next/static/chunks/{0w1iudd1d46sw.js → 07a0rktf_nr5h.js} +1 -1
- package/dist/web/_next/static/chunks/{0_g3irbniww6d.js → 07nbyndvzftr1.js} +1 -1
- package/dist/web/_next/static/chunks/08kfx8on9oty~.js +1 -0
- package/dist/web/_next/static/chunks/{13ess~4pdmxma.js → 0bn~onkv~9xza.js} +1 -1
- package/dist/web/_next/static/chunks/0i51q.5s_-.vh.css +1 -0
- package/dist/web/_next/static/chunks/0k.8qdk7oi_os.js +1 -0
- package/dist/web/_next/static/chunks/0qf163pbrwy4z.js +1 -0
- package/dist/web/_next/static/chunks/{05iy4.c-1cfhd.js → 0rke_patcovgr.js} +1 -1
- package/dist/web/_next/static/chunks/0rnt-f3pjxb-d.js +5 -0
- package/dist/web/_next/static/chunks/0tgbcgsca9mrg.js +1 -0
- package/dist/web/_next/static/chunks/0tyhv6.y7~yxd.js +1 -0
- package/dist/web/_next/static/chunks/0vuvql8rloda5.js +1 -0
- package/dist/web/_next/static/chunks/0xmmk3vebw8xs.js +1 -0
- package/dist/web/_next/static/chunks/0yo22_w53mo~5.js +1 -0
- package/dist/web/_next/static/chunks/12c0jp0qvlr1t.js +1 -0
- package/dist/web/_next/static/chunks/12pg1nn3v~~5k.js +1 -0
- package/dist/web/_next/static/chunks/16cyr8ilpx1nn.js +1 -0
- package/dist/web/_not-found/__next._full.txt +19 -19
- package/dist/web/_not-found/__next._head.txt +4 -4
- package/dist/web/_not-found/__next._index.txt +13 -13
- package/dist/web/_not-found/__next._not-found.__PAGE__.txt +2 -2
- package/dist/web/_not-found/__next._not-found.txt +3 -3
- package/dist/web/_not-found/__next._tree.txt +2 -2
- package/dist/web/_not-found.html +1 -1
- package/dist/web/_not-found.txt +19 -19
- package/dist/web/icons/file_type_audio.svg +3 -1
- package/dist/web/icons/file_type_video.svg +10 -1
- package/dist/web/index.html +1 -1
- package/dist/web/index.txt +23 -22
- package/dist/web/login/__next._full.txt +24 -22
- package/dist/web/login/__next._head.txt +4 -4
- package/dist/web/login/__next._index.txt +13 -13
- package/dist/web/login/__next._tree.txt +2 -2
- package/dist/web/login/__next.login.__PAGE__.txt +4 -4
- package/dist/web/login/__next.login.txt +3 -3
- package/dist/web/login.html +1 -1
- package/dist/web/login.txt +24 -22
- package/dist/web/settings/__next._full.txt +30 -29
- package/dist/web/settings/__next._head.txt +4 -4
- package/dist/web/settings/__next._index.txt +13 -13
- package/dist/web/settings/__next._tree.txt +2 -2
- package/dist/web/settings/__next.settings.__PAGE__.txt +4 -4
- package/dist/web/settings/__next.settings.txt +5 -5
- package/dist/web/settings/agents/__next._full.txt +35 -34
- package/dist/web/settings/agents/__next._head.txt +4 -4
- package/dist/web/settings/agents/__next._index.txt +13 -13
- package/dist/web/settings/agents/__next._tree.txt +2 -2
- package/dist/web/settings/agents/__next.settings.agents.__PAGE__.txt +4 -4
- package/dist/web/settings/agents/__next.settings.agents.txt +3 -3
- package/dist/web/settings/agents/__next.settings.txt +5 -5
- package/dist/web/settings/agents.html +1 -1
- package/dist/web/settings/agents.txt +35 -34
- package/dist/web/settings/mcps/__next._full.txt +35 -34
- package/dist/web/settings/mcps/__next._head.txt +4 -4
- package/dist/web/settings/mcps/__next._index.txt +13 -13
- package/dist/web/settings/mcps/__next._tree.txt +2 -2
- package/dist/web/settings/mcps/__next.settings.mcps.__PAGE__.txt +4 -4
- package/dist/web/settings/mcps/__next.settings.mcps.txt +3 -3
- package/dist/web/settings/mcps/__next.settings.txt +5 -5
- package/dist/web/settings/mcps.html +1 -1
- package/dist/web/settings/mcps.txt +35 -34
- package/dist/web/settings/models/__next._full.txt +35 -34
- package/dist/web/settings/models/__next._head.txt +4 -4
- package/dist/web/settings/models/__next._index.txt +13 -13
- package/dist/web/settings/models/__next._tree.txt +2 -2
- package/dist/web/settings/models/__next.settings.models.__PAGE__.txt +4 -4
- package/dist/web/settings/models/__next.settings.models.txt +3 -3
- package/dist/web/settings/models/__next.settings.txt +5 -5
- package/dist/web/settings/models.html +1 -1
- package/dist/web/settings/models.txt +35 -34
- package/dist/web/settings/prompts/__next._full.txt +35 -34
- package/dist/web/settings/prompts/__next._head.txt +4 -4
- package/dist/web/settings/prompts/__next._index.txt +13 -13
- package/dist/web/settings/prompts/__next._tree.txt +2 -2
- package/dist/web/settings/prompts/__next.settings.prompts.__PAGE__.txt +4 -4
- package/dist/web/settings/prompts/__next.settings.prompts.txt +3 -3
- package/dist/web/settings/prompts/__next.settings.txt +5 -5
- package/dist/web/settings/prompts.html +1 -1
- package/dist/web/settings/prompts.txt +35 -34
- package/dist/web/settings/providers/__next._full.txt +35 -34
- package/dist/web/settings/providers/__next._head.txt +4 -4
- package/dist/web/settings/providers/__next._index.txt +13 -13
- package/dist/web/settings/providers/__next._tree.txt +2 -2
- package/dist/web/settings/providers/__next.settings.providers.__PAGE__.txt +4 -4
- package/dist/web/settings/providers/__next.settings.providers.txt +3 -3
- package/dist/web/settings/providers/__next.settings.txt +5 -5
- package/dist/web/settings/providers.html +1 -1
- package/dist/web/settings/providers.txt +35 -34
- package/dist/web/settings/skills/__next._full.txt +35 -34
- package/dist/web/settings/skills/__next._head.txt +4 -4
- package/dist/web/settings/skills/__next._index.txt +13 -13
- package/dist/web/settings/skills/__next._tree.txt +2 -2
- package/dist/web/settings/skills/__next.settings.skills.__PAGE__.txt +4 -4
- package/dist/web/settings/skills/__next.settings.skills.txt +3 -3
- package/dist/web/settings/skills/__next.settings.txt +5 -5
- package/dist/web/settings/skills.html +1 -1
- package/dist/web/settings/skills.txt +35 -34
- package/dist/web/settings.html +1 -1
- package/dist/web/settings.txt +30 -29
- package/dist/web/workflows/__next._full.txt +31 -30
- package/dist/web/workflows/__next._head.txt +4 -4
- package/dist/web/workflows/__next._index.txt +13 -13
- package/dist/web/workflows/__next._tree.txt +2 -2
- package/dist/web/workflows/__next.workflows.__PAGE__.txt +4 -4
- package/dist/web/workflows/__next.workflows.txt +3 -3
- package/dist/web/workflows.html +1 -1
- package/dist/web/workflows.txt +31 -30
- package/dist/web/workspace/_/__next._full.txt +56 -55
- package/dist/web/workspace/_/__next._head.txt +4 -4
- package/dist/web/workspace/_/__next._index.txt +13 -13
- package/dist/web/workspace/_/__next._tree.txt +2 -2
- package/dist/web/workspace/_/__next.workspace.$d$id.__PAGE__.txt +3 -3
- package/dist/web/workspace/_/__next.workspace.$d$id.txt +3 -3
- package/dist/web/workspace/_/__next.workspace.txt +3 -3
- package/dist/web/workspace/_.html +1 -1
- package/dist/web/workspace/_.txt +56 -55
- package/dist/web/workspaces/__next._full.txt +30 -27
- package/dist/web/workspaces/__next._head.txt +4 -4
- package/dist/web/workspaces/__next._index.txt +13 -13
- package/dist/web/workspaces/__next._tree.txt +2 -2
- package/dist/web/workspaces/__next.workspaces.__PAGE__.txt +4 -4
- package/dist/web/workspaces/__next.workspaces.txt +3 -3
- package/dist/web/workspaces.html +1 -1
- package/dist/web/workspaces.txt +30 -27
- package/dist/ws/agent-runner.js +51 -31
- package/dist/ws/message-parts.js +61 -2
- package/package.json +1 -1
- package/dist/web/_next/static/chunks/0-1pn22sg~67u.js +0 -2
- package/dist/web/_next/static/chunks/03me5_ir..2ix.js +0 -1
- package/dist/web/_next/static/chunks/05eu2fdixf7c9.js +0 -1
- package/dist/web/_next/static/chunks/0cs830n97dgjy.js +0 -1
- package/dist/web/_next/static/chunks/0ju52ko_nh7gi.js +0 -1
- package/dist/web/_next/static/chunks/0mny8i81aenx3.js +0 -1
- package/dist/web/_next/static/chunks/0nsu~f3voe~0i.js +0 -5
- package/dist/web/_next/static/chunks/0w3o.7vi.dlm6.js +0 -1
- package/dist/web/_next/static/chunks/0x4b-a2ixww6m.js +0 -1
- package/dist/web/_next/static/chunks/0~w6ugk9s5p-h.css +0 -1
- package/dist/web/_next/static/chunks/125nunjfj2fj0.js +0 -1
- package/dist/web/_next/static/chunks/1285yqy_oirb-.js +0 -1
- package/dist/web/_next/static/chunks/14n8i2xz4_y-e.js +0 -1
- /package/dist/web/_next/static/{HUlYEknPhyW7dRmJ5mFdG → GS3b40oeaeLxm7i9-ibaj}/_buildManifest.js +0 -0
- /package/dist/web/_next/static/{HUlYEknPhyW7dRmJ5mFdG → GS3b40oeaeLxm7i9-ibaj}/_clientMiddlewareManifest.js +0 -0
- /package/dist/web/_next/static/{HUlYEknPhyW7dRmJ5mFdG → GS3b40oeaeLxm7i9-ibaj}/_ssgManifest.js +0 -0
|
@@ -84,9 +84,12 @@ export class ClaudeCodeRuntime {
|
|
|
84
84
|
options?.onEvent?.({ type: 'session', sessionId });
|
|
85
85
|
}
|
|
86
86
|
const toolUses = extractToolUseEvents(message);
|
|
87
|
+
let sawAskUserQuestion = false;
|
|
87
88
|
for (const toolUse of toolUses) {
|
|
88
89
|
if (toolUse.name === 'AskUserQuestion') {
|
|
89
90
|
pendingAskUserQuestionToolIds.add(toolUse.id);
|
|
91
|
+
waitingForUserAnswer = true;
|
|
92
|
+
sawAskUserQuestion = true;
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
const toolResult = extractToolResultEvent(message);
|
|
@@ -133,8 +136,24 @@ export class ClaudeCodeRuntime {
|
|
|
133
136
|
error = message.errors.join('\n') || message.subtype;
|
|
134
137
|
}
|
|
135
138
|
}
|
|
139
|
+
if (sawAskUserQuestion)
|
|
140
|
+
break;
|
|
136
141
|
}
|
|
137
142
|
const elapsed = Date.now() - startTime;
|
|
143
|
+
if (waitingForUserAnswer) {
|
|
144
|
+
d(`waiting for user answer ${elapsed}ms | turns=${turns} tokens=${tokenCount}`);
|
|
145
|
+
if (usageLine)
|
|
146
|
+
output.push(usageLine);
|
|
147
|
+
return {
|
|
148
|
+
success: true,
|
|
149
|
+
summary: 'Waiting for user answer',
|
|
150
|
+
artifacts: [],
|
|
151
|
+
output,
|
|
152
|
+
usage,
|
|
153
|
+
costUsd,
|
|
154
|
+
sessionId,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
138
157
|
if (!sawResult) {
|
|
139
158
|
const runtimeError = extractRuntimeError([...stderrLines, ...output])
|
|
140
159
|
|| 'Claude Code execution stopped before reporting a final result';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
1
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { extname, isAbsolute, resolve } from 'node:path';
|
|
3
|
+
import { basename, extname, isAbsolute, resolve } from 'node:path';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
|
|
6
6
|
import { isAnthropicBridgeProvider } from './types.js';
|
|
@@ -125,7 +125,7 @@ export function normalizeSkillNames(skills, configDir) {
|
|
|
125
125
|
if (!Array.isArray(skills))
|
|
126
126
|
return [];
|
|
127
127
|
return skills
|
|
128
|
-
.map(
|
|
128
|
+
.map(sanitizeSkillName)
|
|
129
129
|
.filter((skill) => Boolean(skill) && hasSkillContent(skill, configDir));
|
|
130
130
|
}
|
|
131
131
|
export function prepareConfigDir(configDir, agentDir) {
|
|
@@ -138,19 +138,43 @@ export function prepareConfigDir(configDir, agentDir) {
|
|
|
138
138
|
mkdirSync(targetSkillsDir, { recursive: true });
|
|
139
139
|
if (!existsSync(sourceSkillsDir))
|
|
140
140
|
return;
|
|
141
|
-
for (const
|
|
142
|
-
|
|
141
|
+
for (const entry of readdirSync(sourceSkillsDir)) {
|
|
142
|
+
const source = join(sourceSkillsDir, entry);
|
|
143
|
+
const sourceStat = statSync(source);
|
|
144
|
+
if (sourceStat.isDirectory()) {
|
|
145
|
+
const skillName = sanitizeSkillName(entry);
|
|
146
|
+
if (!skillName)
|
|
147
|
+
continue;
|
|
148
|
+
const sourceSkillFile = join(source, 'SKILL.md');
|
|
149
|
+
const targetSkillDir = join(targetSkillsDir, skillName);
|
|
150
|
+
if (existsSync(sourceSkillFile) && statSync(sourceSkillFile).size > 0) {
|
|
151
|
+
cpSync(source, targetSkillDir, { recursive: true, force: true });
|
|
152
|
+
}
|
|
143
153
|
continue;
|
|
144
|
-
|
|
154
|
+
}
|
|
155
|
+
if (!sourceStat.isFile() || extname(entry).toLowerCase() !== '.md')
|
|
156
|
+
continue;
|
|
157
|
+
const skillName = sanitizeSkillName(entry);
|
|
158
|
+
if (!skillName || sourceStat.size === 0)
|
|
159
|
+
continue;
|
|
160
|
+
const targetSkillDir = join(targetSkillsDir, skillName);
|
|
161
|
+
mkdirSync(targetSkillDir, { recursive: true });
|
|
162
|
+
writeFileSync(join(targetSkillDir, 'SKILL.md'), readFileSync(source, 'utf-8'), 'utf-8');
|
|
163
|
+
copyFileSync(source, join(targetSkillsDir, `${skillName}.md`));
|
|
145
164
|
}
|
|
146
165
|
}
|
|
147
166
|
function hasSkillContent(skill, configDir) {
|
|
148
167
|
if (!configDir)
|
|
149
168
|
return true;
|
|
150
|
-
const skillFile = join(configDir, 'skills',
|
|
151
|
-
if (
|
|
152
|
-
return
|
|
153
|
-
|
|
169
|
+
const skillFile = join(configDir, 'skills', skill, 'SKILL.md');
|
|
170
|
+
if (existsSync(skillFile) && statSync(skillFile).size > 0)
|
|
171
|
+
return true;
|
|
172
|
+
const legacySkillFile = join(configDir, 'skills', `${skill}.md`);
|
|
173
|
+
return existsSync(legacySkillFile) && statSync(legacySkillFile).size > 0;
|
|
174
|
+
}
|
|
175
|
+
function sanitizeSkillName(name) {
|
|
176
|
+
const raw = basename(name).replace(/\.md$/i, '').trim();
|
|
177
|
+
return raw.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
154
178
|
}
|
|
155
179
|
export function normalizePermissionMode(permissionMode) {
|
|
156
180
|
if (permissionMode === 'default' ||
|
package/dist/adapters/git.js
CHANGED
|
@@ -3,6 +3,19 @@ import { getWorkspace } from '../storage/workspace-store.js';
|
|
|
3
3
|
import { writeFile, readFile, mkdir } from 'node:fs/promises';
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
5
|
import { join, basename } from 'node:path';
|
|
6
|
+
const BINARY_EXTENSIONS = new Set([
|
|
7
|
+
'png', 'jpg', 'jpeg', 'gif', 'bmp', 'ico', 'webp', 'svg', 'tiff', 'tif',
|
|
8
|
+
'mp3', 'mp4', 'wav', 'avi', 'mov', 'mkv', 'flv', 'wmv', 'webm',
|
|
9
|
+
'zip', 'tar', 'gz', 'bz2', 'xz', '7z', 'rar', 'dmg', 'iso',
|
|
10
|
+
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
|
11
|
+
'woff', 'woff2', 'ttf', 'otf', 'eot',
|
|
12
|
+
'exe', 'dll', 'so', 'dylib', 'bin', 'dat',
|
|
13
|
+
'sqlite', 'db', 'pyc', 'o', 'obj', 'class', 'jar', 'wasm',
|
|
14
|
+
]);
|
|
15
|
+
function isBinaryPath(filePath) {
|
|
16
|
+
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
17
|
+
return ext ? BINARY_EXTENSIONS.has(ext) : true;
|
|
18
|
+
}
|
|
6
19
|
const gitInstances = new Map();
|
|
7
20
|
function getGitOptions() {
|
|
8
21
|
const proxy = process.env.HTTPS_PROXY || process.env.https_proxy
|
|
@@ -115,9 +128,9 @@ export async function gitDiff(workspaceId, filePath) {
|
|
|
115
128
|
const newContent = await import('node:fs/promises').then(fs => fs.readFile(`${ws.boundDirs[0]}/${filePath}`, 'utf-8')).catch(() => '');
|
|
116
129
|
diffs.push({
|
|
117
130
|
path: filePath,
|
|
118
|
-
oldContent,
|
|
119
|
-
newContent,
|
|
120
|
-
isBinary:
|
|
131
|
+
oldContent: isBinaryPath(filePath) ? '' : oldContent,
|
|
132
|
+
newContent: isBinaryPath(filePath) ? '' : newContent,
|
|
133
|
+
isBinary: isBinaryPath(filePath),
|
|
121
134
|
isNew: !oldContent,
|
|
122
135
|
isDeleted: !newContent,
|
|
123
136
|
});
|
|
@@ -126,13 +139,14 @@ export async function gitDiff(workspaceId, filePath) {
|
|
|
126
139
|
const raw = await git.diff(['--name-only']);
|
|
127
140
|
const files = raw.split('\n').filter(Boolean);
|
|
128
141
|
for (const f of files) {
|
|
129
|
-
const
|
|
130
|
-
const
|
|
142
|
+
const binary = isBinaryPath(f);
|
|
143
|
+
const oldContent = binary ? '' : await git.show([`HEAD:${f}`]).catch(() => '');
|
|
144
|
+
const newContent = binary ? '' : await import('node:fs/promises').then(fs => fs.readFile(`${ws.boundDirs[0]}/${f}`, 'utf-8')).catch(() => '');
|
|
131
145
|
diffs.push({
|
|
132
146
|
path: f,
|
|
133
147
|
oldContent,
|
|
134
148
|
newContent,
|
|
135
|
-
isBinary:
|
|
149
|
+
isBinary: binary,
|
|
136
150
|
isNew: !oldContent,
|
|
137
151
|
isDeleted: !newContent,
|
|
138
152
|
});
|
|
@@ -140,13 +154,14 @@ export async function gitDiff(workspaceId, filePath) {
|
|
|
140
154
|
// Also check untracked (staged new files)
|
|
141
155
|
const status = await git.status();
|
|
142
156
|
for (const f of status.not_added) {
|
|
143
|
-
const
|
|
144
|
-
|
|
157
|
+
const binary = isBinaryPath(f);
|
|
158
|
+
const newContent = binary ? '' : await import('node:fs/promises').then(fs => fs.readFile(`${ws.boundDirs[0]}/${f}`, 'utf-8')).catch(() => '');
|
|
159
|
+
if (newContent || binary) {
|
|
145
160
|
diffs.push({
|
|
146
161
|
path: f,
|
|
147
162
|
oldContent: '',
|
|
148
|
-
newContent,
|
|
149
|
-
isBinary:
|
|
163
|
+
newContent: binary ? '' : newContent,
|
|
164
|
+
isBinary: binary,
|
|
150
165
|
isNew: true,
|
|
151
166
|
isDeleted: false,
|
|
152
167
|
});
|
|
@@ -337,9 +352,10 @@ export async function gitCommitDiff(workspaceId, commitHash) {
|
|
|
337
352
|
const files = nameOnlyRaw.split('\n').filter(Boolean);
|
|
338
353
|
const diffs = [];
|
|
339
354
|
for (const f of files) {
|
|
340
|
-
const
|
|
341
|
-
const
|
|
342
|
-
|
|
355
|
+
const binary = isBinaryPath(f);
|
|
356
|
+
const oldContent = binary ? '' : await git.show([`${commitHash}^:${f}`]).catch(() => '');
|
|
357
|
+
const newContent = binary ? '' : await git.show([`${commitHash}:${f}`]).catch(() => '');
|
|
358
|
+
diffs.push({ path: f, oldContent, newContent, isBinary: binary, isNew: !oldContent, isDeleted: !newContent });
|
|
343
359
|
}
|
|
344
360
|
return diffs;
|
|
345
361
|
}
|
|
@@ -46,7 +46,7 @@ export function createAgentMessagePartsTracker(input) {
|
|
|
46
46
|
output.push(event.line);
|
|
47
47
|
input.onOutput?.(event.line);
|
|
48
48
|
},
|
|
49
|
-
buildParts({ sessionId, workspaceRoot, model, usage, success, error }) {
|
|
49
|
+
buildParts({ sessionId, workspaceRoot, model, usage, persistentContext, success, error }) {
|
|
50
50
|
return buildAgentMessageParts({
|
|
51
51
|
sessionId,
|
|
52
52
|
workspaceRoot,
|
|
@@ -55,6 +55,7 @@ export function createAgentMessagePartsTracker(input) {
|
|
|
55
55
|
output,
|
|
56
56
|
reasoning,
|
|
57
57
|
toolDetails,
|
|
58
|
+
persistentContext,
|
|
58
59
|
success,
|
|
59
60
|
error,
|
|
60
61
|
});
|
|
@@ -69,7 +70,7 @@ function buildAgentMessageParts(input) {
|
|
|
69
70
|
: '';
|
|
70
71
|
const usage = input.usage ?? extractUsage(lines);
|
|
71
72
|
const parts = [];
|
|
72
|
-
const chainItems = buildChainItems(lines, finalTextRange, finalText, input.workspaceRoot, input.toolDetails);
|
|
73
|
+
const chainItems = buildChainItems(lines, finalTextRange, finalText, input.workspaceRoot, input.toolDetails, input.persistentContext);
|
|
73
74
|
const reasoningText = normalizeReasoningText(input.reasoning);
|
|
74
75
|
if (reasoningText) {
|
|
75
76
|
parts.push({
|
|
@@ -181,12 +182,17 @@ function findFinalTextRange(lines) {
|
|
|
181
182
|
}
|
|
182
183
|
return { start, end };
|
|
183
184
|
}
|
|
184
|
-
function buildChainItems(lines, finalTextRange, finalText, workspaceRoot, toolDetails) {
|
|
185
|
+
function buildChainItems(lines, finalTextRange, finalText, workspaceRoot, toolDetails, persistentContext) {
|
|
185
186
|
let toolIndex = 0;
|
|
186
187
|
let messageIndex = 0;
|
|
187
188
|
const items = [];
|
|
188
189
|
const toolDetailMatchCounts = new Map();
|
|
189
190
|
let messageBuffer = [];
|
|
191
|
+
const persistentContextItem = buildPersistentContextToolItem(persistentContext);
|
|
192
|
+
if (persistentContextItem) {
|
|
193
|
+
items.push(persistentContextItem);
|
|
194
|
+
toolIndex += 1;
|
|
195
|
+
}
|
|
190
196
|
const flushMessageBuffer = () => {
|
|
191
197
|
if (messageBuffer.length === 0)
|
|
192
198
|
return;
|
|
@@ -229,6 +235,7 @@ function buildToolTodo(line, index, workspaceRoot, toolDetails, toolDetailMatchC
|
|
|
229
235
|
const detailId = findToolDetailId(line, toolDetails, toolDetailMatchCounts);
|
|
230
236
|
return {
|
|
231
237
|
id: `tool-${index}`,
|
|
238
|
+
kind: 'tool',
|
|
232
239
|
title: summary.title,
|
|
233
240
|
description: summary.description,
|
|
234
241
|
status: 'completed',
|
|
@@ -238,6 +245,23 @@ function buildToolTodo(line, index, workspaceRoot, toolDetails, toolDetailMatchC
|
|
|
238
245
|
detailId,
|
|
239
246
|
};
|
|
240
247
|
}
|
|
248
|
+
function buildPersistentContextToolItem(persistentContext) {
|
|
249
|
+
const counts = persistentContext?.counts;
|
|
250
|
+
if (!counts?.total)
|
|
251
|
+
return null;
|
|
252
|
+
const description = [
|
|
253
|
+
counts.claudeMd ? `${counts.claudeMd} claude.md` : null,
|
|
254
|
+
counts.agentsMd ? `${counts.agentsMd} AGENTS.md` : null,
|
|
255
|
+
].filter(Boolean).join(', ') || `${counts.total} instruction files`;
|
|
256
|
+
return {
|
|
257
|
+
id: 'tool-persistent-context',
|
|
258
|
+
kind: 'tool',
|
|
259
|
+
title: 'Load claude.md',
|
|
260
|
+
description,
|
|
261
|
+
status: 'completed',
|
|
262
|
+
toolName: 'LoadClaudeMd',
|
|
263
|
+
};
|
|
264
|
+
}
|
|
241
265
|
function summarizeToolLine(line, workspaceRoot) {
|
|
242
266
|
const trimmed = line.trim();
|
|
243
267
|
const toolName = extractToolName(trimmed);
|
package/dist/package.json
CHANGED
package/dist/routes/channel.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Router } from 'express';
|
|
|
2
2
|
import { listChannels, createChannel, getChannel, updateChannel, deleteChannel } from '../services/channel.js';
|
|
3
3
|
import { listMessages, createMessage, updateMessage, deleteMessage, clearMessages } from '../services/message.js';
|
|
4
4
|
import { broadcastToWorkspace } from '../ws/connection-manager.js';
|
|
5
|
-
import { hasActiveChannelRuns,
|
|
5
|
+
import { hasActiveChannelRuns, stopChannelRuns } from '../ws/agent-runner.js';
|
|
6
6
|
import { getToolDetail } from '../services/tool-detail.js';
|
|
7
7
|
import * as issueService from '../services/issue.js';
|
|
8
8
|
const router = Router({ mergeParams: true });
|
|
@@ -69,11 +69,28 @@ router.get('/:channelId/messages', (req, res) => {
|
|
|
69
69
|
res.status(404).json({ error: 'channel not found' });
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
|
-
if (!hasActiveChannelRuns(id, channelId)) {
|
|
73
|
-
markInactiveChannelRunsStopped(id, channelId);
|
|
74
|
-
}
|
|
75
72
|
res.json(listMessages(id, channelId, { limit: limit ? Number(limit) : undefined, before: before }));
|
|
76
73
|
});
|
|
74
|
+
// GET /api/workspaces/:id/channels/:channelId/state
|
|
75
|
+
router.get('/:channelId/state', (req, res) => {
|
|
76
|
+
const { id, channelId } = req.params;
|
|
77
|
+
if (!getChannel(id, channelId)) {
|
|
78
|
+
res.status(404).json({ error: 'channel not found' });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const messages = listMessages(id, channelId, { limit: 1 });
|
|
82
|
+
const lastMessage = messages.at(-1);
|
|
83
|
+
res.json({
|
|
84
|
+
channelId,
|
|
85
|
+
active: hasActiveChannelRuns(id, channelId),
|
|
86
|
+
lastMessage: lastMessage ? {
|
|
87
|
+
id: lastMessage.id,
|
|
88
|
+
status: lastMessage.status,
|
|
89
|
+
metadata: lastMessage.metadata,
|
|
90
|
+
hasPendingQuestion: Boolean(lastMessage.parts?.some((part) => (part.type === 'ask_user_question' && part.status !== 'answered' && !part.answer))),
|
|
91
|
+
} : null,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
77
94
|
// GET /api/workspaces/:id/channels/:channelId/messages/:messageId/tool-details/:detailId
|
|
78
95
|
router.get('/:channelId/messages/:messageId/tool-details/:detailId', (req, res) => {
|
|
79
96
|
const { id, channelId, messageId, detailId } = req.params;
|
package/dist/routes/file.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { exec } from 'child_process';
|
|
3
|
-
import { resolve, join } from 'path';
|
|
3
|
+
import { resolve, join, extname } from 'path';
|
|
4
|
+
import { existsSync, createReadStream, statSync } from 'node:fs';
|
|
4
5
|
import * as fileService from '../services/file.js';
|
|
5
6
|
import { getDataDir } from '../storage/json-store.js';
|
|
6
7
|
const EDITOR_STATE_PATH = '.agentspace/editor-state.json';
|
|
@@ -16,6 +17,13 @@ router.get('/tree', async (req, res) => {
|
|
|
16
17
|
const tree = await fileService.readTree(ws, path, depth);
|
|
17
18
|
res.json(tree);
|
|
18
19
|
});
|
|
20
|
+
const MIME_MAP = {
|
|
21
|
+
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',
|
|
22
|
+
'.webp': 'image/webp', '.svg': 'image/svg+xml', '.bmp': 'image/bmp', '.ico': 'image/x-icon',
|
|
23
|
+
'.mp4': 'video/mp4', '.webm': 'video/webm', '.ogg': 'video/ogg', '.mov': 'video/quicktime',
|
|
24
|
+
'.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.flac': 'audio/flac', '.aac': 'audio/aac',
|
|
25
|
+
'.m4a': 'audio/mp4', '.opus': 'audio/opus',
|
|
26
|
+
};
|
|
19
27
|
router.get('/content', async (req, res) => {
|
|
20
28
|
const ws = fileService.getWorkspace(req.params.id);
|
|
21
29
|
if (!ws) {
|
|
@@ -27,6 +35,38 @@ router.get('/content', async (req, res) => {
|
|
|
27
35
|
res.status(400).json({ error: 'path is required' });
|
|
28
36
|
return;
|
|
29
37
|
}
|
|
38
|
+
const raw = req.query.raw === 'true';
|
|
39
|
+
if (raw) {
|
|
40
|
+
const abs = fileService.resolvePath(ws, path);
|
|
41
|
+
if (!abs || !existsSync(abs)) {
|
|
42
|
+
res.status(404).json({ error: 'File not found' });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const ext = extname(path).toLowerCase();
|
|
46
|
+
const mime = MIME_MAP[ext] || 'application/octet-stream';
|
|
47
|
+
const stat = statSync(abs);
|
|
48
|
+
const fileSize = stat.size;
|
|
49
|
+
// Handle Range requests for video/audio seeking
|
|
50
|
+
const range = req.headers.range;
|
|
51
|
+
if (range) {
|
|
52
|
+
const parts = range.replace(/bytes=/, '').split('-');
|
|
53
|
+
const start = parseInt(parts[0], 10);
|
|
54
|
+
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
|
|
55
|
+
const chunkSize = end - start + 1;
|
|
56
|
+
res.status(206);
|
|
57
|
+
res.setHeader('Content-Range', `bytes ${start}-${end}/${fileSize}`);
|
|
58
|
+
res.setHeader('Accept-Ranges', 'bytes');
|
|
59
|
+
res.setHeader('Content-Length', chunkSize);
|
|
60
|
+
res.setHeader('Content-Type', mime);
|
|
61
|
+
createReadStream(abs, { start, end }).pipe(res);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
res.setHeader('Content-Type', mime);
|
|
65
|
+
res.setHeader('Accept-Ranges', 'bytes');
|
|
66
|
+
res.setHeader('Content-Length', fileSize);
|
|
67
|
+
createReadStream(abs).pipe(res);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
30
70
|
const result = await fileService.readFileContent(ws, path);
|
|
31
71
|
if (!result) {
|
|
32
72
|
res.status(404).json({ error: 'File not found' });
|
|
@@ -253,5 +293,51 @@ router.post('/upload', async (req, res) => {
|
|
|
253
293
|
}
|
|
254
294
|
res.json({ ok: true, paths: results });
|
|
255
295
|
});
|
|
296
|
+
router.get('/download', async (req, res) => {
|
|
297
|
+
const ws = fileService.getWorkspace(req.params.id);
|
|
298
|
+
if (!ws) {
|
|
299
|
+
res.status(404).json({ error: 'Workspace not found' });
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const path = req.query.path;
|
|
303
|
+
if (!path) {
|
|
304
|
+
res.status(400).json({ error: 'path is required' });
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const abs = fileService.resolvePath(ws, path);
|
|
308
|
+
if (!abs || !existsSync(abs)) {
|
|
309
|
+
res.status(404).json({ error: 'File not found' });
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const stat = statSync(abs);
|
|
313
|
+
if (stat.isDirectory()) {
|
|
314
|
+
res.status(400).json({ error: 'Cannot download a directory' });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const ext = extname(path).toLowerCase();
|
|
318
|
+
const mime = MIME_MAP[ext] || 'application/octet-stream';
|
|
319
|
+
const fileName = path.split('/').pop() || 'download';
|
|
320
|
+
// Handle Range requests for large files
|
|
321
|
+
const range = req.headers.range;
|
|
322
|
+
if (range) {
|
|
323
|
+
const parts = range.replace(/bytes=/, '').split('-');
|
|
324
|
+
const start = parseInt(parts[0], 10);
|
|
325
|
+
const end = parts[1] ? parseInt(parts[1], 10) : stat.size - 1;
|
|
326
|
+
const chunkSize = end - start + 1;
|
|
327
|
+
res.status(206);
|
|
328
|
+
res.setHeader('Content-Range', `bytes ${start}-${end}/${stat.size}`);
|
|
329
|
+
res.setHeader('Accept-Ranges', 'bytes');
|
|
330
|
+
res.setHeader('Content-Length', chunkSize);
|
|
331
|
+
res.setHeader('Content-Type', mime);
|
|
332
|
+
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`);
|
|
333
|
+
createReadStream(abs, { start, end }).pipe(res);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
res.setHeader('Content-Type', mime);
|
|
337
|
+
res.setHeader('Content-Length', stat.size);
|
|
338
|
+
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`);
|
|
339
|
+
res.setHeader('Accept-Ranges', 'bytes');
|
|
340
|
+
createReadStream(abs).pipe(res);
|
|
341
|
+
});
|
|
256
342
|
export default router;
|
|
257
343
|
//# sourceMappingURL=file.js.map
|
package/dist/routes/skill.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import { listSkills, importSkill, toggleFavorite, updateSkillContent, deleteSkill, checkSkillSync, syncSkills } from '../services/skill.js';
|
|
2
|
+
import { listSkills, importSkill, importSkillsBatch, toggleFavorite, toggleEnabled, toggleAllEnabled, updateSkillContent, deleteSkill, checkSkillSync, syncSkills } from '../services/skill.js';
|
|
3
3
|
const router = Router();
|
|
4
4
|
router.get('/', (_req, res) => {
|
|
5
5
|
res.json(listSkills());
|
|
@@ -17,19 +17,42 @@ router.post('/sync', (req, res) => {
|
|
|
17
17
|
res.json({ synced });
|
|
18
18
|
});
|
|
19
19
|
router.post('/import', (req, res) => {
|
|
20
|
-
const { filename, content } = req.body;
|
|
20
|
+
const { filename, content, group } = req.body;
|
|
21
21
|
if (!filename || !content) {
|
|
22
22
|
res.status(400).json({ error: 'filename and content required' });
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
-
const skill = importSkill(filename, content);
|
|
25
|
+
const skill = importSkill(filename, content, group);
|
|
26
26
|
res.json(skill);
|
|
27
27
|
});
|
|
28
|
+
router.post('/import-batch', (req, res) => {
|
|
29
|
+
const { items } = req.body;
|
|
30
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
31
|
+
res.status(400).json({ error: 'items required' });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const skills = importSkillsBatch(items);
|
|
35
|
+
res.json(skills);
|
|
36
|
+
});
|
|
28
37
|
router.post('/:name/favorite', (req, res) => {
|
|
29
38
|
const name = typeof req.params.name === 'string' ? req.params.name : req.params.name[0];
|
|
30
39
|
const favorited = toggleFavorite(name);
|
|
31
40
|
res.json({ favorited });
|
|
32
41
|
});
|
|
42
|
+
router.post('/:name/toggle', (req, res) => {
|
|
43
|
+
const name = typeof req.params.name === 'string' ? req.params.name : req.params.name[0];
|
|
44
|
+
const enabled = toggleEnabled(name);
|
|
45
|
+
res.json({ enabled });
|
|
46
|
+
});
|
|
47
|
+
router.post('/toggle-all', (req, res) => {
|
|
48
|
+
const { names, enabled } = req.body;
|
|
49
|
+
if (!Array.isArray(names) || typeof enabled !== 'boolean') {
|
|
50
|
+
res.status(400).json({ error: 'names and enabled required' });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
toggleAllEnabled(names, enabled);
|
|
54
|
+
res.json({ success: true });
|
|
55
|
+
});
|
|
33
56
|
router.put('/:name', (req, res) => {
|
|
34
57
|
const name = typeof req.params.name === 'string' ? req.params.name : req.params.name[0];
|
|
35
58
|
const { content } = req.body;
|
package/dist/services/agent.js
CHANGED
|
@@ -346,7 +346,13 @@ export function getAvailableSkillNames(agentDir, skills) {
|
|
|
346
346
|
.filter((skill) => {
|
|
347
347
|
if (!skill)
|
|
348
348
|
return false;
|
|
349
|
-
const
|
|
349
|
+
const skillsBase = join(agentDir, 'skills');
|
|
350
|
+
// Check folder structure first, then flat .md
|
|
351
|
+
const skillFolder = join(skillsBase, skill);
|
|
352
|
+
if (existsSync(skillFolder) && statSync(skillFolder).isDirectory()) {
|
|
353
|
+
return readdirSync(skillFolder).some((f) => f.endsWith('.md'));
|
|
354
|
+
}
|
|
355
|
+
const skillFile = join(skillsBase, `${skill}.md`);
|
|
350
356
|
return existsSync(skillFile) && statSync(skillFile).size > 0;
|
|
351
357
|
});
|
|
352
358
|
}
|
|
@@ -448,25 +454,43 @@ function writeAgentTemplate(preset, skillInputs) {
|
|
|
448
454
|
else if (preset.skills?.length) {
|
|
449
455
|
const globalSkillsDir = join(getDataDir(), 'skills');
|
|
450
456
|
const keepFiles = new Set(preset.skills.map((s) => s.endsWith('.md') ? s : `${s}.md`));
|
|
451
|
-
|
|
457
|
+
const keepNames = new Set(preset.skills.map((s) => s.replace(/\.md$/i, '')));
|
|
458
|
+
// Remove skill entries no longer in the list (flat .md files and folders)
|
|
452
459
|
if (existsSync(skillsDir)) {
|
|
453
460
|
for (const existing of readdirSync(skillsDir)) {
|
|
454
|
-
|
|
455
|
-
|
|
461
|
+
const name = existing.endsWith('.md') ? existing.replace(/\.md$/i, '') : existing;
|
|
462
|
+
if (!keepNames.has(name)) {
|
|
463
|
+
rmSync(join(skillsDir, existing), { recursive: true, force: true });
|
|
456
464
|
console.log('[writeAgentTemplate] removed stale skill:', existing);
|
|
457
465
|
}
|
|
458
466
|
}
|
|
459
467
|
}
|
|
460
|
-
// Copy / ensure skill files
|
|
461
|
-
for (const
|
|
462
|
-
const
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
468
|
+
// Copy / ensure skill files (global skills are folders: skills/{name}/SKILL.md)
|
|
469
|
+
for (const skillName of keepNames) {
|
|
470
|
+
const globalFolder = join(globalSkillsDir, skillName);
|
|
471
|
+
const flatTarget = join(skillsDir, `${skillName}.md`);
|
|
472
|
+
const folderTarget = join(skillsDir, skillName);
|
|
473
|
+
console.log('[writeAgentTemplate] skill:', skillName, 'globalFolder:', globalFolder, 'globalExists:', existsSync(globalFolder) && statSync(globalFolder).isDirectory());
|
|
474
|
+
if (existsSync(globalFolder) && statSync(globalFolder).isDirectory()) {
|
|
475
|
+
// Copy entire skill folder
|
|
476
|
+
if (existsSync(folderTarget)) {
|
|
477
|
+
rmSync(folderTarget, { recursive: true, force: true });
|
|
478
|
+
}
|
|
479
|
+
cpSync(globalFolder, folderTarget, { recursive: true, force: true });
|
|
480
|
+
// Remove stale flat .md if it exists
|
|
481
|
+
if (existsSync(flatTarget)) {
|
|
482
|
+
rmSync(flatTarget, { force: true });
|
|
483
|
+
}
|
|
467
484
|
}
|
|
468
|
-
else
|
|
469
|
-
|
|
485
|
+
else {
|
|
486
|
+
// Fallback: single SKILL.md or legacy flat file
|
|
487
|
+
const globalSkillFile = join(globalFolder, 'SKILL.md');
|
|
488
|
+
if (existsSync(globalSkillFile)) {
|
|
489
|
+
copyFileSync(globalSkillFile, flatTarget);
|
|
490
|
+
}
|
|
491
|
+
else if (!existsSync(flatTarget) && !existsSync(folderTarget)) {
|
|
492
|
+
writeFileSync(flatTarget, '', 'utf-8');
|
|
493
|
+
}
|
|
470
494
|
}
|
|
471
495
|
}
|
|
472
496
|
}
|
|
@@ -492,9 +516,14 @@ function copyAgentTemplateToWorkspace(agentId, agentspaceDir) {
|
|
|
492
516
|
if (existsSync(skillsDir)) {
|
|
493
517
|
const workspaceSkillsDir = join(agentspaceDir, 'skills');
|
|
494
518
|
ensureDir(workspaceSkillsDir);
|
|
495
|
-
for (const
|
|
496
|
-
|
|
497
|
-
|
|
519
|
+
for (const entry of readdirSync(skillsDir)) {
|
|
520
|
+
const source = join(skillsDir, entry);
|
|
521
|
+
const sourceStat = statSync(source);
|
|
522
|
+
if (sourceStat.isDirectory()) {
|
|
523
|
+
cpSync(source, join(workspaceSkillsDir, entry), { recursive: true, force: true });
|
|
524
|
+
}
|
|
525
|
+
else if (sourceStat.isFile() && extname(entry).toLowerCase() === '.md') {
|
|
526
|
+
copyFileSync(source, join(workspaceSkillsDir, entry));
|
|
498
527
|
}
|
|
499
528
|
}
|
|
500
529
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, realpathSync, readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
4
4
|
import { getDataDir } from '../storage/json-store.js';
|
|
5
5
|
import { readWorkspacePrompt } from './workspace-prompt.js';
|
|
6
6
|
const INSTRUCTION_FILENAMES = ['CLAUDE.md', 'claude.md', 'AGENTS.md', 'agents.md'];
|
|
@@ -13,10 +13,8 @@ export function prependPersistentAgentContext(prompt, options) {
|
|
|
13
13
|
return `${context}\n\n${prompt}`;
|
|
14
14
|
}
|
|
15
15
|
export function buildPersistentAgentContext(options) {
|
|
16
|
+
const { instructionContext } = buildPersistentAgentContextDetails(options);
|
|
16
17
|
const sections = [];
|
|
17
|
-
const instructionContext = buildInstructionFileContext(options.workingDir, options.boundDirs, {
|
|
18
|
-
excludeNativeClaudeMd: options.excludeNativeClaudeMd,
|
|
19
|
-
});
|
|
20
18
|
if (instructionContext)
|
|
21
19
|
sections.push(instructionContext);
|
|
22
20
|
if (options.includeWorkspacePrompt !== false) {
|
|
@@ -30,8 +28,20 @@ export function buildPersistentAgentContext(options) {
|
|
|
30
28
|
}
|
|
31
29
|
return sections.join('\n\n');
|
|
32
30
|
}
|
|
31
|
+
export function buildPersistentAgentContextDetails(options) {
|
|
32
|
+
const instructionFiles = collectInstructionFiles(options.workingDir, options.boundDirs, {
|
|
33
|
+
excludeNativeClaudeMd: options.excludeNativeClaudeMd,
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
instructionContext: buildInstructionFileContextFromFiles(instructionFiles),
|
|
37
|
+
summary: buildPersistentAgentContextSummary(instructionFiles),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
33
40
|
function buildInstructionFileContext(workingDir, boundDirs, options = {}) {
|
|
34
41
|
const files = collectInstructionFiles(workingDir, boundDirs, options);
|
|
42
|
+
return buildInstructionFileContextFromFiles(files);
|
|
43
|
+
}
|
|
44
|
+
function buildInstructionFileContextFromFiles(files) {
|
|
35
45
|
if (files.length === 0)
|
|
36
46
|
return '';
|
|
37
47
|
const lines = [
|
|
@@ -51,6 +61,28 @@ function buildInstructionFileContext(workingDir, boundDirs, options = {}) {
|
|
|
51
61
|
}
|
|
52
62
|
return lines.join('\n').trim();
|
|
53
63
|
}
|
|
64
|
+
function buildPersistentAgentContextSummary(files) {
|
|
65
|
+
const instructionFiles = files.map((file) => ({
|
|
66
|
+
path: file.path,
|
|
67
|
+
label: file.label,
|
|
68
|
+
filename: file.filename,
|
|
69
|
+
}));
|
|
70
|
+
const counts = instructionFiles.reduce((acc, file) => {
|
|
71
|
+
acc.total += 1;
|
|
72
|
+
if (file.filename === 'CLAUDE.md' || file.filename === 'claude.md') {
|
|
73
|
+
acc.claudeMd += 1;
|
|
74
|
+
}
|
|
75
|
+
if (file.filename === 'AGENTS.md' || file.filename === 'agents.md') {
|
|
76
|
+
acc.agentsMd += 1;
|
|
77
|
+
}
|
|
78
|
+
return acc;
|
|
79
|
+
}, {
|
|
80
|
+
claudeMd: 0,
|
|
81
|
+
agentsMd: 0,
|
|
82
|
+
total: 0,
|
|
83
|
+
});
|
|
84
|
+
return { instructionFiles, counts };
|
|
85
|
+
}
|
|
54
86
|
function collectInstructionFiles(workingDir, boundDirs, options = {}) {
|
|
55
87
|
const cwd = resolve(workingDir || process.cwd());
|
|
56
88
|
const seen = new Set();
|
|
@@ -125,6 +157,7 @@ function addInstructionFile(files, seen, path, label) {
|
|
|
125
157
|
files.push({
|
|
126
158
|
path: realPath,
|
|
127
159
|
label,
|
|
160
|
+
filename: basename(realPath),
|
|
128
161
|
content: readFileSync(path, 'utf-8').trim(),
|
|
129
162
|
});
|
|
130
163
|
}
|