create-walle 0.9.11 → 0.9.13

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 (167) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -2
  3. package/template/bin/dev.sh +7 -1
  4. package/template/bin/setup.js +53 -9
  5. package/template/bin/sync-images.js +53 -0
  6. package/template/builder-journal.md +17 -0
  7. package/template/claude-task-manager/api-prompts.js +98 -13
  8. package/template/claude-task-manager/api-reviews.js +82 -5
  9. package/template/claude-task-manager/db.js +32 -5
  10. package/template/claude-task-manager/docs/session-capture-foundation-design.md +1273 -0
  11. package/template/claude-task-manager/lib/claude-desktop-sessions.js +696 -0
  12. package/template/claude-task-manager/lib/coding-agent-models.js +49 -1
  13. package/template/claude-task-manager/lib/session-capture.js +421 -0
  14. package/template/claude-task-manager/lib/session-history.js +135 -15
  15. package/template/claude-task-manager/lib/session-jobs.js +10 -5
  16. package/template/claude-task-manager/lib/session-stream.js +87 -19
  17. package/template/claude-task-manager/lib/setup-provider-config.js +115 -0
  18. package/template/claude-task-manager/lib/walle-ctm-history.js +72 -0
  19. package/template/claude-task-manager/lib/walle-session-context.js +61 -0
  20. package/template/claude-task-manager/lib/walle-transcript.js +176 -0
  21. package/template/claude-task-manager/public/css/setup.css +35 -8
  22. package/template/claude-task-manager/public/css/walle-session.css +56 -0
  23. package/template/claude-task-manager/public/css/walle.css +120 -0
  24. package/template/claude-task-manager/public/index.html +814 -181
  25. package/template/claude-task-manager/public/js/message-renderer.js +148 -19
  26. package/template/claude-task-manager/public/js/reviews.js +120 -62
  27. package/template/claude-task-manager/public/js/setup.js +75 -31
  28. package/template/claude-task-manager/public/js/stream-view.js +115 -55
  29. package/template/claude-task-manager/public/js/walle-session.js +84 -2
  30. package/template/claude-task-manager/public/js/walle.js +308 -54
  31. package/template/claude-task-manager/server.js +1092 -146
  32. package/template/claude-task-manager/session-integrity.js +181 -54
  33. package/template/claude-task-manager/session-utils.js +123 -41
  34. package/template/claude-task-manager/workers/state-detectors/codex.js +5 -2
  35. package/template/package.json +1 -1
  36. package/template/wall-e/adapters/ctm.js +39 -18
  37. package/template/wall-e/agent-runners/contract.js +17 -0
  38. package/template/wall-e/agent-runners/index.js +22 -0
  39. package/template/wall-e/agent-runtime/harness.js +212 -0
  40. package/template/wall-e/agent-runtime/index.js +8 -0
  41. package/template/wall-e/agent-runtime/registry.js +67 -0
  42. package/template/wall-e/agent-runtime/session-store.js +179 -0
  43. package/template/wall-e/agent-runtime/spawn.js +208 -0
  44. package/template/wall-e/api-walle.js +174 -7
  45. package/template/wall-e/brain.js +266 -28
  46. package/template/wall-e/channels/policy.js +88 -0
  47. package/template/wall-e/channels/registry.js +15 -1
  48. package/template/wall-e/channels/reply-dispatcher.js +70 -0
  49. package/template/wall-e/channels/session-bindings.js +51 -0
  50. package/template/wall-e/chat/code-review-context.js +29 -0
  51. package/template/wall-e/chat.js +188 -42
  52. package/template/wall-e/coding/acp-adapter.js +188 -0
  53. package/template/wall-e/coding/agent-catalog.js +129 -0
  54. package/template/wall-e/coding/compaction-service.js +247 -0
  55. package/template/wall-e/coding/execution-trace.js +3 -0
  56. package/template/wall-e/coding/instruction-service.js +224 -0
  57. package/template/wall-e/coding/model-message.js +67 -0
  58. package/template/wall-e/coding/permission-rules-store.js +111 -0
  59. package/template/wall-e/coding/permission-service.js +266 -0
  60. package/template/wall-e/coding/prompt-bundle.js +67 -0
  61. package/template/wall-e/coding/prompt-runtime.js +243 -0
  62. package/template/wall-e/coding/provider-transform.js +188 -0
  63. package/template/wall-e/coding/runtime-mode.js +132 -0
  64. package/template/wall-e/coding/snapshot-service.js +155 -0
  65. package/template/wall-e/coding/stream-processor.js +268 -0
  66. package/template/wall-e/coding/task-tool.js +255 -0
  67. package/template/wall-e/coding/tool-registry.js +361 -0
  68. package/template/wall-e/coding/transcript-writer.js +143 -0
  69. package/template/wall-e/coding/workspace-replay.js +324 -0
  70. package/template/wall-e/coding-context.js +4 -22
  71. package/template/wall-e/coding-orchestrator.js +307 -18
  72. package/template/wall-e/coding-prompts.js +44 -3
  73. package/template/wall-e/context/context-builder.js +43 -1
  74. package/template/wall-e/context/topic-matcher.js +1 -1
  75. package/template/wall-e/eval/agent-runner.js +59 -13
  76. package/template/wall-e/eval/benchmarks/memory-retrieval.json +155 -57
  77. package/template/wall-e/eval/benchmarks.js +100 -16
  78. package/template/wall-e/eval/eval-orchestrator.js +218 -8
  79. package/template/wall-e/eval/harvester.js +62 -5
  80. package/template/wall-e/eval/head-to-head.js +23 -2
  81. package/template/wall-e/eval/humaneval-adapter.js +30 -5
  82. package/template/wall-e/eval/livecodebench-adapter.js +29 -5
  83. package/template/wall-e/eval/manifest.js +186 -0
  84. package/template/wall-e/eval/run-agent-benchmarks.js +66 -2
  85. package/template/wall-e/eval/session-retrieval-benchmark.js +150 -0
  86. package/template/wall-e/eval/session-transcripts.js +57 -4
  87. package/template/wall-e/eval/swebench-adapter.js +109 -3
  88. package/template/wall-e/evaluation/agent-router.js +53 -1
  89. package/template/wall-e/evaluation/coding-quorum.js +48 -1
  90. package/template/wall-e/evaluation/router.js +4 -2
  91. package/template/wall-e/evaluation/tier-selector.js +11 -1
  92. package/template/wall-e/extraction/contradiction.js +2 -2
  93. package/template/wall-e/extraction/indexer.js +2 -1
  94. package/template/wall-e/extraction/knowledge-extractor.js +2 -2
  95. package/template/wall-e/hooks/cli.js +92 -0
  96. package/template/wall-e/hooks/discovery.js +119 -0
  97. package/template/wall-e/hooks/index.js +7 -0
  98. package/template/wall-e/hooks/manifest.js +55 -0
  99. package/template/wall-e/hooks/runtime.js +84 -0
  100. package/template/wall-e/hooks/session-memory.js +225 -0
  101. package/template/wall-e/http/auth.js +6 -2
  102. package/template/wall-e/http/chat-api.js +54 -8
  103. package/template/wall-e/integrations/claude-plugin/hooks/hooks.json +27 -0
  104. package/template/wall-e/integrations/claude-plugin/hooks/walle-precompact-hook.sh +5 -0
  105. package/template/wall-e/integrations/claude-plugin/hooks/walle-stop-hook.sh +5 -0
  106. package/template/wall-e/integrations/codex-plugin/hooks/walle-hook.sh +7 -0
  107. package/template/wall-e/integrations/codex-plugin/hooks.json +37 -0
  108. package/template/wall-e/listening/calendar.js +3 -1
  109. package/template/wall-e/llm/client.js +64 -10
  110. package/template/wall-e/llm/google.js +39 -5
  111. package/template/wall-e/llm/ollama.js +1 -1
  112. package/template/wall-e/llm/ollama.plugin.json +1 -1
  113. package/template/wall-e/llm/provider-availability.js +10 -0
  114. package/template/wall-e/llm/provider-error.js +269 -0
  115. package/template/wall-e/llm/tool-adapter.js +48 -12
  116. package/template/wall-e/loops/boot.js +2 -1
  117. package/template/wall-e/loops/initiative.js +2 -2
  118. package/template/wall-e/loops/tasks.js +8 -47
  119. package/template/wall-e/loops/workspace-prompts.js +20 -0
  120. package/template/wall-e/mcp-server.js +442 -1
  121. package/template/wall-e/memory/session-ingest-service.js +159 -0
  122. package/template/wall-e/memory/source-indexer.js +289 -0
  123. package/template/wall-e/plugins/discovery.js +83 -0
  124. package/template/wall-e/plugins/manifest-loader.js +50 -10
  125. package/template/wall-e/plugins/manifest-schema.js +69 -0
  126. package/template/wall-e/plugins/model-catalog.js +55 -0
  127. package/template/wall-e/prompts/coding/base.txt +2 -0
  128. package/template/wall-e/prompts/coding/deepseek.txt +1 -0
  129. package/template/wall-e/prompts/coding/memory-protocol.md +9 -0
  130. package/template/wall-e/prompts/coding/plan.txt +1 -0
  131. package/template/wall-e/runtime/execution-trace.js +220 -0
  132. package/template/wall-e/security/audit.js +266 -0
  133. package/template/wall-e/security/ssrf.js +236 -0
  134. package/template/wall-e/session-files.js +303 -0
  135. package/template/wall-e/skills/_bundled/slack-backfill/SKILL.md +3 -0
  136. package/template/wall-e/skills/_bundled/slack-sync/SKILL.md +3 -0
  137. package/template/wall-e/skills/internal-skill-registry.js +2 -2
  138. package/template/wall-e/skills/script-skill-runner.js +143 -0
  139. package/template/wall-e/skills/skill-executor.js +5 -6
  140. package/template/wall-e/skills/skill-fallback.js +3 -1
  141. package/template/wall-e/skills/skill-harness-registry.js +7 -8
  142. package/template/wall-e/skills/skill-planner.js +52 -4
  143. package/template/wall-e/skills/slack-ingest.js +11 -3
  144. package/template/wall-e/sources/base.js +90 -0
  145. package/template/wall-e/sources/builtin.js +33 -0
  146. package/template/wall-e/sources/claude-code-jsonl.js +78 -0
  147. package/template/wall-e/sources/codex-jsonl.js +125 -0
  148. package/template/wall-e/sources/coding-session-utils.js +117 -0
  149. package/template/wall-e/sources/contract-suite.js +59 -0
  150. package/template/wall-e/sources/gemini-jsonl.js +85 -0
  151. package/template/wall-e/sources/index.js +9 -0
  152. package/template/wall-e/sources/jsonl-utils.js +181 -0
  153. package/template/wall-e/sources/record-types.js +252 -0
  154. package/template/wall-e/sources/registry.js +92 -0
  155. package/template/wall-e/sources/transforms.js +100 -0
  156. package/template/wall-e/sources/walle-jsonl.js +108 -0
  157. package/template/wall-e/tools/coding-middleware.js +31 -1
  158. package/template/wall-e/tools/file-tracker.js +25 -1
  159. package/template/wall-e/tools/local-tools.js +75 -47
  160. package/template/wall-e/tools/session-sharing.js +68 -1
  161. package/template/wall-e/tools/shell-analyzer.js +1 -1
  162. package/template/wall-e/tools/shell-policy.js +47 -0
  163. package/template/wall-e/tools/snapshot.js +42 -0
  164. package/template/wall-e/training/harvester.js +62 -5
  165. package/template/wall-e/utils/repair.js +253 -1
  166. package/template/website/index.html +3 -3
  167. package/template/wall-e/skills/_bundled/slack-mentions/.watched-threads.json +0 -18
