crewly 1.20.471 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/backend/backend/src/constants.d.ts +7 -0
  2. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  3. package/dist/backend/backend/src/constants.js +7 -0
  4. package/dist/backend/backend/src/constants.js.map +1 -1
  5. package/dist/backend/backend/src/controllers/pr-review/pr-review.controller.d.ts +2 -0
  6. package/dist/backend/backend/src/controllers/pr-review/pr-review.controller.d.ts.map +1 -1
  7. package/dist/backend/backend/src/controllers/pr-review/pr-review.controller.js +24 -7
  8. package/dist/backend/backend/src/controllers/pr-review/pr-review.controller.js.map +1 -1
  9. package/dist/backend/backend/src/controllers/project/git.controller.d.ts.map +1 -1
  10. package/dist/backend/backend/src/controllers/project/git.controller.js +10 -0
  11. package/dist/backend/backend/src/controllers/project/git.controller.js.map +1 -1
  12. package/dist/backend/backend/src/controllers/project/project.controller.d.ts.map +1 -1
  13. package/dist/backend/backend/src/controllers/project/project.controller.js +4 -3
  14. package/dist/backend/backend/src/controllers/project/project.controller.js.map +1 -1
  15. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  16. package/dist/backend/backend/src/services/agent/agent-registration.service.js +8 -1
  17. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  18. package/dist/backend/backend/src/services/agent/git-integration.service.d.ts +15 -2
  19. package/dist/backend/backend/src/services/agent/git-integration.service.d.ts.map +1 -1
  20. package/dist/backend/backend/src/services/agent/git-integration.service.js +33 -12
  21. package/dist/backend/backend/src/services/agent/git-integration.service.js.map +1 -1
  22. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +2 -2
  23. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
  24. package/dist/backend/backend/src/services/pr-review/pr-review.service.d.ts.map +1 -1
  25. package/dist/backend/backend/src/services/pr-review/pr-review.service.js +8 -4
  26. package/dist/backend/backend/src/services/pr-review/pr-review.service.js.map +1 -1
  27. package/dist/backend/backend/src/services/pr-review/pr-review.validation.d.ts +31 -0
  28. package/dist/backend/backend/src/services/pr-review/pr-review.validation.d.ts.map +1 -0
  29. package/dist/backend/backend/src/services/pr-review/pr-review.validation.js +42 -0
  30. package/dist/backend/backend/src/services/pr-review/pr-review.validation.js.map +1 -0
  31. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  32. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +5 -4
  33. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  34. package/dist/backend/backend/src/utils/git-ref.utils.d.ts +31 -0
  35. package/dist/backend/backend/src/utils/git-ref.utils.d.ts.map +1 -0
  36. package/dist/backend/backend/src/utils/git-ref.utils.js +54 -0
  37. package/dist/backend/backend/src/utils/git-ref.utils.js.map +1 -0
  38. package/dist/backend/backend/src/utils/runtime-model-flags.utils.d.ts +7 -0
  39. package/dist/backend/backend/src/utils/runtime-model-flags.utils.d.ts.map +1 -1
  40. package/dist/backend/backend/src/utils/runtime-model-flags.utils.js +15 -0
  41. package/dist/backend/backend/src/utils/runtime-model-flags.utils.js.map +1 -1
  42. package/dist/backend/build-info.json +2 -2
  43. package/dist/cli/backend/src/constants.d.ts +7 -0
  44. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  45. package/dist/cli/backend/src/constants.js +7 -0
  46. package/dist/cli/backend/src/constants.js.map +1 -1
  47. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  48. package/dist/cli/backend/src/services/agent/agent-registration.service.js +8 -1
  49. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
  50. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +2 -2
  51. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
  52. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  53. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +5 -4
  54. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  55. package/dist/cli/backend/src/utils/runtime-model-flags.utils.d.ts +7 -0
  56. package/dist/cli/backend/src/utils/runtime-model-flags.utils.d.ts.map +1 -1
  57. package/dist/cli/backend/src/utils/runtime-model-flags.utils.js +15 -0
  58. package/dist/cli/backend/src/utils/runtime-model-flags.utils.js.map +1 -1
  59. package/package.json +1 -1
  60. package/packages/crewly-agent/src/runtime/agent-runner.service.test.ts +17 -1
  61. package/packages/crewly-agent/src/runtime/auditor-tools.test.ts +70 -1
  62. package/packages/crewly-agent/src/runtime/auditor-tools.ts +10 -6
  63. package/packages/crewly-agent/src/runtime/tool-registry.test.ts +7 -1
  64. package/packages/crewly-agent/src/runtime/tool-registry.ts +2 -0
  65. package/packages/crewly-agent/src/runtime/types.test.ts +4 -0
  66. package/packages/crewly-agent/src/runtime/types.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crewly",
