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.
- package/dist/backend/backend/src/constants.d.ts +7 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +7 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/pr-review/pr-review.controller.d.ts +2 -0
- package/dist/backend/backend/src/controllers/pr-review/pr-review.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/pr-review/pr-review.controller.js +24 -7
- package/dist/backend/backend/src/controllers/pr-review/pr-review.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/project/git.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/project/git.controller.js +10 -0
- package/dist/backend/backend/src/controllers/project/git.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/project/project.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/project/project.controller.js +4 -3
- package/dist/backend/backend/src/controllers/project/project.controller.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +8 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/git-integration.service.d.ts +15 -2
- package/dist/backend/backend/src/services/agent/git-integration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/git-integration.service.js +33 -12
- package/dist/backend/backend/src/services/agent/git-integration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +2 -2
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/backend/backend/src/services/pr-review/pr-review.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/pr-review/pr-review.service.js +8 -4
- package/dist/backend/backend/src/services/pr-review/pr-review.service.js.map +1 -1
- package/dist/backend/backend/src/services/pr-review/pr-review.validation.d.ts +31 -0
- package/dist/backend/backend/src/services/pr-review/pr-review.validation.d.ts.map +1 -0
- package/dist/backend/backend/src/services/pr-review/pr-review.validation.js +42 -0
- package/dist/backend/backend/src/services/pr-review/pr-review.validation.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +5 -4
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/utils/git-ref.utils.d.ts +31 -0
- package/dist/backend/backend/src/utils/git-ref.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/git-ref.utils.js +54 -0
- package/dist/backend/backend/src/utils/git-ref.utils.js.map +1 -0
- package/dist/backend/backend/src/utils/runtime-model-flags.utils.d.ts +7 -0
- package/dist/backend/backend/src/utils/runtime-model-flags.utils.d.ts.map +1 -1
- package/dist/backend/backend/src/utils/runtime-model-flags.utils.js +15 -0
- package/dist/backend/backend/src/utils/runtime-model-flags.utils.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +7 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +7 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js +8 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +2 -2
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +5 -4
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/cli/backend/src/utils/runtime-model-flags.utils.d.ts +7 -0
- package/dist/cli/backend/src/utils/runtime-model-flags.utils.d.ts.map +1 -1
- package/dist/cli/backend/src/utils/runtime-model-flags.utils.js +15 -0
- package/dist/cli/backend/src/utils/runtime-model-flags.utils.js.map +1 -1
- package/package.json +1 -1
- package/packages/crewly-agent/src/runtime/agent-runner.service.test.ts +17 -1
- package/packages/crewly-agent/src/runtime/auditor-tools.test.ts +70 -1
- package/packages/crewly-agent/src/runtime/auditor-tools.ts +10 -6
- package/packages/crewly-agent/src/runtime/tool-registry.test.ts +7 -1
- package/packages/crewly-agent/src/runtime/tool-registry.ts +2 -0
- package/packages/crewly-agent/src/runtime/types.test.ts +4 -0
- 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.
|
|
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
|
-
|
|
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 =
|
|
237
|
-
|
|
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
|
|
259
|
-
const result =
|
|
260
|
-
|
|
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
|
-
|
|
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');
|