@@ -0,0 +1,220 @@
1
+ 'use strict';
2
+
3
+ const { createAgentRunId } = require('../agent-runners/contract');
4
+
5
+ const WRITE_TOOL_NAMES = new Set([
6
+ 'edit_file',
7
+ 'write_file',
8
+ 'multi_edit',
9
+ 'apply_patch',
10
+ 'run_shell',
11
+ 'task',
12
+ ]);
13
+
14
+ function nowIso() {
15
+ return new Date().toISOString();
16
+ }
17
+
18
+ function createExecutionTrace({
19
+ runId = createAgentRunId('run'),
20
+ parentRunId = null,
21
+ mode = 'build',
22
+ provider = '',
23
+ model = '',
24
+ runnerId = '',
25
+ harnessId = '',
26
+ sessionId = '',
27
+ cwd = '',
28
+ sandbox = null,
29
+ taskType = 'coding',
30
+ now = nowIso,
31
+ } = {}) {
32
+ const startedAt = now();
33
+ return {
34
+ version: 1,
35
+ runId,
36
+ parentRunId,
37
+ startedAt,
38
+ endedAt: null,
39
+ status: 'running',
40
+ liveness: 'running',
41
+ request: {
42
+ taskType,
43
+ mode,
44
+ providerPreference: provider || '',
45
+ modelPreference: model || '',
46
+ runnerId,
47
+ harnessId,
48
+ sandbox,
49
+ cwd,
50
+ },
51
+ selection: {
52
+ candidates: [],
53
+ winner: null,
54
+ fallbackUsed: false,
55
+ reason: '',
56
+ },
57
+ attempts: [],
58
+ tools: [],
59
+ context: {
60
+ compacted: false,
61
+ compactionCount: 0,
62
+ inputTokensBefore: null,
63
+ inputTokensAfter: null,
64
+ truncations: [],
65
+ },
66
+ review: null,
67
+ errors: [],
68
+ sessionId,
69
+ };
70
+ }
71
+
72
+ function recordSelection(trace, selection = {}) {
73
+ if (!trace) return trace;
74
+ trace.selection = {
75
+ candidates: selection.candidates || [],
76
+ winner: selection.selected?.id || selection.winner || null,
77
+ fallbackUsed: Boolean(selection.fallbackUsed),
78
+ reason: selection.reason || '',
79
+ };
80
+ return trace;
81
+ }
82
+
83
+ function recordAttemptStart(trace, attempt = {}) {
84
+ if (!trace) return null;
85
+ const id = attempt.id || `attempt-${trace.attempts.length + 1}`;
86
+ const entry = {
87
+ id,
88
+ provider: attempt.provider || attempt.providerType || '',
89
+ model: attempt.model || '',
90
+ runnerId: attempt.runnerId || '',
91
+ harnessId: attempt.harnessId || '',
92
+ startedAt: attempt.startedAt || nowIso(),
93
+ endedAt: null,
94
+ status: 'running',
95
+ stopReason: '',
96
+ error: null,
97
+ usage: null,
98
+ };
99
+ trace.attempts.push(entry);
100
+ return entry;
101
+ }
102
+
103
+ function recordAttemptFinish(trace, attemptId, patch = {}) {
104
+ if (!trace) return null;
105
+ const entry = trace.attempts.find(attempt => attempt.id === attemptId) || trace.attempts[trace.attempts.length - 1];
106
+ if (!entry) return null;
107
+ Object.assign(entry, {
108
+ endedAt: patch.endedAt || nowIso(),
109
+ status: patch.status || (patch.success === false ? 'failed' : 'completed'),
110
+ stopReason: patch.stopReason || entry.stopReason || '',
111
+ error: patch.error || null,
112
+ usage: patch.usage || entry.usage || null,
113
+ });
114
+ if (entry.status === 'failed' && entry.error) trace.errors.push({ attemptId: entry.id, message: entry.error });
115
+ return entry;
116
+ }
117
+
118
+ function recordToolEvent(trace, event = {}) {
119
+ if (!trace) return null;
120
+ const toolName = event.toolName || event.name || 'unknown';
121
+ const sideEffect = inferToolSideEffect(toolName, event);
122
+ const entry = {
123
+ id: event.id || `tool-${trace.tools.length + 1}`,
124
+ toolName,
125
+ status: event.status || 'completed',
126
+ sideEffect,
127
+ startedAt: event.startedAt || null,
128
+ endedAt: event.endedAt || nowIso(),
129
+ changedFiles: Array.isArray(event.changedFiles) ? event.changedFiles : [],
130
+ error: event.error || null,
131
+ };
132
+ trace.tools.push(entry);
133
+ return entry;
134
+ }
135
+
136
+ function inferToolSideEffect(toolName, event = {}) {
137
+ if (event.sideEffect) return event.sideEffect;
138
+ if (event.readOnly === true) return 'read';
139
+ if (event.changedFiles?.length > 0) return 'write';
140
+ if (WRITE_TOOL_NAMES.has(toolName)) return toolName === 'run_shell' ? 'unknown' : 'write';
141
+ return 'read';
142
+ }
143
+
144
+ function recordContextCompaction(trace, compaction = {}) {
145
+ if (!trace) return trace;
146
+ trace.context.compacted = Boolean(compaction.compacted || trace.context.compacted);
147
+ if (compaction.compacted) trace.context.compactionCount += 1;
148
+ if (Number.isFinite(compaction.tokensBeforeCompaction)) {
149
+ trace.context.inputTokensBefore = compaction.tokensBeforeCompaction;
150
+ }
151
+ if (Number.isFinite(compaction.tokensAfterCompaction)) {
152
+ trace.context.inputTokensAfter = compaction.tokensAfterCompaction;
153
+ }
154
+ if (compaction.truncation) trace.context.truncations.push(compaction.truncation);
155
+ return trace;
156
+ }
157
+
158
+ function recordReview(trace, quorum = {}) {
159
+ if (!trace) return trace;
160
+ trace.review = {
161
+ quorumId: quorum.quorumId || null,
162
+ verdict: quorum.verdict || quorum.aggregate?.verdict || '',
163
+ blockers: Array.isArray(quorum.issues) ? quorum.issues.filter(issue => /block/i.test(String(issue))).length : 0,
164
+ scores: quorum.aggregate?.scores || quorum.scores || {},
165
+ confidence: quorum.confidence ?? quorum.aggregate?.confidence ?? null,
166
+ agreement: quorum.agreement ?? quorum.aggregate?.agreement ?? null,
167
+ };
168
+ return trace;
169
+ }
170
+
171
+ function finalizeTrace(trace, { status = 'completed', liveness = null, error = null } = {}) {
172
+ if (!trace) return trace;
173
+ trace.status = status;
174
+ trace.liveness = liveness || (status === 'completed' ? 'finished' : status);
175
+ trace.endedAt = nowIso();
176
+ if (error) trace.errors.push({ message: String(error) });
177
+ return trace;
178
+ }
179
+
180
+ function hasWriteSideEffects(trace) {
181
+ return (trace?.tools || []).some(tool => tool.sideEffect === 'write' || tool.sideEffect === 'unknown');
182
+ }
183
+
184
+ function hasCompletedWriteSideEffects(trace) {
185
+ return (trace?.tools || []).some(tool => (tool.sideEffect === 'write' || tool.sideEffect === 'unknown') && tool.status === 'completed');
186
+ }
187
+
188
+ function decideRetry(trace, failure = {}) {
189
+ if (!trace) return { retry: false, reason: 'missing-trace' };
190
+ if ((trace.tools || []).length === 0) return { retry: true, reason: 'no-tool-side-effects' };
191
+ if (!hasWriteSideEffects(trace)) return { retry: true, reason: 'read-only-tools' };
192
+ if (hasCompletedWriteSideEffects(trace) && failure.toolResultPersisted === true && failure.fileChangesRecorded === true) {
193
+ return { retry: true, reason: 'write-side-effects-recorded' };
194
+ }
195
+ return { retry: false, reason: 'write-side-effects-not-safe-to-retry' };
196
+ }
197
+
198
+ function attachTraceToResult(result = {}, trace = null) {
199
+ if (!trace) return result;
200
+ return {
201
+ ...result,
202
+ trace,
203
+ };
204
+ }
205
+
206
+ module.exports = {
207
+ attachTraceToResult,
208
+ createExecutionTrace,
209
+ decideRetry,
210
+ finalizeTrace,
211
+ hasCompletedWriteSideEffects,
212
+ hasWriteSideEffects,
213
+ inferToolSideEffect,
214
+ recordAttemptFinish,
215
+ recordAttemptStart,
216
+ recordContextCompaction,
217
+ recordReview,
218
+ recordSelection,
219
+ recordToolEvent,
220
+ };
@@ -0,0 +1,266 @@
1
+ 'use strict';
2
+
3
+ const { validateSafeUrl } = require('./ssrf');
4
+
5
+ const TRUSTED_PLUGIN_SOURCES = new Set(['bundled', 'core', 'trusted']);
6
+ const RISKY_PLUGIN_PERMISSIONS = new Set(['network', 'shell', 'browser', 'childAgent', 'childAgents', 'elevatedExec', 'filesystemWrite']);
7
+
8
+ function auditSecurityConfig(config = {}, options = {}) {
9
+ const findings = [];
10
+
11
+ findings.push(...auditRedaction(config.redaction || config.logging || {}));
12
+ findings.push(...auditPermissionRules(config.permissionRules || config.permissions?.rules || config.permissions || []));
13
+ findings.push(...auditApprovalProfiles(config.approvalProfiles || {}, options));
14
+ findings.push(...auditPlugins(config.plugins || config.pluginCandidates || []));
15
+ findings.push(...auditChannels(config.channels || config.channelPolicies || []));
16
+ findings.push(...auditNetworkEndpoints(config.urls || config.networkEndpoints || []));
17
+
18
+ const summary = summarizeFindings(findings);
19
+ return {
20
+ ok: summary.critical === 0 && summary.high === 0,
21
+ summary,
22
+ findings,
23
+ };
24
+ }
25
+
26
+ function auditPermissionRules(rules = []) {
27
+ const findings = [];
28
+ for (const rule of normalizeRules(rules)) {
29
+ if (!rule || rule.action !== 'allow') continue;
30
+ const permission = String(rule.permission || rule.type || '').toLowerCase();
31
+ const pattern = String(rule.pattern || rule.value || '*');
32
+ if (['bash', 'shell', 'run_shell'].includes(permission) && pattern === '*') {
33
+ findings.push(finding('high', 'wildcard-shell-allow', 'Shell permission allows every command', { rule }));
34
+ }
35
+ if (['bash', 'shell', 'run_shell'].includes(permission) && isRiskyInterpreterPattern(pattern)) {
36
+ findings.push(finding('medium', 'risky-interpreter-allow', 'Shell permission allows an interpreter escape pattern', { rule }));
37
+ }
38
+ if (permission === '*' && pattern === '*') {
39
+ findings.push(finding('high', 'wildcard-permission-allow', 'Permission profile allows every permission and pattern', { rule }));
40
+ }
41
+ }
42
+ return findings;
43
+ }
44
+
45
+ function validateApprovalProfile(childProfile = {}, parentProfile = {}) {
46
+ const parent = normalizeApprovalProfile(parentProfile);
47
+ const child = normalizeApprovalProfile(childProfile);
48
+ const findings = [];
49
+
50
+ for (const capability of ['shell', 'network', 'browser', 'childAgents', 'filesystemWrite', 'elevatedExec']) {
51
+ if (child[capability].enabled && !parent[capability].enabled) {
52
+ findings.push(finding('high', 'approval-profile-escalation', `Child approval profile enables ${capability} beyond parent profile`, { capability }));
53
+ }
54
+ }
55
+
56
+ if (child.shell.wildcard && !parent.shell.wildcard) {
57
+ findings.push(finding('high', 'approval-profile-shell-wildcard', 'Child approval profile allows wildcard shell commands beyond parent profile'));
58
+ }
59
+
60
+ if (child.network.allowLocal && !parent.network.allowLocal) {
61
+ findings.push(finding('high', 'approval-profile-local-network', 'Child approval profile allows local/private network access beyond parent profile'));
62
+ }
63
+
64
+ return {
65
+ ok: findings.length === 0,
66
+ findings,
67
+ parent,
68
+ child,
69
+ };
70
+ }
71
+
72
+ function auditApprovalProfiles(profiles = {}, options = {}) {
73
+ const parent = options.parentApprovalProfile || profiles.parent || profiles.default || {};
74
+ const children = Array.isArray(profiles)
75
+ ? profiles
76
+ : Object.entries(profiles.children || profiles.childProfiles || profiles)
77
+ .filter(([name]) => !['parent', 'default', 'children', 'childProfiles'].includes(name))
78
+ .map(([name, profile]) => ({ name, ...profile }));
79
+
80
+ const findings = [];
81
+ for (const profile of children) {
82
+ const result = validateApprovalProfile(profile, parent);
83
+ for (const item of result.findings) {
84
+ findings.push({ ...item, details: { ...item.details, profile: profile.name || profile.id || 'child' } });
85
+ }
86
+ }
87
+ return findings;
88
+ }
89
+
90
+ function auditPluginCandidate(candidate = {}) {
91
+ const findings = [];
92
+ const source = candidate.source || candidate.sourceTrust || 'unknown';
93
+ const manifest = candidate.manifest || candidate;
94
+ const permissions = { ...(manifest.permissions || {}), ...(candidate.permissions || {}) };
95
+ const pluginId = manifest.id || candidate.id || candidate.name || candidate.dir || 'unknown';
96
+
97
+ for (const diagnostic of candidate.diagnostics || []) {
98
+ if (diagnostic.level === 'error' && /world-writable|escapes root|too large/i.test(diagnostic.message || '')) {
99
+ findings.push(finding('high', 'unsafe-plugin-package', 'Plugin package failed discovery security checks', {
100
+ pluginId,
101
+ source,
102
+ diagnostic,
103
+ }));
104
+ }
105
+ }
106
+
107
+ if (!TRUSTED_PLUGIN_SOURCES.has(source)) {
108
+ for (const [key, value] of Object.entries(permissions)) {
109
+ if (value === true && RISKY_PLUGIN_PERMISSIONS.has(key)) {
110
+ findings.push(finding(key === 'network' ? 'medium' : 'high', 'untrusted-plugin-permission', `Untrusted plugin requests ${key} permission`, {
111
+ pluginId,
112
+ source,
113
+ permission: key,
114
+ }));
115
+ }
116
+ }
117
+ }
118
+
119
+ return findings;
120
+ }
121
+
122
+ function auditPlugins(plugins = []) {
123
+ return (plugins || []).flatMap(plugin => auditPluginCandidate(plugin));
124
+ }
125
+
126
+ function auditChannels(channels = []) {
127
+ const findings = [];
128
+ for (const channel of normalizeChannelEntries(channels)) {
129
+ const policy = channel.policy || channel;
130
+ const id = channel.id || channel.name || policy.id || 'unknown';
131
+ if (policy.group?.defaultOpen === true || policy.defaultOpen === true) {
132
+ findings.push(finding('medium', 'channel-group-open', 'Group channel policy is open by default', { channel: id }));
133
+ }
134
+ const dm = policy.dm || policy.directMessages || {};
135
+ const dmHasAllowlist = Array.isArray(dm.allowUsers || dm.allowlist) && (dm.allowUsers || dm.allowlist).length > 0;
136
+ if (dm.enabled !== false && dm.pairingRequired !== true && !dmHasAllowlist) {
137
+ findings.push(finding('low', 'channel-dm-open', 'Direct-message channel policy has no allowlist or pairing requirement', { channel: id }));
138
+ }
139
+ }
140
+ return findings;
141
+ }
142
+
143
+ function auditNetworkEndpoints(urls = []) {
144
+ const findings = [];
145
+ for (const entry of urls || []) {
146
+ const url = typeof entry === 'string' ? entry : entry.url;
147
+ if (!url) continue;
148
+ const result = validateSafeUrl(url);
149
+ if (!result.ok) {
150
+ findings.push(finding('high', 'unsafe-network-endpoint', 'Configured network endpoint fails SSRF safety checks', {
151
+ url,
152
+ reason: result.reason,
153
+ }));
154
+ }
155
+ }
156
+ return findings;
157
+ }
158
+
159
+ function auditRedaction(redaction = {}) {
160
+ if (redaction.enabled === false || redaction.redactSensitive === false || redaction.sensitive === false) {
161
+ return [finding('high', 'redaction-disabled', 'Sensitive-value redaction is disabled')];
162
+ }
163
+ return [];
164
+ }
165
+
166
+ function normalizeApprovalProfile(profile = {}) {
167
+ return {
168
+ shell: normalizeShellCapability(profile),
169
+ network: normalizeNetworkCapability(profile),
170
+ browser: normalizeBooleanCapability(profile, ['browser']),
171
+ childAgents: normalizeBooleanCapability(profile, ['childAgents', 'childAgent']),
172
+ filesystemWrite: normalizeBooleanCapability(profile, ['filesystemWrite', 'write', 'edit']),
173
+ elevatedExec: normalizeBooleanCapability(profile, ['elevatedExec', 'elevated', 'sudo']),
174
+ };
175
+ }
176
+
177
+ function normalizeShellCapability(profile = {}) {
178
+ const raw = profile.shell ?? profile.bash ?? profile.permissions?.shell ?? profile.permissions?.bash ?? profile.tools?.run_shell;
179
+ const allow = [
180
+ ...listValues(raw && typeof raw === 'object' ? raw.allow || raw.allowlist || raw.patterns : []),
181
+ ...listValues(profile.allowedShell || profile.shellAllow || profile.bashAllow),
182
+ ];
183
+ if (typeof raw === 'string' && !['allow', 'ask', 'deny'].includes(raw)) allow.push(raw);
184
+ const enabled = raw === true || raw === 'allow' || allow.length > 0;
185
+ return {
186
+ enabled,
187
+ allow,
188
+ wildcard: raw === true || raw === 'allow' || allow.includes('*'),
189
+ };
190
+ }
191
+
192
+ function normalizeNetworkCapability(profile = {}) {
193
+ const raw = profile.network ?? profile.permissions?.network;
194
+ const enabled = raw === true || raw === 'allow' || Boolean(raw && typeof raw === 'object' && raw.enabled !== false);
195
+ const allowLocal = Boolean(raw && typeof raw === 'object' && (raw.allowLocal || raw.allowPrivate || raw.localhost));
196
+ return {
197
+ enabled,
198
+ allowLocal,
199
+ };
200
+ }
201
+
202
+ function normalizeBooleanCapability(profile = {}, keys = []) {
203
+ for (const key of keys) {
204
+ const direct = profile[key];
205
+ const permission = profile.permissions && profile.permissions[key];
206
+ const tool = profile.tools && profile.tools[key];
207
+ const value = direct ?? permission ?? tool;
208
+ if (value === true || value === 'allow') return { enabled: true };
209
+ }
210
+ return { enabled: false };
211
+ }
212
+
213
+ function normalizeRules(rules) {
214
+ if (Array.isArray(rules)) return rules;
215
+ if (!rules || typeof rules !== 'object') return [];
216
+ const out = [];
217
+ for (const [permission, value] of Object.entries(rules)) {
218
+ if (typeof value === 'string') {
219
+ out.push({ permission, pattern: '*', action: value });
220
+ } else if (value && typeof value === 'object') {
221
+ for (const [pattern, action] of Object.entries(value)) {
222
+ out.push({ permission, pattern, action });
223
+ }
224
+ }
225
+ }
226
+ return out;
227
+ }
228
+
229
+ function normalizeChannelEntries(channels) {
230
+ if (Array.isArray(channels)) return channels;
231
+ if (!channels || typeof channels !== 'object') return [];
232
+ return Object.entries(channels).map(([id, policy]) => ({ id, policy }));
233
+ }
234
+
235
+ function listValues(value) {
236
+ if (!value) return [];
237
+ return (Array.isArray(value) ? value : [value]).map(item => String(item)).filter(Boolean);
238
+ }
239
+
240
+ function isRiskyInterpreterPattern(pattern) {
241
+ return /^(bash|sh|zsh)\s+-c\b/.test(pattern) ||
242
+ /^(node|python|python3|ruby|perl)\s+(-e|-c)\b/.test(pattern);
243
+ }
244
+
245
+ function summarizeFindings(findings = []) {
246
+ return findings.reduce((summary, item) => {
247
+ summary[item.severity] = (summary[item.severity] || 0) + 1;
248
+ summary.total += 1;
249
+ return summary;
250
+ }, { critical: 0, high: 0, medium: 0, low: 0, total: 0 });
251
+ }
252
+
253
+ function finding(severity, code, message, details = {}) {
254
+ return { severity, code, message, details };
255
+ }
256
+
257
+ module.exports = {
258
+ auditApprovalProfiles,
259
+ auditChannels,
260
+ auditNetworkEndpoints,
261
+ auditPermissionRules,
262
+ auditPluginCandidate,
263
+ auditSecurityConfig,
264
+ normalizeApprovalProfile,
265
+ validateApprovalProfile,
266
+ };