3
- "version": "1.20.471",
3
+ "version": "1.21.0",
4
4
  "type": "module",
5
5
  "description": "Multi-agent orchestration platform for AI coding teams — coordinates Claude Code, Gemini CLI, and Codex agents with a real-time web dashboard",
6
6
  "workspaces": [
@@ -2,7 +2,8 @@ import { describe, it, expect, beforeEach, afterEach, vi, type Mocked, type Mock
2
2
  import { AgentRunnerService, AgentRunTimeoutError, ToolCallLoopDetector } from './agent-runner.service.js';
3
3
  import { ModelManager } from './model-manager.js';
4
4
  import { CrewlyApiClient } from './api-client.js';
5
- import { CREWLY_AGENT_DEFAULTS, type CrewlyAgentConfig, type SecurityPolicy, type AuditEntry } from './types.js';
5
+ import { CREWLY_AGENT_DEFAULTS, type CrewlyAgentConfig, type SecurityPolicy, type AuditEntry, type ToolSensitivity } from './types.js';
6
+ import { TOOL_SENSITIVITY } from './tool-registry.js';
6
7
 
7
8
  describe('AgentRunnerService', () => {
8
9
  let runner: AgentRunnerService;
@@ -1255,6 +1256,21 @@ describe('AgentRunnerService', () => {
1255
1256
  expect(mockApiClient.post).not.toHaveBeenCalled();
1256
1257
  });
1257
1258
 
1259
+ it('should block create_github_issue in readOnlyMode and ask for approval when sensitive tools need it', () => {
1260
+ const check = (name: string) =>
1261
+ (runner as unknown as { checkApproval: (n: string, s: ToolSensitivity) => { allowed: boolean; blocked?: boolean } })
1262
+ .checkApproval(name, TOOL_SENSITIVITY[name]);
1263
+
1264
+ runner.updateSecurityPolicy({ readOnlyMode: true });
1265
+ expect(check('create_github_issue')).toMatchObject({ allowed: false, blocked: true });
1266
+
1267
+ runner.updateSecurityPolicy({ readOnlyMode: false, requireApproval: ['sensitive'] });
1268
+ expect(check('create_github_issue')).toMatchObject({ allowed: false, blocked: false });
1269
+
1270
+ runner.updateSecurityPolicy({ readOnlyMode: false, requireApproval: [] });
1271
+ expect(check('create_github_issue')).toEqual({ allowed: true });
1272
+ });
1273
+
1258
1274
  it('should allow safe/read-only tools when readOnlyMode is active', async () => {
1259
1275
  runner.updateSecurityPolicy({ readOnlyMode: true });
1260
1276
 
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi, type Mocked, type MockInstance } from 'vitest';
2
- import { promises as fs } from 'fs';
2
+ import { promises as fs, mkdtempSync, writeFileSync, readFileSync, existsSync, chmodSync, rmSync } from 'fs';
3
+ import * as os from 'os';
3
4
  import * as path from 'path';
4
5
  import { createAuditorTools, getAuditorToolNames } from './auditor-tools.js';
5
6
  import { CrewlyApiClient } from './api-client.js';
@@ -271,4 +272,72 @@ describe('Auditor Tools', () => {
271
272
  expect(result.recentFindings).toContain('No audit history');
272
273
  });
273
274
  });
275
+ describe('create_github_issue (CREW-967: no shell)', () => {
276
+ let binDir: string;
277
+ let logFile: string;
278
+ let originalPath: string | undefined;
279
+
280
+ /** Splits the fake gh's NUL-separated log into one argv array per call. */
281
+ function readCalls(): string[][] {
282
+ const parts = readFileSync(logFile, 'utf8').split('\0');
283
+ const calls: string[][] = [];
284
+ for (const part of parts) {
285
+ if (part === 'CALL') calls.push([]);
286
+ else if (part !== '' && calls.length > 0) calls[calls.length - 1].push(part);
287
+ }
288
+ return calls;
289
+ }
290
+
291
+ beforeEach(() => {
292
+ binDir = mkdtempSync(path.join(os.tmpdir(), 'fake-gh-'));
293
+ logFile = path.join(binDir, 'argv.log');
294
+ writeFileSync(logFile, '');
295
+ const script = [
296
+ '#!/bin/bash',
297
+ 'printf "CALL\\0" >> "$GH_ARGV_LOG"',
298
+ 'printf "%s\\0" "$@" >> "$GH_ARGV_LOG"',
299
+ 'if [ "$2" = "list" ]; then echo "[]"; else echo "https://github.com/o/r/issues/1"; fi',
300
+ ].join('\n');
301
+ writeFileSync(path.join(binDir, 'gh'), script);
302
+ chmodSync(path.join(binDir, 'gh'), 0o755);
303
+ originalPath = process.env.PATH;
304
+ process.env.PATH = `${binDir}:${originalPath}`;
305
+ process.env.GH_ARGV_LOG = logFile;
306
+ });
307
+
308
+ afterEach(() => {
309
+ process.env.PATH = originalPath;
310
+ delete process.env.GH_ARGV_LOG;
311
+ rmSync(binDir, { recursive: true, force: true });
312
+ });
313
+
314
+ it('does not evaluate $(...) or backticks and passes each value as one argv entry', async () => {
315
+ const marker1 = path.join(binDir, 'pwned-subshell');
316
+ const marker2 = path.join(binDir, 'pwned-backtick');
317
+ const title = `[High] x $(touch ${marker1}) \`touch ${marker2}\` "q"`;
318
+ const body = `body $(touch ${marker1}) \`touch ${marker2}\``;
319
+ const label = `bug$(touch ${marker1})`;
320
+
321
+ const result = await tools.create_github_issue.execute({ title, body, labels: [label, 'enhancement'] }) as { success: boolean; created: boolean };
322
+
323
+ expect(result.success).toBe(true);
324
+ expect(result.created).toBe(true);
325
+ expect(existsSync(marker1)).toBe(false);
326
+ expect(existsSync(marker2)).toBe(false);
327
+
328
+ const calls = readCalls();
329
+ expect(calls).toHaveLength(2); // dedup search + create
330
+ const search = calls[0];
331
+ expect(search.slice(0, 4)).toEqual(['issue', 'list', '--state', 'open']);
332
+ expect(search[search.indexOf('--search') + 1]).toContain('$(touch');
333
+
334
+ const create = calls[1];
335
+ expect(create[0]).toBe('issue');
336
+ expect(create[1]).toBe('create');
337
+ expect(create[create.indexOf('--title') + 1]).toBe(title);
338
+ expect(create[create.indexOf('--body') + 1]).toBe(body);
339
+ const labelValues = create.flatMap((a, i) => (a === '--label' ? [create[i + 1]] : []));
340
+ expect(labelValues).toEqual([label, 'enhancement']);
341
+ });
342
+ });
274
343
  });
@@ -226,15 +226,18 @@ export function createAuditorTools(
226
226
  }),
