solidity-argus 0.2.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.
- package/AGENTS.md +3 -3
- package/README.md +93 -37
- package/package.json +34 -7
- package/skills/INVENTORY.md +88 -57
- package/skills/README.md +26 -23
- package/skills/case-studies/beanstalk-governance/SKILL.md +52 -0
- package/skills/case-studies/bzx-flash-loan/SKILL.md +53 -0
- package/skills/case-studies/cream-finance/SKILL.md +52 -0
- package/skills/case-studies/curve-reentrancy/SKILL.md +52 -0
- package/skills/case-studies/dao-hack/SKILL.md +51 -0
- package/skills/case-studies/euler-finance/SKILL.md +52 -0
- package/skills/case-studies/harvest-finance/SKILL.md +52 -0
- package/skills/case-studies/level-finance/SKILL.md +51 -0
- package/skills/case-studies/mango-markets/SKILL.md +53 -0
- package/skills/case-studies/nomad-bridge/SKILL.md +51 -0
- package/skills/case-studies/parity-multisig/SKILL.md +55 -0
- package/skills/case-studies/poly-network/SKILL.md +51 -0
- package/skills/case-studies/rari-fuse/SKILL.md +51 -0
- package/skills/case-studies/ronin-bridge/SKILL.md +52 -0
- package/skills/case-studies/wormhole-bridge/SKILL.md +51 -0
- package/skills/manifests/smartbugs.json +1 -3
- package/skills/manifests/sunweb3sec.json +1 -3
- package/skills/vulnerability-patterns/access-control/SKILL.md +14 -0
- package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +13 -1
- package/skills/vulnerability-patterns/assert-violation/SKILL.md +8 -1
- package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +12 -1
- package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +2 -1
- package/skills/vulnerability-patterns/cross-chain-bridge-vulnerabilities/SKILL.md +217 -0
- package/skills/vulnerability-patterns/default-visibility/SKILL.md +13 -1
- package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +2 -1
- package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +8 -1
- package/skills/vulnerability-patterns/dos-revert/SKILL.md +1 -0
- package/skills/vulnerability-patterns/erc4626-exchange-rate-manipulation/SKILL.md +64 -0
- package/skills/vulnerability-patterns/fee-on-transfer-tokens/SKILL.md +93 -0
- package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +1 -0
- package/skills/vulnerability-patterns/floating-pragma/SKILL.md +8 -1
- package/skills/vulnerability-patterns/front-running-attacks/SKILL.md +209 -0
- package/skills/vulnerability-patterns/gas-optimization-patterns/SKILL.md +203 -0
- package/skills/vulnerability-patterns/governance-attacks/SKILL.md +208 -0
- package/skills/vulnerability-patterns/hash-collision/SKILL.md +8 -1
- package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +12 -1
- package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +8 -1
- package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +8 -1
- package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +12 -1
- package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +7 -1
- package/skills/vulnerability-patterns/logic-errors/SKILL.md +10 -0
- package/skills/vulnerability-patterns/missing-parameter-bounds/SKILL.md +44 -0
- package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +17 -1
- package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +12 -1
- package/skills/vulnerability-patterns/off-by-one/SKILL.md +7 -1
- package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +9 -0
- package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +8 -1
- package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +1 -0
- package/skills/vulnerability-patterns/proxy-vulnerabilities/SKILL.md +209 -0
- package/skills/vulnerability-patterns/reentrancy/SKILL.md +9 -0
- package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +8 -1
- package/skills/vulnerability-patterns/share-accounting-desynchronization/SKILL.md +44 -0
- package/skills/vulnerability-patterns/signature-malleability/SKILL.md +2 -1
- package/skills/vulnerability-patterns/stateful-parameter-update-drift/SKILL.md +44 -0
- package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +2 -1
- package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +8 -1
- package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +8 -1
- package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +8 -1
- package/skills/vulnerability-patterns/unsafe-erc20-transfers/SKILL.md +132 -0
- package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +11 -1
- package/skills/vulnerability-patterns/unused-variables/SKILL.md +8 -1
- package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +8 -1
- package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +8 -1
- package/skills/vulnerability-patterns/weird-tokens/SKILL.md +10 -0
- package/skills/vulnerability-patterns/zero-address-misconfiguration/SKILL.md +48 -0
- package/src/agents/argus-prompt.ts +34 -7
- package/src/agents/pythia-prompt.ts +13 -4
- package/src/agents/scribe-prompt.ts +20 -2
- package/src/agents/sentinel-prompt.ts +45 -5
- package/src/cli/cli-program.ts +29 -26
- package/src/cli/commands/check-skills.ts +135 -0
- package/src/cli/commands/doctor.ts +48 -26
- package/src/cli/commands/init.ts +5 -3
- package/src/cli/commands/install.ts +7 -5
- package/src/cli/commands/lint-skills.ts +16 -12
- package/src/cli/index.ts +5 -5
- package/src/cli/types.ts +3 -3
- package/src/config/index.ts +1 -1
- package/src/config/loader.ts +4 -6
- package/src/config/schema.ts +6 -5
- package/src/config/types.ts +2 -2
- package/src/constants/defaults.ts +2 -0
- package/src/create-hooks.ts +145 -34
- package/src/create-managers.ts +10 -8
- package/src/create-tools.ts +13 -9
- package/src/features/background-agent/background-manager.ts +93 -87
- package/src/features/background-agent/index.ts +1 -1
- package/src/features/context-monitor/context-monitor.ts +3 -3
- package/src/features/context-monitor/index.ts +2 -2
- package/src/features/error-recovery/session-recovery.ts +2 -4
- package/src/features/error-recovery/tool-error-recovery.ts +12 -7
- package/src/features/index.ts +5 -5
- package/src/features/persistent-state/audit-state-manager.ts +143 -60
- package/src/features/persistent-state/global-run-index.ts +38 -0
- package/src/features/persistent-state/index.ts +1 -1
- package/src/features/persistent-state/run-journal.ts +86 -0
- package/src/hooks/config-handler.ts +28 -11
- package/src/hooks/context-budget.ts +2 -5
- package/src/hooks/event-hook.ts +47 -23
- package/src/hooks/hook-system.ts +4 -4
- package/src/hooks/index.ts +5 -5
- package/src/hooks/knowledge-sync-hook.ts +18 -21
- package/src/hooks/recon-context-builder.ts +2 -2
- package/src/hooks/safe-create-hook.ts +6 -7
- package/src/hooks/system-prompt-hook.ts +18 -1
- package/src/hooks/tool-tracking-hook.ts +110 -51
- package/src/hooks/types.ts +2 -1
- package/src/index.ts +24 -37
- package/src/knowledge/retry.ts +22 -22
- package/src/knowledge/scvd-client.ts +88 -95
- package/src/knowledge/scvd-errors.ts +35 -35
- package/src/knowledge/scvd-index.ts +78 -80
- package/src/knowledge/scvd-sync.ts +106 -101
- package/src/managers/index.ts +1 -1
- package/src/managers/types.ts +19 -14
- package/src/plugin-interface.ts +7 -9
- package/src/shared/binary-utils.ts +44 -35
- package/src/shared/deep-merge.ts +55 -36
- package/src/shared/file-utils.ts +21 -19
- package/src/shared/index.ts +11 -5
- package/src/shared/jsonc-parser.ts +123 -28
- package/src/shared/logger.ts +16 -3
- package/src/shared/project-utils.ts +30 -0
- package/src/skills/analysis/cluster.ts +414 -0
- package/src/skills/analysis/gates.ts +227 -0
- package/src/skills/analysis/index.ts +33 -0
- package/src/skills/analysis/normalize.ts +217 -0
- package/src/skills/analysis/similarity.ts +224 -0
- package/src/skills/argus-skill-resolver.ts +17 -6
- package/src/skills/skill-schema.ts +11 -10
- package/src/solodit-lifecycle.ts +203 -0
- package/src/state/audit-state.ts +8 -8
- package/src/state/finding-store.ts +68 -55
- package/src/state/types.ts +88 -67
- package/src/tools/argus-skill-load-tool.ts +12 -7
- package/src/tools/contract-analyzer-tool.ts +142 -77
- package/src/tools/forge-coverage-tool.ts +226 -0
- package/src/tools/forge-fuzz-tool.ts +127 -127
- package/src/tools/forge-test-tool.ts +201 -158
- package/src/tools/gas-analysis-tool.ts +264 -0
- package/src/tools/pattern-checker-tool.ts +203 -191
- package/src/tools/pattern-loader.ts +5 -111
- package/src/tools/pattern-schema.ts +3 -0
- package/src/tools/proxy-detection-tool.ts +224 -0
- package/src/tools/report-generator-tool.ts +305 -206
- package/src/tools/slither-tool.ts +266 -218
- package/src/tools/solodit-search-tool.ts +235 -119
- package/src/tools/sync-knowledge-tool.ts +7 -11
- package/src/utils/audit-artifact-detector.ts +28 -29
- package/src/utils/dependency-scanner.ts +37 -37
- package/src/utils/project-detector.ts +111 -124
- package/src/utils/solidity-parser.ts +175 -75
- package/skills/patterns/access-control.yaml +0 -31
- package/skills/patterns/erc4626.yaml +0 -29
- package/skills/patterns/flash-loan.yaml +0 -20
- package/skills/patterns/oracle.yaml +0 -30
- package/skills/patterns/proxy.yaml +0 -30
- package/skills/patterns/reentrancy.yaml +0 -30
- package/skills/patterns/signature.yaml +0 -31
- package/src/hooks/event-hook-v2.ts +0 -99
- package/src/state/plugin-state.ts +0 -14
package/src/create-hooks.ts
CHANGED
|
@@ -1,34 +1,47 @@
|
|
|
1
|
+
import { join } from "node:path"
|
|
1
2
|
import type { Hooks as PluginHooks } from "@opencode-ai/plugin"
|
|
2
3
|
import type { ArgusConfig } from "./config/types"
|
|
3
|
-
import type { Managers } from "./managers/types"
|
|
4
|
-
import type { HookName } from "./hooks/types"
|
|
5
|
-
import { createConfigHandler } from "./hooks/config-handler"
|
|
6
|
-
import { createCompactionHook } from "./hooks/compaction-hook"
|
|
7
|
-
import { createToolTrackingHook } from "./hooks/tool-tracking-hook"
|
|
8
|
-
import { createEventHookV2 } from "./hooks/event-hook-v2"
|
|
9
|
-
import { createAgentTracker } from "./hooks/agent-tracker"
|
|
10
|
-
import { createSystemPromptHook } from "./hooks/system-prompt-hook"
|
|
11
|
-
import { safeCreateHook } from "./hooks/safe-create-hook"
|
|
12
|
-
import { createContextMonitor, createToolOutputTruncator } from "./features/context-monitor"
|
|
13
4
|
import { createAuditEnforcer } from "./features/audit-enforcer/audit-enforcer"
|
|
5
|
+
import { createContextMonitor, createToolOutputTruncator } from "./features/context-monitor"
|
|
6
|
+
import {
|
|
7
|
+
createSessionRecoveryHandler,
|
|
8
|
+
createToolErrorRecoveryHandler,
|
|
9
|
+
} from "./features/error-recovery"
|
|
10
|
+
import { createDebouncedSave } from "./features/persistent-state/audit-state-manager"
|
|
11
|
+
import { recordRun } from "./features/persistent-state/global-run-index"
|
|
12
|
+
import { createRunJournal } from "./features/persistent-state/run-journal"
|
|
13
|
+
import { createAgentTracker } from "./hooks/agent-tracker"
|
|
14
|
+
import { createCompactionHook } from "./hooks/compaction-hook"
|
|
15
|
+
import { createConfigHandler } from "./hooks/config-handler"
|
|
14
16
|
import { getTokenBudgetForAgent } from "./hooks/context-budget"
|
|
15
|
-
import {
|
|
16
|
-
import { createToolErrorRecoveryHandler } from "./features/error-recovery"
|
|
17
|
-
import { detectProject } from "./utils/project-detector"
|
|
18
|
-
import type { ProjectConfig } from "./utils/project-detector"
|
|
19
|
-
import { detectAuditArtifacts } from "./utils/audit-artifact-detector"
|
|
17
|
+
import { createEventHook } from "./hooks/event-hook"
|
|
20
18
|
import type { ReconContext } from "./hooks/recon-context-builder"
|
|
21
19
|
import { buildReconContextBlock } from "./hooks/recon-context-builder"
|
|
20
|
+
import { safeCreateHook } from "./hooks/safe-create-hook"
|
|
21
|
+
import { createSystemPromptHook } from "./hooks/system-prompt-hook"
|
|
22
|
+
import { createToolTrackingHook } from "./hooks/tool-tracking-hook"
|
|
23
|
+
import type { HookName } from "./hooks/types"
|
|
24
|
+
import type { Managers } from "./managers/types"
|
|
25
|
+
import { createLogger } from "./shared/logger"
|
|
22
26
|
import type { AuditState } from "./state/types"
|
|
27
|
+
import { detectAuditArtifacts } from "./utils/audit-artifact-detector"
|
|
28
|
+
import { detectProject, type ProjectConfig } from "./utils/project-detector"
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
const logger = createLogger()
|
|
31
|
+
|
|
32
|
+
export type AgentTrackerRef = {
|
|
33
|
+
getAgentForSession(sessionID: string): string | undefined
|
|
34
|
+
isArgusAgent(sessionID: string): boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let _agentTrackerRef: AgentTrackerRef | undefined
|
|
25
38
|
|
|
26
39
|
export function getAgentForSession(sessionID: string): string | undefined {
|
|
27
|
-
return
|
|
40
|
+
return _agentTrackerRef?.getAgentForSession(sessionID)
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
export function isArgusAgent(sessionID: string): boolean {
|
|
31
|
-
return
|
|
44
|
+
return _agentTrackerRef?.isArgusAgent(sessionID) ?? false
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
export type Hooks = Pick<
|
|
@@ -62,26 +75,103 @@ export function createHooks(args: {
|
|
|
62
75
|
const { config, managers, projectDir, isHookEnabled } = args
|
|
63
76
|
const { auditStateManager, backgroundManager } = managers
|
|
64
77
|
const agentTracker = createAgentTracker()
|
|
65
|
-
|
|
78
|
+
_agentTrackerRef = agentTracker
|
|
66
79
|
|
|
67
80
|
const contextMonitor = createContextMonitor()
|
|
68
81
|
const sessionRecoveryHandler = createSessionRecoveryHandler(auditStateManager)
|
|
82
|
+
const debouncedSave = createDebouncedSave(auditStateManager.save)
|
|
83
|
+
const runJournal = createRunJournal(projectDir)
|
|
69
84
|
let auditStateGetter: (() => AuditState | null) | undefined
|
|
70
|
-
const toolErrorRecoveryHandler = createToolErrorRecoveryHandler(
|
|
85
|
+
const toolErrorRecoveryHandler = createToolErrorRecoveryHandler(
|
|
86
|
+
() => auditStateGetter?.() ?? null,
|
|
87
|
+
(patch) => auditStateManager.update(patch),
|
|
88
|
+
)
|
|
71
89
|
const outputTruncator = createToolOutputTruncator()
|
|
72
90
|
|
|
73
|
-
|
|
91
|
+
// Sub-handlers run sequentially. The state persistence handler MUST be first:
|
|
92
|
+
// it loads persisted state on session.created, overriding the fresh default.
|
|
93
|
+
const {
|
|
94
|
+
hook: eventHook,
|
|
95
|
+
getAuditState,
|
|
96
|
+
setAuditState,
|
|
97
|
+
} = createEventHook(projectDir, [
|
|
74
98
|
async ({ type, sessionId, auditState, setAuditState: setState }) => {
|
|
75
99
|
if (type === "session.created") {
|
|
76
|
-
const
|
|
100
|
+
const timestamp = Date.now()
|
|
101
|
+
let recoveredState: AuditState | null = null
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
recoveredState = await auditStateManager.load()
|
|
105
|
+
} finally {
|
|
106
|
+
runJournal.log({
|
|
107
|
+
type: "state.loaded",
|
|
108
|
+
timestamp,
|
|
109
|
+
success: recoveredState !== null,
|
|
110
|
+
findingsCount: recoveredState?.findings.length ?? 0,
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
77
114
|
if (recoveredState) {
|
|
78
115
|
setState(recoveredState)
|
|
79
116
|
}
|
|
117
|
+
|
|
118
|
+
runJournal.log({
|
|
119
|
+
type: "session.created",
|
|
120
|
+
sessionId,
|
|
121
|
+
timestamp: Date.now(),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const effectiveState = recoveredState ?? auditStateManager.get()
|
|
125
|
+
if (effectiveState) {
|
|
126
|
+
void recordRun({
|
|
127
|
+
runId: effectiveState.sessionId,
|
|
128
|
+
opencodeSessionId: sessionId,
|
|
129
|
+
projectDir: effectiveState.projectDir,
|
|
130
|
+
statePath: join(effectiveState.projectDir, ".opencode", "argus-state.json"),
|
|
131
|
+
journalPath: join(effectiveState.projectDir, ".opencode", "argus-journal.jsonl"),
|
|
132
|
+
startedAt: effectiveState.startTime,
|
|
133
|
+
phase: effectiveState.currentPhase,
|
|
134
|
+
findingsCount: effectiveState.findings.length,
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
80
138
|
return
|
|
81
139
|
}
|
|
82
140
|
|
|
83
141
|
if (type === "session.idle" && auditState) {
|
|
84
|
-
await
|
|
142
|
+
await debouncedSave.flush()
|
|
143
|
+
|
|
144
|
+
let saveSuccess = true
|
|
145
|
+
try {
|
|
146
|
+
await auditStateManager.save(auditState)
|
|
147
|
+
} catch {
|
|
148
|
+
saveSuccess = false
|
|
149
|
+
} finally {
|
|
150
|
+
runJournal.log({
|
|
151
|
+
type: "state.saved",
|
|
152
|
+
timestamp: Date.now(),
|
|
153
|
+
success: saveSuccess,
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
runJournal.log({
|
|
158
|
+
type: "session.idle",
|
|
159
|
+
timestamp: Date.now(),
|
|
160
|
+
findingsCount: auditState.findings.length,
|
|
161
|
+
toolsExecutedCount: auditState.toolsExecuted.length,
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
void recordRun({
|
|
165
|
+
runId: auditState.sessionId,
|
|
166
|
+
opencodeSessionId: sessionId,
|
|
167
|
+
projectDir: auditState.projectDir,
|
|
168
|
+
statePath: join(auditState.projectDir, ".opencode", "argus-state.json"),
|
|
169
|
+
journalPath: join(auditState.projectDir, ".opencode", "argus-journal.jsonl"),
|
|
170
|
+
startedAt: auditState.startTime,
|
|
171
|
+
phase: auditState.currentPhase,
|
|
172
|
+
findingsCount: auditState.findings.length,
|
|
173
|
+
})
|
|
174
|
+
|
|
85
175
|
return
|
|
86
176
|
}
|
|
87
177
|
|
|
@@ -89,7 +179,13 @@ export function createHooks(args: {
|
|
|
89
179
|
if (sessionId) {
|
|
90
180
|
agentTracker.clearSession(sessionId)
|
|
91
181
|
}
|
|
92
|
-
|
|
182
|
+
|
|
183
|
+
await auditStateManager.archive()
|
|
184
|
+
runJournal.log({
|
|
185
|
+
type: "session.deleted",
|
|
186
|
+
timestamp: Date.now(),
|
|
187
|
+
archived: true,
|
|
188
|
+
})
|
|
93
189
|
}
|
|
94
190
|
},
|
|
95
191
|
async ({ type, sessionId, setAuditState: setState }) => {
|
|
@@ -121,11 +217,12 @@ export function createHooks(args: {
|
|
|
121
217
|
},
|
|
122
218
|
getTokenBudget: getTokenBudgetForAgent,
|
|
123
219
|
getEnforcerReminder: auditEnforcer,
|
|
124
|
-
getReconBlock: () =>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
220
|
+
getReconBlock: () =>
|
|
221
|
+
buildReconContextBlock({
|
|
222
|
+
projectConfig: reconProjectConfig,
|
|
223
|
+
dependencyRisks: reconProjectConfig?.dependencyRisks ?? [],
|
|
224
|
+
auditArtifacts: detectAuditArtifacts(projectDir),
|
|
225
|
+
}),
|
|
129
226
|
})
|
|
130
227
|
|
|
131
228
|
let reconProjectConfig: ProjectConfig | null = null
|
|
@@ -135,7 +232,7 @@ export function createHooks(args: {
|
|
|
135
232
|
reconProjectConfig = config
|
|
136
233
|
})
|
|
137
234
|
.catch(() => {
|
|
138
|
-
|
|
235
|
+
logger.debug("Project detection failed, using fallback recon context")
|
|
139
236
|
})
|
|
140
237
|
|
|
141
238
|
const getReconContext = (): ReconContext => ({
|
|
@@ -149,7 +246,23 @@ export function createHooks(args: {
|
|
|
149
246
|
: undefined
|
|
150
247
|
|
|
151
248
|
const toolTrackingHook = isHookEnabled("tool-tracking")
|
|
152
|
-
? safeCreateHook(
|
|
249
|
+
? safeCreateHook(
|
|
250
|
+
() =>
|
|
251
|
+
createToolTrackingHook(getAuditState, ({ tool, findingsCount }) => {
|
|
252
|
+
const currentState = getAuditState()
|
|
253
|
+
if (currentState) {
|
|
254
|
+
debouncedSave.save(currentState)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
runJournal.log({
|
|
258
|
+
type: "tool.executed",
|
|
259
|
+
tool,
|
|
260
|
+
timestamp: Date.now(),
|
|
261
|
+
findingsCount,
|
|
262
|
+
})
|
|
263
|
+
}),
|
|
264
|
+
"tool-tracking",
|
|
265
|
+
)
|
|
153
266
|
: undefined
|
|
154
267
|
|
|
155
268
|
const safeEventHook = isHookEnabled("event")
|
|
@@ -186,9 +299,7 @@ export function createHooks(args: {
|
|
|
186
299
|
result: output.output,
|
|
187
300
|
})
|
|
188
301
|
|
|
189
|
-
const outputWithHint = recoveryHint
|
|
190
|
-
? `${output.output}${recoveryHint}`
|
|
191
|
-
: output.output
|
|
302
|
+
const outputWithHint = recoveryHint ? `${output.output}${recoveryHint}` : output.output
|
|
192
303
|
output.output = outputTruncator(outputWithHint)
|
|
193
304
|
}
|
|
194
305
|
: undefined,
|
package/src/create-managers.ts
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import type { ArgusConfig } from "./config/types"
|
|
2
|
-
import type {
|
|
2
|
+
import type { Dispatcher } from "./features/background-agent/background-manager"
|
|
3
3
|
import { createBackgroundManager } from "./features/background-agent/background-manager"
|
|
4
4
|
import { createAuditStateManager } from "./features/persistent-state/audit-state-manager"
|
|
5
|
+
import type { Managers } from "./managers/types"
|
|
5
6
|
import { createLogger } from "./shared/logger"
|
|
6
7
|
|
|
7
8
|
export function createManagers(args: {
|
|
8
9
|
projectDir: string
|
|
9
10
|
config: ArgusConfig
|
|
11
|
+
backgroundDispatcher?: Dispatcher
|
|
10
12
|
}): Managers {
|
|
11
|
-
const { projectDir } = args
|
|
13
|
+
const { projectDir, config, backgroundDispatcher } = args
|
|
12
14
|
const logger = createLogger()
|
|
13
15
|
|
|
14
16
|
const backgroundManager = createBackgroundManager(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
`Background dispatch not wired: ${agentName} (${prompt.slice(0, 50)}...)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
},
|
|
17
|
+
backgroundDispatcher ??
|
|
18
|
+
(async (agentName: string, prompt: string) => {
|
|
19
|
+
logger.warn(`Background dispatch not wired: ${agentName} (${prompt.slice(0, 50)}...)`)
|
|
20
|
+
return `noop-${Date.now()}`
|
|
21
|
+
}),
|
|
22
|
+
{ maxConcurrent: config.background?.max_concurrent ?? 3 },
|
|
21
23
|
)
|
|
22
24
|
|
|
23
25
|
const auditStateManager = createAuditStateManager(projectDir)
|
package/src/create-tools.ts
CHANGED
|
@@ -1,31 +1,35 @@
|
|
|
1
1
|
import type { ToolDefinition } from "@opencode-ai/plugin"
|
|
2
2
|
import type { ArgusConfig } from "./config/types"
|
|
3
|
-
import {
|
|
4
|
-
import { forgeTestTool } from "./tools/forge-test-tool"
|
|
5
|
-
import { forgeFuzzTool } from "./tools/forge-fuzz-tool"
|
|
3
|
+
import { argusSkillLoadTool } from "./tools/argus-skill-load-tool"
|
|
6
4
|
import { contractAnalyzerTool } from "./tools/contract-analyzer-tool"
|
|
5
|
+
import { forgeCoverageTool } from "./tools/forge-coverage-tool"
|
|
6
|
+
import { forgeFuzzTool } from "./tools/forge-fuzz-tool"
|
|
7
|
+
import { forgeTestTool } from "./tools/forge-test-tool"
|
|
8
|
+
import { gasAnalysisTool } from "./tools/gas-analysis-tool"
|
|
7
9
|
import { patternCheckerTool } from "./tools/pattern-checker-tool"
|
|
8
|
-
import {
|
|
10
|
+
import { proxyDetectionTool } from "./tools/proxy-detection-tool"
|
|
9
11
|
import { reportGeneratorTool } from "./tools/report-generator-tool"
|
|
12
|
+
import { slitherTool } from "./tools/slither-tool"
|
|
13
|
+
import { createSoloditSearchTool } from "./tools/solodit-search-tool"
|
|
10
14
|
import { syncKnowledgeTool } from "./tools/sync-knowledge-tool"
|
|
11
|
-
import { argusSkillLoadTool } from "./tools/argus-skill-load-tool"
|
|
12
15
|
|
|
13
|
-
export function createTools(
|
|
14
|
-
config: ArgusConfig,
|
|
15
|
-
): Record<string, ToolDefinition> {
|
|
16
|
+
export function createTools(config: ArgusConfig): Record<string, ToolDefinition> {
|
|
16
17
|
const tools: Record<string, ToolDefinition> = {
|
|
17
18
|
argus_slither_analyze: slitherTool,
|
|
18
19
|
argus_forge_test: forgeTestTool,
|
|
20
|
+
argus_gas_analysis: gasAnalysisTool,
|
|
19
21
|
argus_forge_fuzz: forgeFuzzTool,
|
|
22
|
+
argus_forge_coverage: forgeCoverageTool,
|
|
20
23
|
argus_analyze_contract: contractAnalyzerTool,
|
|
21
24
|
argus_check_patterns: patternCheckerTool,
|
|
25
|
+
argus_proxy_detection: proxyDetectionTool,
|
|
22
26
|
argus_skill_load: argusSkillLoadTool,
|
|
23
27
|
argus_generate_report: reportGeneratorTool,
|
|
24
28
|
argus_sync_knowledge: syncKnowledgeTool,
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
if (config.solodit?.enabled !== false) {
|
|
28
|
-
tools.argus_solodit_search =
|
|
32
|
+
tools.argus_solodit_search = createSoloditSearchTool(config.solodit?.port ?? 3000)
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
return tools
|
|
@@ -1,122 +1,125 @@
|
|
|
1
|
-
import type { BackgroundManager } from "../../managers/types"
|
|
2
|
-
import { createLogger } from "../../shared/logger"
|
|
1
|
+
import type { BackgroundManager } from "../../managers/types"
|
|
2
|
+
import { createLogger } from "../../shared/logger"
|
|
3
3
|
|
|
4
|
-
type TaskStatus = "queued" | "running" | "completed" | "failed" | "cancelled"
|
|
5
|
-
type CompletionCallback = (taskId: string, result: unknown) => void
|
|
4
|
+
type TaskStatus = "queued" | "running" | "completed" | "failed" | "cancelled"
|
|
5
|
+
type CompletionCallback = (taskId: string, result: unknown) => void
|
|
6
6
|
|
|
7
7
|
export interface BackgroundTaskOptions {
|
|
8
|
-
priority?: number
|
|
9
|
-
max_concurrent?: number
|
|
8
|
+
priority?: number
|
|
9
|
+
max_concurrent?: number
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export interface BackgroundManagerWithTaskCallbacks extends BackgroundManager {
|
|
13
|
-
dispatch(agentName: string, prompt: string, options?: BackgroundTaskOptions): string
|
|
14
|
-
onComplete(callback: CompletionCallback): void
|
|
15
|
-
onComplete(taskId: string, callback: CompletionCallback): void
|
|
13
|
+
dispatch(agentName: string, prompt: string, options?: BackgroundTaskOptions): string
|
|
14
|
+
onComplete(callback: CompletionCallback): void
|
|
15
|
+
onComplete(taskId: string, callback: CompletionCallback): void
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
interface TaskInfo {
|
|
19
|
-
status: TaskStatus
|
|
20
|
-
agentName: string
|
|
21
|
-
prompt: string
|
|
22
|
-
options?: BackgroundTaskOptions
|
|
23
|
-
result?: unknown
|
|
24
|
-
error?: unknown
|
|
25
|
-
callbacks: Set<CompletionCallback
|
|
19
|
+
status: TaskStatus
|
|
20
|
+
agentName: string
|
|
21
|
+
prompt: string
|
|
22
|
+
options?: BackgroundTaskOptions
|
|
23
|
+
result?: unknown
|
|
24
|
+
error?: unknown
|
|
25
|
+
callbacks: Set<CompletionCallback>
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
type Dispatcher = (
|
|
28
|
+
export type Dispatcher = (
|
|
29
29
|
agentName: string,
|
|
30
30
|
prompt: string,
|
|
31
31
|
options?: BackgroundTaskOptions,
|
|
32
|
-
) => Promise<string
|
|
32
|
+
) => Promise<string>
|
|
33
33
|
|
|
34
34
|
export function createBackgroundManager(
|
|
35
|
-
|
|
35
|
+
initialDispatcher: Dispatcher,
|
|
36
|
+
options?: { maxConcurrent?: number },
|
|
36
37
|
): BackgroundManagerWithTaskCallbacks {
|
|
37
|
-
const logger = createLogger()
|
|
38
|
-
const tasks = new Map<string, TaskInfo>()
|
|
39
|
-
const queue: string[] = []
|
|
40
|
-
const globalCallbacks = new Set<CompletionCallback>()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
let
|
|
44
|
-
|
|
38
|
+
const logger = createLogger()
|
|
39
|
+
const tasks = new Map<string, TaskInfo>()
|
|
40
|
+
const queue: string[] = []
|
|
41
|
+
const globalCallbacks = new Set<CompletionCallback>()
|
|
42
|
+
|
|
43
|
+
const dispatcher = initialDispatcher
|
|
44
|
+
let runningCount = 0
|
|
45
|
+
const maxConcurrent = options?.maxConcurrent ?? 3
|
|
46
|
+
let taskCount = 0
|
|
47
|
+
|
|
48
|
+
function safeInvokeCallback(callback: CompletionCallback, taskId: string, result: unknown): void {
|
|
49
|
+
try {
|
|
50
|
+
callback(taskId, result)
|
|
51
|
+
} catch (error: unknown) {
|
|
52
|
+
logger.error(`Background callback failed: ${taskId}`, error)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
45
55
|
|
|
46
56
|
function invokeCallbacks(taskId: string, result: unknown): void {
|
|
47
|
-
const task = tasks.get(taskId)
|
|
57
|
+
const task = tasks.get(taskId)
|
|
48
58
|
if (!task) {
|
|
49
|
-
return
|
|
59
|
+
return
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
for (const callback of globalCallbacks) {
|
|
53
|
-
callback
|
|
63
|
+
safeInvokeCallback(callback, taskId, result)
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
for (const callback of task.callbacks) {
|
|
57
|
-
callback
|
|
67
|
+
safeInvokeCallback(callback, taskId, result)
|
|
58
68
|
}
|
|
59
69
|
|
|
60
|
-
task.callbacks.clear()
|
|
70
|
+
task.callbacks.clear()
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
function processQueue(): void {
|
|
64
74
|
while (runningCount < maxConcurrent && queue.length > 0) {
|
|
65
|
-
const nextTaskId = queue.shift()
|
|
75
|
+
const nextTaskId = queue.shift()
|
|
66
76
|
|
|
67
77
|
if (!nextTaskId) {
|
|
68
|
-
return
|
|
78
|
+
return
|
|
69
79
|
}
|
|
70
80
|
|
|
71
|
-
const task = tasks.get(nextTaskId)
|
|
81
|
+
const task = tasks.get(nextTaskId)
|
|
72
82
|
if (!task || task.status === "cancelled") {
|
|
73
|
-
continue
|
|
83
|
+
continue
|
|
74
84
|
}
|
|
75
85
|
|
|
76
|
-
task.status = "running"
|
|
77
|
-
runningCount += 1
|
|
86
|
+
task.status = "running"
|
|
87
|
+
runningCount += 1
|
|
78
88
|
|
|
79
89
|
dispatcher(task.agentName, task.prompt, task.options)
|
|
80
90
|
.then((result) => {
|
|
81
|
-
const currentTask = tasks.get(nextTaskId)
|
|
91
|
+
const currentTask = tasks.get(nextTaskId)
|
|
82
92
|
|
|
83
93
|
if (!currentTask || currentTask.status === "cancelled") {
|
|
84
|
-
return
|
|
94
|
+
return
|
|
85
95
|
}
|
|
86
96
|
|
|
87
|
-
currentTask.status = "completed"
|
|
88
|
-
currentTask.result = result
|
|
89
|
-
invokeCallbacks(nextTaskId, result)
|
|
97
|
+
currentTask.status = "completed"
|
|
98
|
+
currentTask.result = result
|
|
99
|
+
invokeCallbacks(nextTaskId, result)
|
|
90
100
|
})
|
|
91
101
|
.catch((error: unknown) => {
|
|
92
|
-
const currentTask = tasks.get(nextTaskId)
|
|
102
|
+
const currentTask = tasks.get(nextTaskId)
|
|
93
103
|
|
|
94
104
|
if (!currentTask || currentTask.status === "cancelled") {
|
|
95
|
-
return
|
|
105
|
+
return
|
|
96
106
|
}
|
|
97
107
|
|
|
98
|
-
currentTask.status = "failed"
|
|
99
|
-
currentTask.error = error
|
|
100
|
-
logger.error(`Background task failed: ${nextTaskId}`, error)
|
|
108
|
+
currentTask.status = "failed"
|
|
109
|
+
currentTask.error = error
|
|
110
|
+
logger.error(`Background task failed: ${nextTaskId}`, error)
|
|
111
|
+
invokeCallbacks(nextTaskId, error)
|
|
101
112
|
})
|
|
102
113
|
.finally(() => {
|
|
103
|
-
runningCount = Math.max(0, runningCount - 1)
|
|
104
|
-
processQueue()
|
|
105
|
-
})
|
|
114
|
+
runningCount = Math.max(0, runningCount - 1)
|
|
115
|
+
processQueue()
|
|
116
|
+
})
|
|
106
117
|
}
|
|
107
118
|
}
|
|
108
119
|
|
|
109
|
-
function dispatch(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
options?: BackgroundTaskOptions,
|
|
113
|
-
): string {
|
|
114
|
-
if (typeof options?.max_concurrent === "number" && options.max_concurrent > 0) {
|
|
115
|
-
maxConcurrent = options.max_concurrent;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
taskCount += 1;
|
|
119
|
-
const taskId = `task-${taskCount}`;
|
|
120
|
+
function dispatch(agentName: string, prompt: string, options?: BackgroundTaskOptions): string {
|
|
121
|
+
taskCount += 1
|
|
122
|
+
const taskId = `task-${taskCount}`
|
|
120
123
|
|
|
121
124
|
tasks.set(taskId, {
|
|
122
125
|
status: "queued",
|
|
@@ -124,70 +127,73 @@ export function createBackgroundManager(
|
|
|
124
127
|
prompt,
|
|
125
128
|
options,
|
|
126
129
|
callbacks: new Set<CompletionCallback>(),
|
|
127
|
-
})
|
|
130
|
+
})
|
|
128
131
|
|
|
129
|
-
queue.push(taskId)
|
|
130
|
-
processQueue()
|
|
132
|
+
queue.push(taskId)
|
|
133
|
+
processQueue()
|
|
131
134
|
|
|
132
|
-
return taskId
|
|
135
|
+
return taskId
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
function cancel(taskId: string): void {
|
|
136
|
-
const task = tasks.get(taskId)
|
|
139
|
+
const task = tasks.get(taskId)
|
|
137
140
|
if (!task) {
|
|
138
|
-
return
|
|
141
|
+
return
|
|
139
142
|
}
|
|
140
143
|
|
|
141
|
-
task.status = "cancelled"
|
|
144
|
+
task.status = "cancelled"
|
|
142
145
|
|
|
143
|
-
const queuedTaskIndex = queue.indexOf(taskId)
|
|
146
|
+
const queuedTaskIndex = queue.indexOf(taskId)
|
|
144
147
|
if (queuedTaskIndex >= 0) {
|
|
145
|
-
queue.splice(queuedTaskIndex, 1)
|
|
148
|
+
queue.splice(queuedTaskIndex, 1)
|
|
146
149
|
}
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
function getResult(taskId: string): Promise<unknown> {
|
|
150
|
-
const task = tasks.get(taskId)
|
|
153
|
+
const task = tasks.get(taskId)
|
|
151
154
|
if (!task || task.status !== "completed") {
|
|
152
|
-
return Promise.resolve(undefined)
|
|
155
|
+
return Promise.resolve(undefined)
|
|
153
156
|
}
|
|
154
157
|
|
|
155
|
-
return Promise.resolve(task.result)
|
|
158
|
+
return Promise.resolve(task.result)
|
|
156
159
|
}
|
|
157
160
|
|
|
158
|
-
function onComplete(
|
|
161
|
+
function onComplete(
|
|
162
|
+
taskIdOrCallback: string | CompletionCallback,
|
|
163
|
+
callback?: CompletionCallback,
|
|
164
|
+
): void {
|
|
159
165
|
if (typeof taskIdOrCallback === "function") {
|
|
160
|
-
globalCallbacks.add(taskIdOrCallback)
|
|
161
|
-
return
|
|
166
|
+
globalCallbacks.add(taskIdOrCallback)
|
|
167
|
+
return
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
if (!callback) {
|
|
165
|
-
return
|
|
171
|
+
return
|
|
166
172
|
}
|
|
167
173
|
|
|
168
|
-
const task = tasks.get(taskIdOrCallback)
|
|
174
|
+
const task = tasks.get(taskIdOrCallback)
|
|
169
175
|
if (!task) {
|
|
170
|
-
return
|
|
176
|
+
return
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
if (task.status === "completed") {
|
|
174
|
-
callback
|
|
175
|
-
return
|
|
180
|
+
safeInvokeCallback(callback, taskIdOrCallback, task.result)
|
|
181
|
+
return
|
|
176
182
|
}
|
|
177
183
|
|
|
178
|
-
task.callbacks.add(callback)
|
|
184
|
+
task.callbacks.add(callback)
|
|
179
185
|
}
|
|
180
186
|
|
|
181
187
|
function getActiveCount(): number {
|
|
182
|
-
let activeCount = 0
|
|
188
|
+
let activeCount = 0
|
|
183
189
|
|
|
184
190
|
for (const task of tasks.values()) {
|
|
185
191
|
if (task.status === "queued" || task.status === "running") {
|
|
186
|
-
activeCount += 1
|
|
192
|
+
activeCount += 1
|
|
187
193
|
}
|
|
188
194
|
}
|
|
189
195
|
|
|
190
|
-
return activeCount
|
|
196
|
+
return activeCount
|
|
191
197
|
}
|
|
192
198
|
|
|
193
199
|
return {
|
|
@@ -196,5 +202,5 @@ export function createBackgroundManager(
|
|
|
196
202
|
getResult,
|
|
197
203
|
onComplete,
|
|
198
204
|
getActiveCount,
|
|
199
|
-
}
|
|
205
|
+
}
|
|
200
206
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./background-manager"
|
|
1
|
+
export * from "./background-manager"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { AuditState } from "../../state/types"
|
|
2
1
|
import { createLogger } from "../../shared/logger"
|
|
2
|
+
import type { AuditState } from "../../state/types"
|
|
3
3
|
|
|
4
4
|
const DEFAULT_MAX_TOKENS = 200_000
|
|
5
|
-
const REMINDER_THRESHOLD = 0.
|
|
5
|
+
const REMINDER_THRESHOLD = 0.7
|
|
6
6
|
const COMPACTION_THRESHOLD = 0.85
|
|
7
7
|
|
|
8
8
|
export interface ContextMonitorConfig {
|
|
@@ -19,7 +19,7 @@ export function createContextMonitor(config: ContextMonitorConfig = {}) {
|
|
|
19
19
|
|
|
20
20
|
function getContextStatus(
|
|
21
21
|
systemText: string,
|
|
22
|
-
|
|
22
|
+
_auditState: AuditState | null,
|
|
23
23
|
): { usage: number; reminder: string | null; shouldCompact: boolean } {
|
|
24
24
|
const tokens = estimateTokens(systemText)
|
|
25
25
|
const usage = tokens / maxTokens
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createContextMonitor } from "./context-monitor"
|
|
2
1
|
export type { ContextMonitorConfig } from "./context-monitor"
|
|
3
|
-
export {
|
|
2
|
+
export { createContextMonitor } from "./context-monitor"
|
|
4
3
|
export type { TruncatorConfig } from "./tool-output-truncator"
|
|
4
|
+
export { createToolOutputTruncator } from "./tool-output-truncator"
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import type { AuditState } from "../../state/types"
|
|
2
1
|
import type { AuditStateManager } from "../../managers/types"
|
|
3
2
|
import { createLogger } from "../../shared/logger"
|
|
3
|
+
import type { AuditState } from "../../state/types"
|
|
4
4
|
|
|
5
|
-
export function createSessionRecoveryHandler(
|
|
6
|
-
auditStateManager: AuditStateManager,
|
|
7
|
-
) {
|
|
5
|
+
export function createSessionRecoveryHandler(auditStateManager: AuditStateManager) {
|
|
8
6
|
const logger = createLogger()
|
|
9
7
|
|
|
10
8
|
return async (event: {
|