@agent-spaces/server 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/dist/adapters/claude-code-runtime/index.js +48 -6
  2. package/dist/adapters/claude-code-runtime/message-format.js +3 -3
  3. package/dist/adapters/git.js +140 -2
  4. package/dist/agents/agent-designer.js +334 -0
  5. package/dist/agents/agent-message-parts.js +8 -0
  6. package/dist/agents/commit-agent.js +9 -2
  7. package/dist/agents/issue-agent-runner.js +0 -1
  8. package/dist/agents/issue-task-controller.js +14 -4
  9. package/dist/app.js +51 -1
  10. package/dist/package.json +1 -1
  11. package/dist/routes/agent-sse.js +4 -1
  12. package/dist/routes/agent.js +15 -0
  13. package/dist/routes/channel.js +1 -1
  14. package/dist/routes/file.js +97 -1
  15. package/dist/routes/git.js +122 -1
  16. package/dist/routes/iframe-bookmarks.js +33 -0
  17. package/dist/routes/issue.js +12 -0
  18. package/dist/routes/speech-recognition.js +99 -0
  19. package/dist/services/agent.js +48 -5
  20. package/dist/services/builtin-tools.js +67 -1
  21. package/dist/services/channel.js +2 -0
  22. package/dist/services/command-process-manager.js +14 -1
  23. package/dist/services/file.js +86 -6
  24. package/dist/services/gitignore.js +65 -0
  25. package/dist/services/issue-retry.js +2 -1
  26. package/dist/services/issue.js +13 -4
  27. package/dist/services/notification-hub/bot-agent.js +9 -1
  28. package/dist/services/persistent-agent-context.js +155 -0
  29. package/dist/services/pty.js +47 -2
  30. package/dist/services/search.js +11 -3
  31. package/dist/services/speech-recognition/base.js +3 -0
  32. package/dist/services/speech-recognition/index.js +16 -0
  33. package/dist/services/speech-recognition/tencent.js +117 -0
  34. package/dist/storage/iframe-bookmarks-store.js +46 -0
  35. package/dist/storage/speech-recognition-store.js +61 -0
  36. package/dist/web/404.html +1 -1
  37. package/dist/web/__next.__PAGE__.txt +4 -4
  38. package/dist/web/__next._full.txt +31 -26
  39. package/dist/web/__next._head.txt +4 -4
  40. package/dist/web/__next._index.txt +17 -13
  41. package/dist/web/__next._tree.txt +2 -2
  42. package/dist/web/_next/static/chunks/00amm3dvu3po9.js +31 -0
  43. package/dist/web/_next/static/chunks/01hrib8z5bn-6.js +5 -0
  44. package/dist/web/_next/static/chunks/08nbjuc5i-ewu.js +3 -0
  45. package/dist/web/_next/static/chunks/0anuxr11yn5iv.js +2 -0
  46. package/dist/web/_next/static/chunks/0cxrev-hb8gkk.js +2 -0
  47. package/dist/web/_next/static/chunks/0egot8y~i-ge3.js +1 -0
  48. package/dist/web/_next/static/chunks/0e~0o6v_awcod.js +1 -0
  49. package/dist/web/_next/static/chunks/0f_dcx0nu5z7o.js +1 -0
  50. package/dist/web/_next/static/chunks/0fpf.rso9mhc0.js +1 -0
  51. package/dist/web/_next/static/chunks/0jc-4igj54z00.js +189 -0
  52. package/dist/web/_next/static/chunks/0plreh4_gkxf9.js +1 -0
  53. package/dist/web/_next/static/chunks/0u7_qd9hzqwac.js +1 -0
  54. package/dist/web/_next/static/chunks/0uo5x.bu.5u2-.css +1 -0
  55. package/dist/web/_next/static/chunks/0utxlfbckz4f5.js +1 -0
  56. package/dist/web/_next/static/chunks/0vesz93a98px4.js +1 -0
  57. package/dist/web/_next/static/chunks/0w1iudd1d46sw.js +8 -0
  58. package/dist/web/_next/static/chunks/0w3o.7vi.dlm6.js +1 -0
  59. package/dist/web/_next/static/chunks/0xtmr0y4vo-~p.js +1 -0
  60. package/dist/web/_next/static/chunks/11ozf700niy2z.js +1 -0
  61. package/dist/web/_next/static/chunks/{0jmsjlawtg6oa.js → 145gbwitjxamg.js} +1 -1
  62. package/dist/web/_next/static/chunks/{turbopack-0lxiiw.jhevml.js → turbopack-0kcwat8.leugk.js} +1 -1
  63. package/dist/web/_not-found/__next._full.txt +34 -30
  64. package/dist/web/_not-found/__next._head.txt +4 -4
  65. package/dist/web/_not-found/__next._index.txt +17 -13
  66. package/dist/web/_not-found/__next._not-found.__PAGE__.txt +2 -2
  67. package/dist/web/_not-found/__next._not-found.txt +3 -3
  68. package/dist/web/_not-found/__next._tree.txt +2 -2
  69. package/dist/web/_not-found.html +1 -1
  70. package/dist/web/_not-found.txt +34 -30
  71. package/dist/web/index.html +1 -1
  72. package/dist/web/index.txt +31 -26
  73. package/dist/web/login/__next._full.txt +35 -29
  74. package/dist/web/login/__next._head.txt +4 -4
  75. package/dist/web/login/__next._index.txt +17 -13
  76. package/dist/web/login/__next._tree.txt +2 -2
  77. package/dist/web/login/__next.login.__PAGE__.txt +4 -4
  78. package/dist/web/login/__next.login.txt +3 -3
  79. package/dist/web/login.html +1 -1
  80. package/dist/web/login.txt +35 -29
  81. package/dist/web/settings/__next._full.txt +36 -34
  82. package/dist/web/settings/__next._head.txt +4 -4
  83. package/dist/web/settings/__next._index.txt +17 -13
  84. package/dist/web/settings/__next._tree.txt +2 -2
  85. package/dist/web/settings/__next.settings.__PAGE__.txt +4 -4
  86. package/dist/web/settings/__next.settings.txt +5 -5
  87. package/dist/web/settings/agents/__next._full.txt +38 -35
  88. package/dist/web/settings/agents/__next._head.txt +4 -4
  89. package/dist/web/settings/agents/__next._index.txt +17 -13
  90. package/dist/web/settings/agents/__next._tree.txt +2 -2
  91. package/dist/web/settings/agents/__next.settings.agents.__PAGE__.txt +4 -4
  92. package/dist/web/settings/agents/__next.settings.agents.txt +3 -3
  93. package/dist/web/settings/agents/__next.settings.txt +5 -5
  94. package/dist/web/settings/agents.html +1 -1
  95. package/dist/web/settings/agents.txt +38 -35
  96. package/dist/web/settings/mcps/__next._full.txt +38 -35
  97. package/dist/web/settings/mcps/__next._head.txt +4 -4
  98. package/dist/web/settings/mcps/__next._index.txt +17 -13
  99. package/dist/web/settings/mcps/__next._tree.txt +2 -2
  100. package/dist/web/settings/mcps/__next.settings.mcps.__PAGE__.txt +4 -4
  101. package/dist/web/settings/mcps/__next.settings.mcps.txt +3 -3
  102. package/dist/web/settings/mcps/__next.settings.txt +5 -5
  103. package/dist/web/settings/mcps.html +1 -1
  104. package/dist/web/settings/mcps.txt +38 -35
  105. package/dist/web/settings/models/__next._full.txt +38 -35
  106. package/dist/web/settings/models/__next._head.txt +4 -4
  107. package/dist/web/settings/models/__next._index.txt +17 -13
  108. package/dist/web/settings/models/__next._tree.txt +2 -2
  109. package/dist/web/settings/models/__next.settings.models.__PAGE__.txt +4 -4
  110. package/dist/web/settings/models/__next.settings.models.txt +3 -3
  111. package/dist/web/settings/models/__next.settings.txt +5 -5
  112. package/dist/web/settings/models.html +1 -1
  113. package/dist/web/settings/models.txt +38 -35
  114. package/dist/web/settings/providers/__next._full.txt +38 -35
  115. package/dist/web/settings/providers/__next._head.txt +4 -4
  116. package/dist/web/settings/providers/__next._index.txt +17 -13
  117. package/dist/web/settings/providers/__next._tree.txt +2 -2
  118. package/dist/web/settings/providers/__next.settings.providers.__PAGE__.txt +4 -4
  119. package/dist/web/settings/providers/__next.settings.providers.txt +3 -3
  120. package/dist/web/settings/providers/__next.settings.txt +5 -5
  121. package/dist/web/settings/providers.html +1 -1
  122. package/dist/web/settings/providers.txt +38 -35
  123. package/dist/web/settings/skills/__next._full.txt +38 -35
  124. package/dist/web/settings/skills/__next._head.txt +4 -4
  125. package/dist/web/settings/skills/__next._index.txt +17 -13
  126. package/dist/web/settings/skills/__next._tree.txt +2 -2
  127. package/dist/web/settings/skills/__next.settings.skills.__PAGE__.txt +4 -4
  128. package/dist/web/settings/skills/__next.settings.skills.txt +3 -3
  129. package/dist/web/settings/skills/__next.settings.txt +5 -5
  130. package/dist/web/settings/skills.html +1 -1
  131. package/dist/web/settings/skills.txt +38 -35
  132. package/dist/web/settings.html +1 -1
  133. package/dist/web/settings.txt +36 -34
  134. package/dist/web/workflows/__next._full.txt +36 -31
  135. package/dist/web/workflows/__next._head.txt +4 -4
  136. package/dist/web/workflows/__next._index.txt +17 -13
  137. package/dist/web/workflows/__next._tree.txt +2 -2
  138. package/dist/web/workflows/__next.workflows.__PAGE__.txt +4 -4
  139. package/dist/web/workflows/__next.workflows.txt +3 -3
  140. package/dist/web/workflows.html +1 -1
  141. package/dist/web/workflows.txt +36 -31
  142. package/dist/web/workspace/_/__next._full.txt +39 -34
  143. package/dist/web/workspace/_/__next._head.txt +4 -4
  144. package/dist/web/workspace/_/__next._index.txt +17 -13
  145. package/dist/web/workspace/_/__next._tree.txt +2 -2
  146. package/dist/web/workspace/_/__next.workspace.$d$id.__PAGE__.txt +3 -3
  147. package/dist/web/workspace/_/__next.workspace.$d$id.txt +3 -3
  148. package/dist/web/workspace/_/__next.workspace.txt +3 -3
  149. package/dist/web/workspace/_.html +1 -1
  150. package/dist/web/workspace/_.txt +39 -34
  151. package/dist/web/workspaces/__next._full.txt +35 -30
  152. package/dist/web/workspaces/__next._head.txt +4 -4
  153. package/dist/web/workspaces/__next._index.txt +17 -13
  154. package/dist/web/workspaces/__next._tree.txt +2 -2
  155. package/dist/web/workspaces/__next.workspaces.__PAGE__.txt +4 -4
  156. package/dist/web/workspaces/__next.workspaces.txt +3 -3
  157. package/dist/web/workspaces.html +1 -1
  158. package/dist/web/workspaces.txt +35 -30
  159. package/dist/ws/agent-prompt.js +48 -18
  160. package/dist/ws/agent-runner.js +10 -3
  161. package/dist/ws/message-parts.js +8 -0
  162. package/dist/ws/terminal-handler.js +1 -1
  163. package/package.json +1 -1
  164. package/public/avatars/1778953228323-5je54x-user.jpg +0 -0
  165. package/public/avatars/1778954057880-g01q6y-user.jpg +0 -0
  166. package/dist/web/_next/static/chunks/002-dxhags4dj.js +0 -2
  167. package/dist/web/_next/static/chunks/00fu7sjtftd3f.js +0 -1
  168. package/dist/web/_next/static/chunks/00s_1ft2-qz0r.js +0 -1
  169. package/dist/web/_next/static/chunks/02wkg1tjv~_t2.js +0 -1
  170. package/dist/web/_next/static/chunks/03jy~o.gx._e-.js +0 -3
  171. package/dist/web/_next/static/chunks/07bs5ds68tg38.js +0 -2
  172. package/dist/web/_next/static/chunks/0b1w9_ne4c2ez.js +0 -1
  173. package/dist/web/_next/static/chunks/0bzechrx76-ix.js +0 -1
  174. package/dist/web/_next/static/chunks/0csqa76ud3h4d.js +0 -1
  175. package/dist/web/_next/static/chunks/0fxdvo63ywaa~.js +0 -1
  176. package/dist/web/_next/static/chunks/0k5bmi-es-2nj.js +0 -1
  177. package/dist/web/_next/static/chunks/0pep4mkvt3.rh.js +0 -31
  178. package/dist/web/_next/static/chunks/0tqwdxb-8t_n1.js +0 -1
  179. package/dist/web/_next/static/chunks/0y5ipuqgqfrob.js +0 -8
  180. package/dist/web/_next/static/chunks/0yc41k980zabo.js +0 -1
  181. package/dist/web/_next/static/chunks/0zhhkzv4.0koq.js +0 -183
  182. package/dist/web/_next/static/chunks/0zl19l5tuoppw.js +0 -1
  183. package/dist/web/_next/static/chunks/102lz3yanx8i~.css +0 -1
  184. package/dist/web/_next/static/chunks/13wm9.vry~.lh.js +0 -5
  185. /package/dist/web/_next/static/{XzP71HbFE1fuE_bLT-x7i → 9X6R42UNn3h4WL_zj_Yh4}/_buildManifest.js +0 -0
  186. /package/dist/web/_next/static/{XzP71HbFE1fuE_bLT-x7i → 9X6R42UNn3h4WL_zj_Yh4}/_clientMiddlewareManifest.js +0 -0
  187. /package/dist/web/_next/static/{XzP71HbFE1fuE_bLT-x7i → 9X6R42UNn3h4WL_zj_Yh4}/_ssgManifest.js +0 -0