227
227
  execute: async (args) => {
228
228
  const { title, body, labels } = args as { title: string; body: string; labels: string[] };
229
- const { execSync } = await import('child_process');
229
+ // No shell: every value below reaches gh as one literal argv entry, so
230
+ // $(...) and backticks in model-supplied text are never evaluated (CREW-967).
231
+ const { execFileSync } = await import('child_process');
230
232
 
231
233
  try {
232
234
  // Dedup: search for existing open issues with similar title
233
235
  const searchQuery = title.replace(/\[.*?\]\s*/, '').slice(0, 50);
234
236
  let existingIssues = '';
235
237
  try {
236
- existingIssues = execSync(
237
- `gh issue list --state open --search "${searchQuery.replace(/"/g, '\\"')}" --json number,title --limit 5`,
238
+ existingIssues = execFileSync(
239
+ 'gh',
240
+ ['issue', 'list', '--state', 'open', '--search', searchQuery, '--json', 'number,title', '--limit', '5'],
238
241
  { encoding: 'utf-8', timeout: 15000 }
239
242
  );
240
243
  } catch { /* gh not available or no repo */ }
@@ -255,9 +258,10 @@ export function createAuditorTools(
255
258
  }
256
259
 
257
260
  // Create the issue
258
- const labelFlag = labels.map(l => `--label "${l}"`).join(' ');
259
- const result = execSync(
260
- `gh issue create --title "${title.replace(/"/g, '\\"')}" --body "${body.replace(/"/g, '\\"')}" ${labelFlag}`,
261
+ const labelArgs = labels.flatMap(l => ['--label', l]);
262
+ const result = execFileSync(
263
+ 'gh',
264
+ ['issue', 'create', '--title', title, '--body', body, ...labelArgs],
261
265
  { encoding: 'utf-8', timeout: 30000 }
262
266
  );
263
267
 
@@ -4,6 +4,7 @@ import { createTools, getToolNames, TOOL_SENSITIVITY, stripNotifyMarkers, conver
4
4
  import { CrewlyApiClient } from './api-client.js';
5
5
  import type { AuditEntry, ToolCallbacks, CompactionResult, AuditLogFilters } from './types.js';
6
6
  import { WRITE_TOOLS } from './types.js';
7
+ import { getAuditorToolNames } from './auditor-tools.js';
7
8
 
8
9
  describe('Tool Registry', () => {
9
10
  let mockClient: Mocked<CrewlyApiClient>;
@@ -1574,6 +1575,10 @@ describe('Tool Registry', () => {
1574
1575
  });
1575
1576
 
1576
1577
  describe('TOOL_SENSITIVITY', () => {
1578
+ it('should classify create_github_issue as sensitive', () => {
1579
+ expect(TOOL_SENSITIVITY.create_github_issue).toBe('sensitive');
1580
+ });
1581
+
1577
1582
  it('should classify read-only tools as safe', () => {
1578
1583
  expect(TOOL_SENSITIVITY.get_agent_status).toBe('safe');
1579
1584
  expect(TOOL_SENSITIVITY.get_team_status).toBe('safe');
@@ -2025,7 +2030,8 @@ describe('Tool Registry', () => {
2025
2030
  });
2026
2031
 
2027
2032
  it('should only contain valid tool names from the registry', () => {
2028
- const validNames = getToolNames();
2033
+ // The auditor registry owns create_github_issue, so both name lists count.
2034
+ const validNames = [...getToolNames(), ...getAuditorToolNames()];
2029
2035
  for (const writeTool of WRITE_TOOLS) {
2030
2036
  expect(validNames).toContain(writeTool);
2031
2037
  }
@@ -596,6 +596,8 @@ export const TOOL_SENSITIVITY: Record<string, ToolSensitivity> = {
596
596
  remember: 'sensitive',
597
597
  complete_task: 'sensitive',
598
598
  broadcast: 'sensitive',
599
+ // Files an issue on GitHub as the owner's gh login (CREW-967).
600
+ create_github_issue: 'sensitive',
599
601
  // Destructive: irreversible or high-impact operations
600
602
  start_agent: 'destructive',
601
603
  stop_agent: 'destructive',
@@ -341,6 +341,10 @@ describe('Crewly Agent Types', () => {
341
341
  expect(WRITE_TOOLS).toContain('handle_agent_failure');
342
342
  });
343
343
 
344
+ it('should contain create_github_issue (files an issue as the owner)', () => {
345
+ expect(WRITE_TOOLS).toContain('create_github_issue');
346
+ });
347
+
344
348
  it('should not contain read-only tools', () => {
345
349
  expect(WRITE_TOOLS).not.toContain('read_file');
346
350
  expect(WRITE_TOOLS).not.toContain('get_team_status');
@@ -392,6 +392,7 @@ export const WRITE_TOOLS: readonly string[] = [
392
392
  'report_status',
393
393
  'remember',
394
394
  'complete_task',
395
+ 'create_github_issue',
395
396
  ] as const;
396
397
 
397
398
  /**