@@ -9,6 +9,7 @@ import { listChannels, updateChannel } from './channel.js';
9
9
  import { ensureDir, getDataDir } from '../storage/json-store.js';
10
10
  import { extractUsageFromOutput } from '../storage/usage.js';
11
11
  const DEFAULT_AGENT_ROLE = 'agent';
12
+ export const AGENT_GENERATOR_PRESET_ID = 'agent-generator';
12
13
  const VALID_RUNTIME_KINDS = ['open-agent-sdk', 'claude-code', 'codex', 'langchain'];
13
14
  const VALID_TOOL_NAMES = new Set(BUILT_IN_AGENT_TOOLS.map((tool) => tool.name));
14
15
  const ANTHROPIC_BRIDGE_PROVIDERS = [
@@ -24,11 +25,15 @@ export function isValidRole(role) {
24
25
  export function listTemplates() {
25
26
  const root = getGlobalAgentTemplatesDir();
26
27
  if (!existsSync(root))
27
- return [];
28
- return readdirSync(root, { withFileTypes: true })
28
+ return [getDefaultAgentGeneratorPreset()];
29
+ const templates = readdirSync(root, { withFileTypes: true })
29
30
  .filter((entry) => entry.isDirectory())
30
31
  .map((entry) => readAgentTemplate(entry.name))
31
32
  .filter((template) => Boolean(template));
33
+ if (!templates.some((template) => template.id === AGENT_GENERATOR_PRESET_ID)) {
34
+ templates.unshift(getDefaultAgentGeneratorPreset());
35
+ }
36
+ return templates;
32
37
  }
33
38
  export async function testConnection(workspaceId, data) {
34
39
  if (workspaceId) {
@@ -509,21 +514,57 @@ function ensureWorkspaceAgentCopy(preset, agentspaceDir) {
509
514
  }
510
515
  export function readAgentTemplate(agentId) {
511
516
  const filePath = join(getGlobalAgentTemplateDir(agentId), 'agent.json');
512
- if (!existsSync(filePath))
513
- return null;
517
+ if (!existsSync(filePath)) {
518
+ return agentId === AGENT_GENERATOR_PRESET_ID ? getDefaultAgentGeneratorPreset() : null;
519
+ }
514
520
  try {
515
521
  return JSON.parse(readFileSync(filePath, 'utf-8'));
516
522
  }
517
523
  catch {
518
- return null;
524
+ return agentId === AGENT_GENERATOR_PRESET_ID ? getDefaultAgentGeneratorPreset() : null;
519
525
  }
520
526
  }
527
+ function getDefaultAgentGeneratorPreset() {
528
+ return {
529
+ id: AGENT_GENERATOR_PRESET_ID,
530
+ name: 'Agent Generator',
531
+ role: 'agent',
532
+ description: '根据提示词生成其他 Agent 的名称、描述和 Markdown 系统提示。',
533
+ runtimeKind: 'claude-code',
534
+ modelProvider: undefined,
535
+ modelId: '',
536
+ apiBase: '',
537
+ apiKey: '',
538
+ workingDir: '',
539
+ mcps: {},
540
+ skills: [],
541
+ tools: [],
542
+ systemPrompt: [
543
+ '# Role',
544
+ 'You are an Agent Spaces agent designer.',
545
+ '',
546
+ '# Responsibilities',
547
+ '- Convert user requirements into production-ready Agent preset metadata.',
548
+ '- Return only valid JSON with name, description, and systemPrompt.',
549
+ '- Write the systemPrompt in clear Markdown with role, responsibilities, workflow, constraints, and output expectations.',
550
+ '',
551
+ '# Constraints',
552
+ '- Do not include Markdown code fences around JSON.',
553
+ '- Keep names concise and descriptions practical.',
554
+ ].join('\n'),
555
+ temperature: 0.2,
556
+ maxTokens: 4096,
557
+ enabled: true,
558
+ };
559
+ }
521
560
  function sanitizeMarkdownFilename(name) {
522
561
  const raw = basename(name).replace(/\.md$/i, '');
523
562
  const safe = raw.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'skill';
524
563
  return `${safe}.md`;
525
564
  }
526
565
  export function deletePreset(workspaceId, presetId) {
566
+ if (presetId === AGENT_GENERATOR_PRESET_ID)
567
+ return false;
527
568
  const ws = getWorkspace(workspaceId);
528
569
  if (!ws)
529
570
  return null;
@@ -611,6 +652,8 @@ export function updateGlobalPreset(presetId, data) {
611
652
  return updated;
612
653
  }
613
654
  export function deleteGlobalPreset(presetId) {
655
+ if (presetId === AGENT_GENERATOR_PRESET_ID)
656
+ return false;
614
657
  const templateDir = getGlobalAgentTemplateDir(presetId);
615
658
  if (!existsSync(templateDir))
616
659
  return false;
@@ -6,6 +6,17 @@ import * as taskService from './task.js';
6
6
  import * as agentService from './agent.js';
7
7
  import * as commandService from './command.js';
8
8
  import * as commandProcessManager from './command-process-manager.js';
9
+ const issueStatuses = [
10
+ 'draft',
11
+ 'planned',
12
+ 'in_progress',
13
+ 'review_pending',
14
+ 'changes_requested',
15
+ 'approved',
16
+ 'completed',
17
+ 'archived',
18
+ 'error',
19
+ ];
9
20
  const currentChannelInputSchema = {
10
21
  type: 'object',
11
22
  properties: {
@@ -32,6 +43,11 @@ const createIssueInputSchema = {
32
43
  type: 'string',
33
44
  description: 'Issue description to create for the current channel.',
34
45
  },
46
+ status: {
47
+ type: 'string',
48
+ enum: issueStatuses,
49
+ description: 'Issue status to create. Use completed when creating a finished issue report. Defaults to draft.',
50
+ },
35
51
  },
36
52
  required: ['channelId', 'title'],
37
53
  additionalProperties: false,
@@ -51,6 +67,32 @@ const addCommentInputSchema = {
51
67
  required: ['channelId', 'content'],
52
68
  additionalProperties: false,
53
69
  };
70
+ const readTerminalOutputInputSchema = {
71
+ type: 'object',
72
+ properties: {
73
+ workspaceId: {
74
+ type: 'string',
75
+ description: 'Optional workspace ID. If omitted, the current workspace is used.',
76
+ },
77
+ sessionId: {
78
+ type: 'string',
79
+ description: 'Terminal session ID to read.',
80
+ },
81
+ offset: {
82
+ type: 'integer',
83
+ minimum: 0,
84
+ description: 'Number of newest lines to skip before reading. Defaults to 0.',
85
+ },
86
+ limit: {
87
+ type: 'integer',
88
+ minimum: 1,
89
+ maximum: 1000,
90
+ description: 'Maximum number of lines to read. Defaults to 100.',
91
+ },
92
+ },
93
+ required: ['sessionId'],
94
+ additionalProperties: false,
95
+ };
54
96
  export function createIssueFunctionTools(workspaceId, channel, actor, allowedTools) {
55
97
  if (!channel)
56
98
  return [];
@@ -94,9 +136,10 @@ function createCurrentChannelIssue(workspaceId, channel, input) {
94
136
  throw new Error(`Current channel already has a bound issue: ${currentChannel.issueId}`);
95
137
  const title = typeof data.title === 'string' ? data.title.trim() : '';
96
138
  const description = typeof data.description === 'string' ? data.description.trim() : '';
139
+ const status = parseIssueStatus(data.status);
97
140
  if (!title)
98
141
  throw new Error('title is required.');
99
- const issue = issueService.createForChannel(workspaceId, currentChannel.id, { title, description });
142
+ const issue = issueService.createForChannel(workspaceId, currentChannel.id, { title, description, status });
100
143
  if (!issue)
101
144
  throw new Error(`Current channel not found: ${currentChannel.id}`);
102
145
  channel.issueId = issue.id;
@@ -104,6 +147,14 @@ function createCurrentChannelIssue(workspaceId, channel, input) {
104
147
  channel.name = title;
105
148
  return issue;
106
149
  }
150
+ function parseIssueStatus(status) {
151
+ if (status === undefined)
152
+ return undefined;
153
+ if (typeof status !== 'string' || !issueStatuses.includes(status)) {
154
+ throw new Error(`status must be one of: ${issueStatuses.join(', ')}.`);
155
+ }
156
+ return status;
157
+ }
107
158
  function viewCurrentChannelIssue(workspaceId, channel, input) {
108
159
  assertCurrentChannelId(channel, input);
109
160
  const issue = getBoundIssue(workspaceId, channel);
@@ -185,6 +236,21 @@ function assertCurrentChannelId(channel, input) {
185
236
  }
186
237
  export function createCommandFunctionTools(workspaceId) {
187
238
  return [
239
+ {
240
+ name: 'ReadTerminalOutput',
241
+ description: 'Read paginated terminal output by terminal session ID. Defaults to the newest 100 lines.',
242
+ inputSchema: readTerminalOutputInputSchema,
243
+ annotations: { readOnly: true, openWorld: false },
244
+ execute: async (input) => {
245
+ const data = input;
246
+ if (data.workspaceId && data.workspaceId !== workspaceId)
247
+ throw new Error('workspaceId mismatch');
248
+ return commandProcessManager.readTerminalOutput(workspaceId, data.sessionId, {
249
+ offset: data.offset,
250
+ limit: data.limit,
251
+ });
252
+ },
253
+ },
188
254
  {
189
255
  name: 'ListQuickCommands',
190
256
  description: 'List all quick commands for the workspace with running status.',
@@ -53,6 +53,8 @@ export function updateChannel(workspaceId, channelId, data) {
53
53
  channels[idx].draft = data.draft;
54
54
  if (Object.hasOwn(data, 'todos'))
55
55
  channels[idx].todos = data.todos;
56
+ if (Object.hasOwn(data, 'archived'))
57
+ channels[idx].archived = data.archived;
56
58
  writeJsonFile(channelsPath(workspaceId), channels);
57
59
  return channels[idx];
58
60
  }
@@ -1,3 +1,6 @@
1
+ // packages/server/src/services/command-process-manager.ts
2
+ import { statSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
1
4
  import * as ptyService from './pty.js';
2
5
  import * as commandService from './command.js';
3
6
  import { getWorkspace } from '../storage/workspace-store.js';
@@ -15,7 +18,14 @@ export function runCommand(workspaceId, commandId) {
15
18
  if (!command)
16
19
  throw new Error('Command not found');
17
20
  const workspace = getWorkspace(workspaceId);
18
- const cwd = command.cwd || workspace?.boundDirs[0] || process.env.HOME || '/tmp';
21
+ const rawCwd = command.cwd || workspace?.boundDirs[0] || process.env.HOME || process.env.USERPROFILE || (process.platform === 'win32' ? process.env.SYSTEMROOT || 'C:\\' : '/tmp');
22
+ let cwd;
23
+ try {
24
+ cwd = statSync(rawCwd).isFile() ? dirname(rawCwd) : rawCwd;
25
+ }
26
+ catch {
27
+ cwd = rawCwd;
28
+ }
19
29
  const shell = command.shell;
20
30
  const env = command.env;
21
31
  console.log(`[command] runCommand: workspace=${workspaceId} command=${commandId} cwd=${cwd} shell=${shell} cmd=${command.command}`);
@@ -121,6 +131,9 @@ export function getCommandProcesses(workspaceId) {
121
131
  }
122
132
  return result;
123
133
  }
134
+ export function readTerminalOutput(workspaceId, sessionId, options) {
135
+ return ptyService.readSessionOutput(workspaceId, sessionId, options);
136
+ }
124
137
  export function cleanup(workspaceId) {
125
138
  for (const [commandId, cmdProcess] of processes) {
126
139
  if (cmdProcess.workspaceId === workspaceId) {
@@ -1,6 +1,7 @@
1
- import { readdir, stat, readFile, writeFile, mkdir, rm } from 'node:fs/promises';
2
- import { join, resolve, relative, isAbsolute } from 'node:path';
1
+ import { readdir, stat, readFile, writeFile, mkdir, rm, rename as fsRename } from 'node:fs/promises';
2
+ import { join, resolve, relative, isAbsolute, dirname } from 'node:path';
3
3
  import { getWorkspace } from '../storage/workspace-store.js';
4
+ import { createGitignoreFilter } from './gitignore.js';
4
5
  const IGNORED_DIRS = new Set(['.git', 'node_modules', '.next', '.DS_Store']);
5
6
  export function resolvePath(workspace, relPath) {
6
7
  const base = resolve(workspace.boundDirs[0]);
@@ -10,25 +11,33 @@ export function resolvePath(workspace, relPath) {
10
11
  return null;
11
12
  return abs;
12
13
  }
13
- export async function readTree(workspace, relPath = '') {
14
+ export async function readTree(workspace, relPath = '', depth = Infinity) {
14
15
  const dirPath = resolvePath(workspace, relPath);
15
16
  if (!dirPath)
16
17
  return [];
17
- const entries = await readdir(dirPath, { withFileTypes: true });
18
+ const baseDir = resolve(workspace.boundDirs[0]);
19
+ const ig = await createGitignoreFilter(baseDir);
20
+ try {
21
+ var entries = await readdir(dirPath, { withFileTypes: true });
22
+ }
23
+ catch {
24
+ return [];
25
+ }
18
26
  const nodes = [];
19
27
  for (const entry of entries) {
20
28
  if (IGNORED_DIRS.has(entry.name))
21
29
  continue;
22
30
  const fullPath = join(dirPath, entry.name);
23
31
  const entryRelPath = relPath ? `${relPath}/${entry.name}` : entry.name;
32
+ const isIgnored = ig.isIgnored(entryRelPath, entry.name, entry.isDirectory());
24
33
  const s = await stat(fullPath);
25
34
  if (entry.isDirectory()) {
26
- const children = await readTree(workspace, entryRelPath);
27
35
  nodes.push({
28
36
  name: entry.name,
29
37
  path: entryRelPath,
30
38
  type: 'directory',
31
- children,
39
+ ignored: isIgnored || undefined,
40
+ children: depth > 1 ? await readTree(workspace, entryRelPath, depth - 1) : undefined,
32
41
  });
33
42
  }
34
43
  else {
@@ -36,6 +45,7 @@ export async function readTree(workspace, relPath = '') {
36
45
  name: entry.name,
37
46
  path: entryRelPath,
38
47
  type: 'file',
48
+ ignored: isIgnored || undefined,
39
49
  size: s.size,
40
50
  modifiedAt: s.mtime.toISOString(),
41
51
  });
@@ -85,5 +95,75 @@ export async function deletePath(workspace, relPath) {
85
95
  return false;
86
96
  }
87
97
  }
98
+ export async function writeFileBinary(workspace, relPath, buffer) {
99
+ const abs = resolvePath(workspace, relPath);
100
+ if (!abs)
101
+ return false;
102
+ try {
103
+ await mkdir(join(abs, '..'), { recursive: true });
104
+ await writeFile(abs, buffer);
105
+ return true;
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
111
+ export async function importFromUrl(workspace, url, targetDir) {
112
+ try {
113
+ const res = await fetch(url);
114
+ if (!res.ok)
115
+ return null;
116
+ const buffer = Buffer.from(await res.arrayBuffer());
117
+ const urlPath = new URL(url).pathname;
118
+ const filename = decodeURIComponent(urlPath.split('/').pop() || 'downloaded-file');
119
+ const relPath = targetDir ? `${targetDir}/${filename}` : filename;
120
+ const ok = await writeFileBinary(workspace, relPath, buffer);
121
+ return ok ? relPath : null;
122
+ }
123
+ catch {
124
+ return null;
125
+ }
126
+ }
127
+ export async function importFromAbsPath(workspace, absPath, targetDir) {
128
+ try {
129
+ const content = await readFile(absPath);
130
+ const filename = absPath.split(/[/\\]/).pop() || 'imported-file';
131
+ const relPath = targetDir ? `${targetDir}/${filename}` : filename;
132
+ const ok = await writeFileBinary(workspace, relPath, content);
133
+ return ok ? relPath : null;
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ }
139
+ export async function renamePath(workspace, oldRelPath, newRelPath) {
140
+ const oldAbs = resolvePath(workspace, oldRelPath);
141
+ const newAbs = resolvePath(workspace, newRelPath);
142
+ if (!oldAbs || !newAbs)
143
+ return false;
144
+ try {
145
+ await mkdir(dirname(newAbs), { recursive: true });
146
+ await fsRename(oldAbs, newAbs);
147
+ return true;
148
+ }
149
+ catch {
150
+ return false;
151
+ }
152
+ }
153
+ export async function copyPath(workspace, srcRelPath, destRelPath) {
154
+ const srcAbs = resolvePath(workspace, srcRelPath);
155
+ const destAbs = resolvePath(workspace, destRelPath);
156
+ if (!srcAbs || !destAbs)
157
+ return false;
158
+ try {
159
+ const { cp } = await import('node:fs/promises');
160
+ await mkdir(dirname(destAbs), { recursive: true });
161
+ await cp(srcAbs, destAbs, { recursive: true });
162
+ return true;
163
+ }
164
+ catch {
165
+ return false;
166
+ }
167
+ }
88
168
  export { getWorkspace };
89
169
  //# sourceMappingURL=file.js.map
@@ -0,0 +1,65 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ const cache = new Map();
4
+ function matchPattern(pattern, name) {
5
+ if (pattern.startsWith('*.')) {
6
+ return name.endsWith(pattern.slice(1));
7
+ }
8
+ if (pattern.startsWith('*.') && pattern.includes('/')) {
9
+ return name.endsWith(pattern.split('/').pop());
10
+ }
11
+ if (pattern.includes('*')) {
12
+ const re = new RegExp('^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
13
+ return re.test(name);
14
+ }
15
+ return name === pattern;
16
+ }
17
+ export async function createGitignoreFilter(rootDir) {
18
+ const gitignorePath = join(rootDir, '.gitignore');
19
+ let mtime = 0;
20
+ try {
21
+ const { stat } = await import('node:fs/promises');
22
+ mtime = (await stat(gitignorePath)).mtimeMs;
23
+ }
24
+ catch {
25
+ // no .gitignore
26
+ }
27
+ const cached = cache.get(rootDir);
28
+ if (cached && cached.mtime === mtime)
29
+ return cached.filter;
30
+ let patterns = [];
31
+ try {
32
+ const content = await readFile(gitignorePath, 'utf-8');
33
+ patterns = content.split('\n')
34
+ .map(l => l.trim())
35
+ .filter(l => l && !l.startsWith('#'))
36
+ .map(raw => {
37
+ const negated = raw.startsWith('!');
38
+ let p = negated ? raw.slice(1) : raw;
39
+ const dirOnly = p.endsWith('/');
40
+ if (dirOnly)
41
+ p = p.slice(0, -1);
42
+ // extract the last segment for name matching
43
+ const namePart = p.includes('/') ? p.split('/').filter(Boolean).pop() : p;
44
+ return { raw: namePart, dirOnly, negated, fullPath: p };
45
+ });
46
+ }
47
+ catch {
48
+ // no .gitignore
49
+ }
50
+ const filter = {
51
+ isIgnored(relPath, name, isDir) {
52
+ for (const p of patterns) {
53
+ if (p.dirOnly && !isDir)
54
+ continue;
55
+ if (matchPattern(p.raw, name) || matchPattern(p.fullPath, relPath)) {
56
+ return !p.negated;
57
+ }
58
+ }
59
+ return false;
60
+ },
61
+ };
62
+ cache.set(rootDir, { mtime, filter });
63
+ return filter;
64
+ }
65
+ //# sourceMappingURL=gitignore.js.map
@@ -38,9 +38,10 @@ export async function retryIssue(workspaceId, issueId, ctx, options = {}) {
38
38
  ctx.broadcast('issue.updated', paused);
39
39
  return { issue: paused, retried: false, reason: 'issue retry limit reached' };
40
40
  }
41
+ const RETRYABLE_STATUSES = ['failed', 'running', 'reviewing', 'retrying', 'waiting_review'];
41
42
  const issueTasks = taskService.list(workspaceId, issueId);
42
43
  for (const task of issueTasks) {
43
- if (task.status !== 'failed')
44
+ if (!RETRYABLE_STATUSES.includes(task.status))
44
45
  continue;
45
46
  const reset = taskService.resetForRetry(workspaceId, task.id, { resetRetryCount: true });
46
47
  if (!reset)
@@ -4,7 +4,14 @@ import * as channelService from '../services/channel.js';
4
4
  import * as taskService from '../services/task.js';
5
5
  function ensureChannel(workspaceId, issue) {
6
6
  ensureRetryDefaults(workspaceId, issue);
7
- const channelMembers = issue.members || [];
7
+ const existingChannel = issue.channelId ? channelService.getChannel(workspaceId, issue.channelId) : undefined;
8
+ const issueMembers = issue.members || [];
9
+ const fallbackChannelMembers = existingChannel?.members ?? [];
10
+ const channelMembers = issueMembers.length > 0 ? issueMembers : fallbackChannelMembers;
11
+ if (issueMembers.length === 0 && fallbackChannelMembers.length > 0) {
12
+ issue.members = [...fallbackChannelMembers];
13
+ updateIssue(issue);
14
+ }
8
15
  if (issue.channelId) {
9
16
  channelService.updateChannel(workspaceId, issue.channelId, { type: 'issue', issueId: issue.id, members: channelMembers });
10
17
  return;
@@ -59,7 +66,7 @@ export function create(workspaceId, input) {
59
66
  channelId: channel.id,
60
67
  title: input.title,
61
68
  description: input.description,
62
- status: 'draft',
69
+ status: input.status ?? 'draft',
63
70
  tasks: [],
64
71
  members: input.members || [],
65
72
  workflowId: input.workflowId,
@@ -77,15 +84,16 @@ export function createForChannel(workspaceId, channelId, input) {
77
84
  return null;
78
85
  const now = new Date().toISOString();
79
86
  const issueId = uuid();
87
+ const issueMembers = input.members ?? channel.members ?? [];
80
88
  const issue = {
81
89
  id: issueId,
82
90
  workspaceId,
83
91
  channelId,
84
92
  title: input.title,
85
93
  description: input.description,
86
- status: 'draft',
94
+ status: input.status ?? 'draft',
87
95
  tasks: [],
88
- members: [],
96
+ members: [...issueMembers],
89
97
  retryCount: 0,
90
98
  maxRetries: 3,
91
99
  createdAt: now,
@@ -96,6 +104,7 @@ export function createForChannel(workspaceId, channelId, input) {
96
104
  name: input.title,
97
105
  type: 'issue',
98
106
  issueId,
107
+ members: issueMembers,
99
108
  });
100
109
  return issue;
101
110
  }
@@ -4,6 +4,7 @@ import { createAgentRuntime } from '../../adapters/agent-runtime.js';
4
4
  import { getThinkingRuntimeConfig } from '../llm-model-config.js';
5
5
  import { runIssueAutomation } from '../../agents/issue-agent-runner.js';
6
6
  import { publishWorkspaceEvent } from './events.js';
7
+ import { prependPersistentAgentContext } from '../persistent-agent-context.js';
7
8
  export function getConfiguredBotAgent(workspaceId) {
8
9
  const workspace = workspaceService.getById(workspaceId);
9
10
  const botAgentId = workspace?.notificationSettings?.botAgentId;
@@ -25,8 +26,15 @@ export async function runBotAgent(workspaceId, preset, message) {
25
26
  ...getThinkingRuntimeConfig(preset),
26
27
  });
27
28
  const workingDir = agentService.resolveWorkingDir(workspaceId, preset);
29
+ const workspace = workspaceService.getById(workspaceId);
28
30
  try {
29
- const result = await runtime.execute(buildBotPrompt(message), workingDir, {
31
+ const result = await runtime.execute(prependPersistentAgentContext(buildBotPrompt(message), {
32
+ workspaceId,
33
+ workingDir,
34
+ boundDirs: workspace?.boundDirs,
35
+ includeWorkspacePrompt: false,
36
+ excludeNativeClaudeMd: preset.runtimeKind === 'claude-code',
37
+ }), workingDir, {
30
38
  maxTurns: 20,
31
39
  mcpServers: agentService.getMcpServers(preset.mcps),
32
40
  skills: agentService.getAvailableSkillNames(agentService.getAgentConfigDir(workspaceId, preset), preset.skills),