patchwork-os 0.2.0-alpha.35 → 0.2.0-alpha.36
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/README.md +70 -15
- package/dist/activityLog.d.ts +49 -0
- package/dist/activityLog.js +78 -0
- package/dist/activityLog.js.map +1 -1
- package/dist/approvalHttp.d.ts +25 -0
- package/dist/approvalHttp.js +74 -18
- package/dist/approvalHttp.js.map +1 -1
- package/dist/approvalInsights.d.ts +49 -0
- package/dist/approvalInsights.js +97 -0
- package/dist/approvalInsights.js.map +1 -0
- package/dist/approvalQueue.d.ts +11 -0
- package/dist/approvalQueue.js +80 -1
- package/dist/approvalQueue.js.map +1 -1
- package/dist/approvalSignals.d.ts +124 -0
- package/dist/approvalSignals.js +512 -0
- package/dist/approvalSignals.js.map +1 -0
- package/dist/automation.d.ts +37 -0
- package/dist/automation.js +105 -61
- package/dist/automation.js.map +1 -1
- package/dist/automationSuggestions.d.ts +79 -0
- package/dist/automationSuggestions.js +150 -0
- package/dist/automationSuggestions.js.map +1 -0
- package/dist/bridge.js +46 -0
- package/dist/bridge.js.map +1 -1
- package/dist/ccPermissions.d.ts +15 -0
- package/dist/ccPermissions.js +15 -0
- package/dist/ccPermissions.js.map +1 -1
- package/dist/claudeDriver.js +74 -16
- package/dist/claudeDriver.js.map +1 -1
- package/dist/commands/patchworkInit.d.ts +8 -0
- package/dist/commands/patchworkInit.js +41 -5
- package/dist/commands/patchworkInit.js.map +1 -1
- package/dist/commands/recipe.d.ts +20 -0
- package/dist/commands/recipe.js +194 -5
- package/dist/commands/recipe.js.map +1 -1
- package/dist/commands/recipeInstall.js +93 -4
- package/dist/commands/recipeInstall.js.map +1 -1
- package/dist/commands/tracesExport.d.ts +83 -0
- package/dist/commands/tracesExport.js +269 -0
- package/dist/commands/tracesExport.js.map +1 -0
- package/dist/commands/tracesImport.d.ts +56 -0
- package/dist/commands/tracesImport.js +161 -0
- package/dist/commands/tracesImport.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +9 -1
- package/dist/config.js.map +1 -1
- package/dist/connectorRoutes.d.ts +43 -0
- package/dist/connectorRoutes.js +1023 -0
- package/dist/connectorRoutes.js.map +1 -0
- package/dist/connectors/asana.js +6 -7
- package/dist/connectors/asana.js.map +1 -1
- package/dist/connectors/baseConnector.d.ts +20 -0
- package/dist/connectors/baseConnector.js +45 -4
- package/dist/connectors/baseConnector.js.map +1 -1
- package/dist/connectors/discord.js +6 -7
- package/dist/connectors/discord.js.map +1 -1
- package/dist/connectors/gmail.js +39 -10
- package/dist/connectors/gmail.js.map +1 -1
- package/dist/connectors/googleCalendar.js +36 -10
- package/dist/connectors/googleCalendar.js.map +1 -1
- package/dist/connectors/googleDrive.js +22 -6
- package/dist/connectors/googleDrive.js.map +1 -1
- package/dist/connectors/linear.js +2 -2
- package/dist/connectors/linear.js.map +1 -1
- package/dist/connectors/mcpOAuth.js +26 -2
- package/dist/connectors/mcpOAuth.js.map +1 -1
- package/dist/connectors/oauthStateStore.d.ts +31 -0
- package/dist/connectors/oauthStateStore.js +52 -0
- package/dist/connectors/oauthStateStore.js.map +1 -0
- package/dist/connectors/slack.d.ts +15 -0
- package/dist/connectors/slack.js +54 -4
- package/dist/connectors/slack.js.map +1 -1
- package/dist/connectors/tokenStorage.js +27 -2
- package/dist/connectors/tokenStorage.js.map +1 -1
- package/dist/connectors/zendesk.js +19 -1
- package/dist/connectors/zendesk.js.map +1 -1
- package/dist/cors.d.ts +10 -0
- package/dist/cors.js +29 -0
- package/dist/cors.js.map +1 -0
- package/dist/decisionReplay.d.ts +72 -0
- package/dist/decisionReplay.js +92 -0
- package/dist/decisionReplay.js.map +1 -0
- package/dist/decisionTraceLog.d.ts +6 -0
- package/dist/decisionTraceLog.js +54 -2
- package/dist/decisionTraceLog.js.map +1 -1
- package/dist/fp/automationInterpreter.js +25 -21
- package/dist/fp/automationInterpreter.js.map +1 -1
- package/dist/fp/automationState.js +4 -1
- package/dist/fp/automationState.js.map +1 -1
- package/dist/fp/policyParser.js +4 -1
- package/dist/fp/policyParser.js.map +1 -1
- package/dist/inboxRoutes.d.ts +22 -0
- package/dist/inboxRoutes.js +114 -0
- package/dist/inboxRoutes.js.map +1 -0
- package/dist/index.js +479 -17
- package/dist/index.js.map +1 -1
- package/dist/mcpRoutes.d.ts +37 -0
- package/dist/mcpRoutes.js +76 -0
- package/dist/mcpRoutes.js.map +1 -0
- package/dist/oauth.d.ts +3 -0
- package/dist/oauth.js +151 -26
- package/dist/oauth.js.map +1 -1
- package/dist/oauthRoutes.d.ts +32 -0
- package/dist/oauthRoutes.js +124 -0
- package/dist/oauthRoutes.js.map +1 -0
- package/dist/orchestrator/orchestratorBridge.js +2 -2
- package/dist/orchestrator/orchestratorBridge.js.map +1 -1
- package/dist/patchworkConfig.d.ts +7 -0
- package/dist/patchworkConfig.js.map +1 -1
- package/dist/pluginLoader.d.ts +12 -0
- package/dist/pluginLoader.js +43 -4
- package/dist/pluginLoader.js.map +1 -1
- package/dist/pluginWatcher.js +8 -3
- package/dist/pluginWatcher.js.map +1 -1
- package/dist/preToolUseHook.d.ts +12 -0
- package/dist/preToolUseHook.js +23 -0
- package/dist/preToolUseHook.js.map +1 -1
- package/dist/recipeOrchestration.d.ts +1 -0
- package/dist/recipeOrchestration.js +173 -13
- package/dist/recipeOrchestration.js.map +1 -1
- package/dist/recipeRoutes.d.ts +154 -0
- package/dist/recipeRoutes.js +1098 -0
- package/dist/recipeRoutes.js.map +1 -0
- package/dist/recipes/chainedRunner.d.ts +15 -0
- package/dist/recipes/chainedRunner.js +73 -8
- package/dist/recipes/chainedRunner.js.map +1 -1
- package/dist/recipes/compiler.js +3 -3
- package/dist/recipes/compiler.js.map +1 -1
- package/dist/recipes/installer.js +3 -3
- package/dist/recipes/installer.js.map +1 -1
- package/dist/recipes/migrationWarnings.d.ts +12 -0
- package/dist/recipes/migrationWarnings.js +44 -0
- package/dist/recipes/migrationWarnings.js.map +1 -0
- package/dist/recipes/resolveRecipePath.d.ts +69 -0
- package/dist/recipes/resolveRecipePath.js +202 -0
- package/dist/recipes/resolveRecipePath.js.map +1 -0
- package/dist/recipes/tools/file.d.ts +6 -0
- package/dist/recipes/tools/file.js +12 -8
- package/dist/recipes/tools/file.js.map +1 -1
- package/dist/recipes/tools/index.d.ts +2 -0
- package/dist/recipes/tools/index.js +2 -0
- package/dist/recipes/tools/index.js.map +1 -1
- package/dist/recipes/tools/jira.d.ts +14 -0
- package/dist/recipes/tools/jira.js +369 -0
- package/dist/recipes/tools/jira.js.map +1 -0
- package/dist/recipes/tools/linear.js +6 -3
- package/dist/recipes/tools/linear.js.map +1 -1
- package/dist/recipes/tools/sentry.d.ts +12 -0
- package/dist/recipes/tools/sentry.js +73 -0
- package/dist/recipes/tools/sentry.js.map +1 -0
- package/dist/recipes/tools/slack.js +7 -3
- package/dist/recipes/tools/slack.js.map +1 -1
- package/dist/recipes/validation.js +83 -14
- package/dist/recipes/validation.js.map +1 -1
- package/dist/recipes/yamlRunner.d.ts +7 -0
- package/dist/recipes/yamlRunner.js +107 -13
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/recipesHttp.d.ts +44 -1
- package/dist/recipesHttp.js +168 -15
- package/dist/recipesHttp.js.map +1 -1
- package/dist/runLog.d.ts +14 -0
- package/dist/runLog.js +88 -4
- package/dist/runLog.js.map +1 -1
- package/dist/schemas/dry-run-plan.v1.json +139 -0
- package/dist/schemas/recipe.v1.json +684 -0
- package/dist/server.d.ts +71 -10
- package/dist/server.js +363 -1703
- package/dist/server.js.map +1 -1
- package/dist/ssrfGuard.d.ts +54 -0
- package/dist/ssrfGuard.js +122 -0
- package/dist/ssrfGuard.js.map +1 -0
- package/dist/streamableHttp.d.ts +8 -0
- package/dist/streamableHttp.js +112 -21
- package/dist/streamableHttp.js.map +1 -1
- package/dist/tools/getDocumentSymbols.d.ts +24 -0
- package/dist/tools/getDocumentSymbols.js +74 -8
- package/dist/tools/getDocumentSymbols.js.map +1 -1
- package/dist/tools/getSecurityAdvisories.js +10 -1
- package/dist/tools/getSecurityAdvisories.js.map +1 -1
- package/dist/tools/getSessionUsage.d.ts +3 -0
- package/dist/tools/getSessionUsage.js +3 -0
- package/dist/tools/getSessionUsage.js.map +1 -1
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +32 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/transaction.d.ts +19 -0
- package/dist/tools/transaction.js +29 -0
- package/dist/tools/transaction.js.map +1 -1
- package/dist/traceEncryption.d.ts +46 -0
- package/dist/traceEncryption.js +124 -0
- package/dist/traceEncryption.js.map +1 -0
- package/dist/transport.d.ts +39 -0
- package/dist/transport.js +88 -8
- package/dist/transport.js.map +1 -1
- package/package.json +4 -2
- package/templates/policies/README.md +72 -0
- package/templates/policies/conservative.json +14 -0
- package/templates/policies/developer.json +14 -0
- package/templates/policies/headless-ci.json +24 -0
- package/templates/policies/personal-assistant.json +15 -0
- package/templates/policies/regulated-industry.json +18 -0
- package/templates/recipes/webhook/README.md +70 -0
- package/templates/recipes/webhook/capture-thought.yaml +26 -0
- package/templates/recipes/webhook/customer-escalation.yaml +49 -0
- package/templates/recipes/webhook/incident-intake.yaml +46 -0
- package/templates/recipes/webhook/meeting-prep.yaml +48 -0
- package/templates/recipes/webhook/morning-brief.yaml +57 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* approvalSignals — passive risk personalization for the approval queue.
|
|
3
|
+
*
|
|
4
|
+
* Computes user-specific signals based on past approval / activity history.
|
|
5
|
+
* These supplement (not replace) the policy-engine `riskSignals` which are
|
|
6
|
+
* computed from the call's CONTENT (params shape, destructive flags, etc.).
|
|
7
|
+
*
|
|
8
|
+
* Personal signals describe the user's RELATIONSHIP to this tool/call:
|
|
9
|
+
* "you approved this 27 times" is a different signal class from "this
|
|
10
|
+
* command contains rm -rf". Both should reach the approval modal.
|
|
11
|
+
*
|
|
12
|
+
* Catalog (from docs/strategic/2026-05-02/memory-ecosystem-report.md §5):
|
|
13
|
+
* three of the twelve heuristics shipped here. Heuristics 4-12 follow.
|
|
14
|
+
*
|
|
15
|
+
* 1. "You approved similar actions N times" — past allow on same tool
|
|
16
|
+
* 2. "You rejected this tool before" — past deny on same tool
|
|
17
|
+
* 3. "First use of this connector" — connector namespace ∩ activity log
|
|
18
|
+
* 5. "Last called T days ago" — gap since most recent call (heuristic 4
|
|
19
|
+
* from the catalog is satisfied by the first_connector_use kind above)
|
|
20
|
+
* 7. "Risk tier escalation" — current tier exceeds the user's typical
|
|
21
|
+
* approved tier across recent decisions
|
|
22
|
+
* 6. "Mirrors a recipe step you trust" — primary param matches a step
|
|
23
|
+
* from a successful past recipe run
|
|
24
|
+
* 8. "Often runs alongside X" — co-occurrence pairing in recent activity
|
|
25
|
+
* 9. "Workspace mismatch" — call from a workspace that has never
|
|
26
|
+
* approved this tool before
|
|
27
|
+
* 10. "Time-of-day anomaly" — call hour outside the user's usual window
|
|
28
|
+
* for this tool (opt-in: gate via `enableTimeOfDayAnomaly`)
|
|
29
|
+
* 11. "Param novelty" — primary param (command/url/pattern) prefix never
|
|
30
|
+
* seen on prior approvals of this tool
|
|
31
|
+
* 12. "Cooldown breach" — same tool fired N+ times in a short window
|
|
32
|
+
*
|
|
33
|
+
* The signals are **transparent**: every signal has a `source` enum so a
|
|
34
|
+
* future "why is this signal here?" UI can link back to the rows that
|
|
35
|
+
* produced it. We do not infer; we count and match. No model, no
|
|
36
|
+
* fine-tuning. Honesty is the value proposition.
|
|
37
|
+
*
|
|
38
|
+
* Privacy: signals are computed locally over local logs. They flow into
|
|
39
|
+
* the approval queue's PendingApproval shape, which is exposed via
|
|
40
|
+
* GET /approvals (bearer-auth-gated) and the SSE stream (same auth).
|
|
41
|
+
* Nothing leaves the machine.
|
|
42
|
+
*/
|
|
43
|
+
import { isConnectorNamespace } from "./recipes/toolRegistry.js";
|
|
44
|
+
/**
|
|
45
|
+
* Threshold under which heuristic 1 ("you approved this N times") does
|
|
46
|
+
* not surface. With < 3 prior approvals the signal is noise — it could
|
|
47
|
+
* be the first three exploratory calls a user always rubber-stamps. ≥ 3
|
|
48
|
+
* is "you have a pattern here."
|
|
49
|
+
*/
|
|
50
|
+
const PRIOR_APPROVALS_THRESHOLD = 3;
|
|
51
|
+
/**
|
|
52
|
+
* Minimum gap before "you haven't used this in a while" surfaces. Under a
|
|
53
|
+
* week the user almost certainly remembers; past a week, the call may
|
|
54
|
+
* deserve a second look. Tunable, not load-bearing — change freely if
|
|
55
|
+
* dashboard feedback says it's noisy or too quiet.
|
|
56
|
+
*/
|
|
57
|
+
const STALE_TOOL_DAYS = 7;
|
|
58
|
+
const STALE_TOOL_MS = STALE_TOOL_DAYS * 24 * 60 * 60 * 1_000;
|
|
59
|
+
/** Bumped severity threshold — past a month away is "have you forgotten what this does" territory. */
|
|
60
|
+
const STALE_TOOL_HIGH_DAYS = 30;
|
|
61
|
+
/**
|
|
62
|
+
* Minimum prior-allow decisions needed to establish a "typical tier"
|
|
63
|
+
* baseline for heuristic 7. Below this we don't claim to know the user's
|
|
64
|
+
* pattern — first few approvals could be anything. 5 is small but enough
|
|
65
|
+
* to make a single outlier not dominate.
|
|
66
|
+
*/
|
|
67
|
+
const TIER_BASELINE_MIN_SAMPLES = 5;
|
|
68
|
+
const TIER_RANK = { low: 0, medium: 1, high: 2 };
|
|
69
|
+
/**
|
|
70
|
+
* Window for co-occurrence detection — 15 minutes lines up with the
|
|
71
|
+
* catalog's heuristic 8 description and is wide enough to capture an
|
|
72
|
+
* "I'm in the middle of doing X" workflow without crossing session
|
|
73
|
+
* boundaries.
|
|
74
|
+
*/
|
|
75
|
+
const COOCCURRENCE_WINDOW_MS = 15 * 60 * 1_000;
|
|
76
|
+
/** Minimum co-occurrence count before the chip surfaces. < 3 is noise. */
|
|
77
|
+
const COOCCURRENCE_MIN_COUNT = 3;
|
|
78
|
+
/**
|
|
79
|
+
* Cooldown-breach window — five minutes is short enough that "fired N
|
|
80
|
+
* times" is a behavioral pattern (a runaway loop, a panicked retry, a
|
|
81
|
+
* stuck recipe) rather than ordinary use. ApprovalQueue.inflightKey
|
|
82
|
+
* already deduplicates concurrent identical requests; this surfaces the
|
|
83
|
+
* cumulative pattern that inflight dedup hides.
|
|
84
|
+
*/
|
|
85
|
+
const COOLDOWN_WINDOW_MS = 5 * 60 * 1_000;
|
|
86
|
+
/** Min repeat count inside the window before the chip surfaces. */
|
|
87
|
+
const COOLDOWN_BREACH_MIN = 3;
|
|
88
|
+
/** Severity bumps to high once the burst is unmistakable. */
|
|
89
|
+
const COOLDOWN_BREACH_HIGH = 6;
|
|
90
|
+
/**
|
|
91
|
+
* Minimum prior calls (any hour) before the time-of-day histogram is
|
|
92
|
+
* trustworthy. Below this we don't claim to know the user's pattern —
|
|
93
|
+
* any single hour could legitimately be the first time the tool runs.
|
|
94
|
+
*/
|
|
95
|
+
const TIME_OF_DAY_BASELINE_MIN = 10;
|
|
96
|
+
/** Most recent calls considered when building the per-hour histogram. */
|
|
97
|
+
const TIME_OF_DAY_LOOKBACK = 200;
|
|
98
|
+
/**
|
|
99
|
+
* Tools whose calls have a single high-signal "primary param" that's
|
|
100
|
+
* worth tracking for novelty. Map value = the param key. Other tools
|
|
101
|
+
* skip h11 entirely — generic approval-modal fields like "specifier"
|
|
102
|
+
* are too noisy to compare across calls.
|
|
103
|
+
*/
|
|
104
|
+
const PARAM_NOVELTY_PRIMARY_KEYS = {
|
|
105
|
+
runCommand: "command",
|
|
106
|
+
Bash: "command",
|
|
107
|
+
sendHttpRequest: "url",
|
|
108
|
+
WebFetch: "url",
|
|
109
|
+
searchAndReplace: "pattern",
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Number of leading characters used as the prefix key for novelty
|
|
113
|
+
* comparison. Long enough to distinguish `git push` from `git pull` but
|
|
114
|
+
* short enough that param tail variation (file paths, sha args) doesn't
|
|
115
|
+
* make every call look novel.
|
|
116
|
+
*/
|
|
117
|
+
const PARAM_NOVELTY_PREFIX_LEN = 24;
|
|
118
|
+
/** Minimum prior calls before the novelty signal is trustworthy. */
|
|
119
|
+
const PARAM_NOVELTY_BASELINE_MIN = 5;
|
|
120
|
+
/**
|
|
121
|
+
* How many recent successful recipe runs we scan for matching step
|
|
122
|
+
* results in heuristic 6. 200 is enough to catch any "you run this
|
|
123
|
+
* recipe daily" pattern without unbounded scan cost.
|
|
124
|
+
*/
|
|
125
|
+
const RECIPE_STEP_TRUST_LOOKBACK = 200;
|
|
126
|
+
/**
|
|
127
|
+
* Minimum number of matching successful runs before we surface the
|
|
128
|
+
* "trusted recipe step" signal. ≥ 2 means "this isn't a one-off."
|
|
129
|
+
*/
|
|
130
|
+
const RECIPE_STEP_TRUST_MIN = 2;
|
|
131
|
+
/**
|
|
132
|
+
* Compute the personal-signal set for an incoming approval request.
|
|
133
|
+
*
|
|
134
|
+
* Pure function over the activity log; no I/O of its own. Tested in
|
|
135
|
+
* isolation by feeding a mock ActivityLog. The activityLog argument is
|
|
136
|
+
* passed positionally rather than wired through deps so the surface
|
|
137
|
+
* stays inspectable.
|
|
138
|
+
*/
|
|
139
|
+
export function computePersonalSignals(input) {
|
|
140
|
+
const { toolName, activityLog, currentTier, currentWorkspace, enableTimeOfDayAnomaly, currentParams, recipeRunLog, } = input;
|
|
141
|
+
if (!toolName)
|
|
142
|
+
return [];
|
|
143
|
+
const signals = [];
|
|
144
|
+
// Heuristic 1: "You approved this N times before."
|
|
145
|
+
// Surface only when count crosses PRIOR_APPROVALS_THRESHOLD so we don't
|
|
146
|
+
// flag every second call. Cap the message at three buckets for legibility.
|
|
147
|
+
const priorApprovals = activityLog.queryApprovalDecisions({
|
|
148
|
+
toolName,
|
|
149
|
+
decision: "allow",
|
|
150
|
+
});
|
|
151
|
+
if (priorApprovals.length >= PRIOR_APPROVALS_THRESHOLD) {
|
|
152
|
+
signals.push({
|
|
153
|
+
kind: "prior_approvals",
|
|
154
|
+
label: priorApprovalsLabel(priorApprovals.length),
|
|
155
|
+
severity: "low",
|
|
156
|
+
source: "approval_history",
|
|
157
|
+
count: priorApprovals.length,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// Heuristic 2: "You rejected this tool before."
|
|
161
|
+
// Any prior rejection is signal — explicit rejections are rare and
|
|
162
|
+
// intentional. Severity scales with how recent and how many.
|
|
163
|
+
const priorRejections = activityLog.queryApprovalDecisions({
|
|
164
|
+
toolName,
|
|
165
|
+
decision: "deny",
|
|
166
|
+
});
|
|
167
|
+
if (priorRejections.length > 0) {
|
|
168
|
+
signals.push({
|
|
169
|
+
kind: "prior_rejection",
|
|
170
|
+
label: priorRejectionLabel(priorRejections.length),
|
|
171
|
+
severity: priorRejections.length >= 2 ? "high" : "medium",
|
|
172
|
+
source: "approval_history",
|
|
173
|
+
count: priorRejections.length,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// Heuristic 3: "First use of this connector" / "first use of this tool".
|
|
177
|
+
// For namespaced tool ids only (`namespace.subtool` shape). Two flavors:
|
|
178
|
+
// - If the namespace is in the connector registry → "first connector use"
|
|
179
|
+
// (high salience: connectors hit external services with credentials).
|
|
180
|
+
// - Otherwise, plain "first use" with low salience.
|
|
181
|
+
// We require a namespaced tool id so we don't flag every CLI tool ever
|
|
182
|
+
// called with no prior history (which would fire on every fresh session).
|
|
183
|
+
const namespace = toolName.split(".")[0];
|
|
184
|
+
if (namespace && namespace !== toolName) {
|
|
185
|
+
const priorInNamespace = activityLog.queryByNamespace(namespace, 50);
|
|
186
|
+
if (priorInNamespace.length === 0) {
|
|
187
|
+
const isConnector = isConnectorNamespace(namespace);
|
|
188
|
+
signals.push({
|
|
189
|
+
kind: isConnector ? "first_connector_use" : "first_tool_use",
|
|
190
|
+
label: isConnector
|
|
191
|
+
? `First use of the ${namespace} connector — credentials and external calls about to fire.`
|
|
192
|
+
: `First use of any ${namespace}.* tool in this workspace.`,
|
|
193
|
+
severity: isConnector ? "high" : "low",
|
|
194
|
+
source: "activity_history",
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Heuristic 5: "Last called T days ago."
|
|
199
|
+
// Surfaces a low/medium signal when the user hasn't called this exact
|
|
200
|
+
// tool in a while. The message gives the user a beat to reconsider —
|
|
201
|
+
// memory of *why* a call was approved fades faster than the data does.
|
|
202
|
+
// No signal at all when there's no prior call (heuristic 3 handles
|
|
203
|
+
// first-use); no signal when the gap is under STALE_TOOL_DAYS.
|
|
204
|
+
const lastCall = activityLog.queryLastToolCall(toolName);
|
|
205
|
+
if (lastCall) {
|
|
206
|
+
const lastMs = Date.parse(lastCall.timestamp);
|
|
207
|
+
const gapMs = Number.isFinite(lastMs) ? Date.now() - lastMs : 0;
|
|
208
|
+
if (gapMs >= STALE_TOOL_MS) {
|
|
209
|
+
const days = Math.floor(gapMs / (24 * 60 * 60 * 1_000));
|
|
210
|
+
signals.push({
|
|
211
|
+
kind: "stale_tool_use",
|
|
212
|
+
label: staleToolLabel(days),
|
|
213
|
+
severity: days >= STALE_TOOL_HIGH_DAYS ? "medium" : "low",
|
|
214
|
+
source: "activity_history",
|
|
215
|
+
count: days,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Heuristic 7: "Risk tier escalation."
|
|
220
|
+
// Compare the incoming call's tier against the user's typical approved
|
|
221
|
+
// tier across recent allow-decisions. If the user usually approves low
|
|
222
|
+
// and this is medium/high — or usually medium and this is high — surface.
|
|
223
|
+
// Cross-tool: the question is "is this user's threshold being exceeded",
|
|
224
|
+
// not "is this tool a step up from this tool's history". The latter is
|
|
225
|
+
// covered by heuristic 1 (prior approvals on the same tool).
|
|
226
|
+
if (currentTier) {
|
|
227
|
+
const recentAllows = activityLog
|
|
228
|
+
.queryApprovalDecisions({ decision: "allow", last: 50 })
|
|
229
|
+
.filter((e) => e.metadata?.tier === "low" ||
|
|
230
|
+
e.metadata?.tier === "medium" ||
|
|
231
|
+
e.metadata?.tier === "high");
|
|
232
|
+
if (recentAllows.length >= TIER_BASELINE_MIN_SAMPLES) {
|
|
233
|
+
// p50 over rank space — sort ranks, pick the middle. Equivalent to
|
|
234
|
+
// median; cheaper than a full distribution and stable on small N.
|
|
235
|
+
const ranks = recentAllows
|
|
236
|
+
.map((e) => TIER_RANK[e.metadata.tier])
|
|
237
|
+
.sort((a, b) => a - b);
|
|
238
|
+
const baselineRank = ranks[Math.floor(ranks.length / 2)] ?? 0;
|
|
239
|
+
const currentRank = TIER_RANK[currentTier];
|
|
240
|
+
if (currentRank > baselineRank) {
|
|
241
|
+
const baselineTier = Object.keys(TIER_RANK).find((t) => TIER_RANK[t] === baselineRank);
|
|
242
|
+
const jump = currentRank - baselineRank;
|
|
243
|
+
signals.push({
|
|
244
|
+
kind: "tier_escalation",
|
|
245
|
+
label: tierEscalationLabel(baselineTier ?? "low", currentTier),
|
|
246
|
+
// jump of 1 (low→med, med→high) = medium; jump of 2 (low→high) = high
|
|
247
|
+
severity: jump >= 2 ? "high" : "medium",
|
|
248
|
+
source: "approval_history",
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Heuristic 8: "Often runs alongside X."
|
|
254
|
+
// Informational chip — when the user calls this tool it tends to run
|
|
255
|
+
// near another tool (within 15min). Surfaces the strongest partner.
|
|
256
|
+
// Distinct from heuristic 1 (history of THIS tool); this signal is
|
|
257
|
+
// about the workflow shape: "you're probably mid-deploy" / "this is
|
|
258
|
+
// your morning-brief sequence". Severity always low — this is context,
|
|
259
|
+
// not warning. Catalog flags this as medium-FP, so we underclaim.
|
|
260
|
+
const pairs = activityLog.coOccurrence(COOCCURRENCE_WINDOW_MS);
|
|
261
|
+
let bestPartner = null;
|
|
262
|
+
for (const { pair, count } of pairs) {
|
|
263
|
+
if (count < COOCCURRENCE_MIN_COUNT)
|
|
264
|
+
continue;
|
|
265
|
+
const [a, b] = pair.split("|");
|
|
266
|
+
const partner = a === toolName ? b : b === toolName ? a : null;
|
|
267
|
+
if (!partner)
|
|
268
|
+
continue;
|
|
269
|
+
// pairs is sorted by count desc, so the first match is the strongest.
|
|
270
|
+
bestPartner = { name: partner, count };
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
if (bestPartner) {
|
|
274
|
+
signals.push({
|
|
275
|
+
kind: "cooccurrence_pattern",
|
|
276
|
+
label: `Often runs alongside ${bestPartner.name} (${bestPartner.count} co-occurrences in your recent activity).`,
|
|
277
|
+
severity: "low",
|
|
278
|
+
source: "activity_history",
|
|
279
|
+
count: bestPartner.count,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
// Heuristic 9: "Workspace mismatch."
|
|
283
|
+
// Surfaces when this tool has been approved (allow OR deny — any
|
|
284
|
+
// human decision counts as a "this workspace has seen this tool"
|
|
285
|
+
// signal) in other workspaces but not in the one the call is coming
|
|
286
|
+
// from. Catches "I just opened a new project and the agent wants to
|
|
287
|
+
// run the same risky tool — fresh workspace, no consent record."
|
|
288
|
+
// Skipped when currentWorkspace is missing (test fixtures, callers
|
|
289
|
+
// without a workspace context).
|
|
290
|
+
if (currentWorkspace) {
|
|
291
|
+
const allDecisions = activityLog.queryApprovalDecisions({ toolName });
|
|
292
|
+
const seenWorkspaces = new Set();
|
|
293
|
+
let priorWithWorkspace = 0;
|
|
294
|
+
for (const e of allDecisions) {
|
|
295
|
+
const ws = e.metadata?.workspace;
|
|
296
|
+
if (typeof ws === "string" && ws.length > 0) {
|
|
297
|
+
seenWorkspaces.add(ws);
|
|
298
|
+
priorWithWorkspace++;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Need ≥ 1 prior decision *with workspace metadata* to claim a
|
|
302
|
+
// baseline. Older rows lacking the field can't tell us anything.
|
|
303
|
+
if (priorWithWorkspace > 0 && !seenWorkspaces.has(currentWorkspace)) {
|
|
304
|
+
signals.push({
|
|
305
|
+
kind: "workspace_mismatch",
|
|
306
|
+
label: workspaceMismatchLabel(seenWorkspaces.size),
|
|
307
|
+
// Catalog says "FP low" — workspace is a strong intent boundary.
|
|
308
|
+
// But it's still informational rather than a hard warning.
|
|
309
|
+
severity: "medium",
|
|
310
|
+
source: "approval_history",
|
|
311
|
+
count: seenWorkspaces.size,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Heuristic 10: "Time-of-day anomaly."
|
|
316
|
+
// Build a 24-bucket hour-of-day histogram for past calls of this tool.
|
|
317
|
+
// If the current hour has zero prior calls AND total prior calls is
|
|
318
|
+
// past the baseline, surface as informational (low severity). Catalog
|
|
319
|
+
// flags this as medium-FP so it stays opt-in and never escalates to
|
|
320
|
+
// medium/high — purely a "huh, that's unusual" chip.
|
|
321
|
+
if (enableTimeOfDayAnomaly) {
|
|
322
|
+
const recent = activityLog.query({
|
|
323
|
+
tool: toolName,
|
|
324
|
+
last: TIME_OF_DAY_LOOKBACK,
|
|
325
|
+
});
|
|
326
|
+
if (recent.length >= TIME_OF_DAY_BASELINE_MIN) {
|
|
327
|
+
const hourCounts = new Array(24).fill(0);
|
|
328
|
+
for (const e of recent) {
|
|
329
|
+
const hr = new Date(e.timestamp).getHours();
|
|
330
|
+
if (Number.isFinite(hr) && hr >= 0 && hr < 24) {
|
|
331
|
+
hourCounts[hr] = (hourCounts[hr] ?? 0) + 1;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const currentHour = new Date().getHours();
|
|
335
|
+
if ((hourCounts[currentHour] ?? 0) === 0) {
|
|
336
|
+
signals.push({
|
|
337
|
+
kind: "time_of_day_anomaly",
|
|
338
|
+
label: timeOfDayLabel(currentHour, recent.length),
|
|
339
|
+
severity: "low",
|
|
340
|
+
source: "activity_history",
|
|
341
|
+
count: recent.length,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Heuristic 11: "Param novelty."
|
|
347
|
+
// For tools with a clear primary string param (runCommand → command,
|
|
348
|
+
// sendHttpRequest → url, etc.), build a Set of past param prefixes
|
|
349
|
+
// from approval_decision metadata. If the current call's prefix isn't
|
|
350
|
+
// in that set AND we have enough baseline, surface as informational.
|
|
351
|
+
// Compares only the leading PARAM_NOVELTY_PREFIX_LEN chars so the
|
|
352
|
+
// tail (file paths, sha args, query params) doesn't make every call
|
|
353
|
+
// look novel.
|
|
354
|
+
const primaryKey = PARAM_NOVELTY_PRIMARY_KEYS[toolName];
|
|
355
|
+
if (primaryKey && currentParams) {
|
|
356
|
+
const currentRaw = currentParams[primaryKey];
|
|
357
|
+
if (typeof currentRaw === "string" && currentRaw.length > 0) {
|
|
358
|
+
const currentPrefix = paramPrefix(currentRaw);
|
|
359
|
+
const priorDecisions = activityLog.queryApprovalDecisions({
|
|
360
|
+
toolName,
|
|
361
|
+
});
|
|
362
|
+
const seenPrefixes = new Set();
|
|
363
|
+
for (const e of priorDecisions) {
|
|
364
|
+
const params = e.metadata?.params;
|
|
365
|
+
if (typeof params !== "object" || params === null)
|
|
366
|
+
continue;
|
|
367
|
+
const v = params[primaryKey];
|
|
368
|
+
if (typeof v === "string" && v.length > 0) {
|
|
369
|
+
seenPrefixes.add(paramPrefix(v));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (seenPrefixes.size >= PARAM_NOVELTY_BASELINE_MIN &&
|
|
373
|
+
!seenPrefixes.has(currentPrefix)) {
|
|
374
|
+
signals.push({
|
|
375
|
+
kind: "param_novelty",
|
|
376
|
+
label: paramNoveltyLabel(primaryKey, seenPrefixes.size),
|
|
377
|
+
severity: "medium",
|
|
378
|
+
source: "approval_history",
|
|
379
|
+
count: seenPrefixes.size,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Heuristic 6: "Mirrors a recipe step you trust."
|
|
385
|
+
// Scan recent SUCCESSFUL recipe runs for a step whose tool matches
|
|
386
|
+
// and whose resolvedParams primary key shares the same 24-char prefix
|
|
387
|
+
// as the current call. If we find ≥ RECIPE_STEP_TRUST_MIN such runs,
|
|
388
|
+
// surface the recipe name and the count. Reuses h11's primary-key
|
|
389
|
+
// map so "this tool needs a primary param to compare" is one decision.
|
|
390
|
+
// Trust signal — low severity (informational reassurance, not warning).
|
|
391
|
+
const recipePrimaryKey = PARAM_NOVELTY_PRIMARY_KEYS[toolName];
|
|
392
|
+
if (recipeRunLog && recipePrimaryKey && currentParams) {
|
|
393
|
+
const currentRaw = currentParams[recipePrimaryKey];
|
|
394
|
+
if (typeof currentRaw === "string" && currentRaw.length > 0) {
|
|
395
|
+
const currentPrefix = paramPrefix(currentRaw);
|
|
396
|
+
const recentRuns = recipeRunLog.query({
|
|
397
|
+
status: "done",
|
|
398
|
+
limit: RECIPE_STEP_TRUST_LOOKBACK,
|
|
399
|
+
});
|
|
400
|
+
const matchingRecipes = new Map();
|
|
401
|
+
for (const run of recentRuns) {
|
|
402
|
+
if (!run.stepResults)
|
|
403
|
+
continue;
|
|
404
|
+
for (const step of run.stepResults) {
|
|
405
|
+
if (step.tool !== toolName || step.status !== "ok")
|
|
406
|
+
continue;
|
|
407
|
+
const sp = step.resolvedParams;
|
|
408
|
+
if (typeof sp !== "object" || sp === null)
|
|
409
|
+
continue;
|
|
410
|
+
const v = sp[recipePrimaryKey];
|
|
411
|
+
if (typeof v === "string" &&
|
|
412
|
+
v.length > 0 &&
|
|
413
|
+
paramPrefix(v) === currentPrefix) {
|
|
414
|
+
matchingRecipes.set(run.recipeName, (matchingRecipes.get(run.recipeName) ?? 0) + 1);
|
|
415
|
+
// One match per run is enough — same step run repeatedly in
|
|
416
|
+
// a single recipe execution doesn't add weight.
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
let totalMatches = 0;
|
|
422
|
+
let topRecipe = null;
|
|
423
|
+
let topCount = 0;
|
|
424
|
+
for (const [name, count] of matchingRecipes) {
|
|
425
|
+
totalMatches += count;
|
|
426
|
+
if (count > topCount) {
|
|
427
|
+
topCount = count;
|
|
428
|
+
topRecipe = name;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (totalMatches >= RECIPE_STEP_TRUST_MIN && topRecipe) {
|
|
432
|
+
signals.push({
|
|
433
|
+
kind: "recipe_step_trust",
|
|
434
|
+
label: recipeStepTrustLabel(topRecipe, totalMatches),
|
|
435
|
+
severity: "low",
|
|
436
|
+
source: "recipe_run_log",
|
|
437
|
+
count: totalMatches,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// Heuristic 12: "Cooldown breach."
|
|
443
|
+
// Same tool fired N+ times within a short window — pattern of a runaway
|
|
444
|
+
// loop, panicked retry, or stuck recipe. Distinct from heuristic 1
|
|
445
|
+
// (long-tail history): h1 says "you usually approve this", h12 says
|
|
446
|
+
// "you're approving this RIGHT NOW more than usual." Approvals modal
|
|
447
|
+
// should show both when both fire.
|
|
448
|
+
const recentCalls = activityLog.query({ tool: toolName, last: 50 });
|
|
449
|
+
const cutoff = Date.now() - COOLDOWN_WINDOW_MS;
|
|
450
|
+
const burstCount = recentCalls.filter((e) => Date.parse(e.timestamp) >= cutoff).length;
|
|
451
|
+
if (burstCount >= COOLDOWN_BREACH_MIN) {
|
|
452
|
+
signals.push({
|
|
453
|
+
kind: "cooldown_breach",
|
|
454
|
+
label: cooldownBreachLabel(burstCount),
|
|
455
|
+
severity: burstCount >= COOLDOWN_BREACH_HIGH ? "high" : "medium",
|
|
456
|
+
source: "activity_history",
|
|
457
|
+
count: burstCount,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
return signals;
|
|
461
|
+
}
|
|
462
|
+
function workspaceMismatchLabel(otherCount) {
|
|
463
|
+
if (otherCount === 1) {
|
|
464
|
+
return "Approved in a different workspace before — first time you've allowed it here.";
|
|
465
|
+
}
|
|
466
|
+
return `Approved in ${otherCount} other workspaces before — first time you've allowed it here.`;
|
|
467
|
+
}
|
|
468
|
+
function paramPrefix(s) {
|
|
469
|
+
// Trim leading whitespace so " git push" and "git push" hash the same.
|
|
470
|
+
return s.trimStart().slice(0, PARAM_NOVELTY_PREFIX_LEN);
|
|
471
|
+
}
|
|
472
|
+
function recipeStepTrustLabel(recipeName, totalMatches) {
|
|
473
|
+
return `Matches a step in your ${recipeName} recipe (${totalMatches} successful runs).`;
|
|
474
|
+
}
|
|
475
|
+
function paramNoveltyLabel(paramKey, baselineSize) {
|
|
476
|
+
return `Novel ${paramKey} prefix — across ${baselineSize} prior approvals you've never run this exact shape.`;
|
|
477
|
+
}
|
|
478
|
+
function timeOfDayLabel(hour, baselineCount) {
|
|
479
|
+
const hh = hour.toString().padStart(2, "0");
|
|
480
|
+
return `First call at ${hh}:00 — outside your usual hours for this tool (across ${baselineCount} prior calls).`;
|
|
481
|
+
}
|
|
482
|
+
function cooldownBreachLabel(count) {
|
|
483
|
+
return `Called ${count} times in the last 5 minutes — possible runaway loop or panicked retry.`;
|
|
484
|
+
}
|
|
485
|
+
function tierEscalationLabel(baseline, current) {
|
|
486
|
+
return `You usually approve ${baseline}-tier calls — this one is ${current}.`;
|
|
487
|
+
}
|
|
488
|
+
function priorApprovalsLabel(count) {
|
|
489
|
+
if (count >= 100) {
|
|
490
|
+
return `You've approved this tool ${count}+ times — well-trusted in your workflow.`;
|
|
491
|
+
}
|
|
492
|
+
if (count >= 20) {
|
|
493
|
+
return `You've approved this tool ${count} times before.`;
|
|
494
|
+
}
|
|
495
|
+
return `You've approved this tool ${count} times before.`;
|
|
496
|
+
}
|
|
497
|
+
function staleToolLabel(days) {
|
|
498
|
+
if (days >= 365)
|
|
499
|
+
return `Last called over a year ago (${days} days).`;
|
|
500
|
+
if (days >= 30) {
|
|
501
|
+
const months = Math.floor(days / 30);
|
|
502
|
+
return `Last called ${months === 1 ? "a month" : `${months} months`} ago — context may have changed since.`;
|
|
503
|
+
}
|
|
504
|
+
return `Last called ${days} days ago.`;
|
|
505
|
+
}
|
|
506
|
+
function priorRejectionLabel(count) {
|
|
507
|
+
if (count === 1) {
|
|
508
|
+
return "You rejected this tool once before — context may have changed since.";
|
|
509
|
+
}
|
|
510
|
+
return `You've rejected this tool ${count} times before — recurring pattern of caution.`;
|
|
511
|
+
}
|
|
512
|
+
//# sourceMappingURL=approvalSignals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approvalSignals.js","sourceRoot":"","sources":["../src/approvalSignals.ts"],"names":[],"mappings":"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"}
|
package/dist/automation.d.ts
CHANGED
|
@@ -506,6 +506,43 @@ export declare class AutomationHooks {
|
|
|
506
506
|
/** Last interpreter run promise — allows tests to await completion. */
|
|
507
507
|
private _lastRunPromise;
|
|
508
508
|
constructor(policy: AutomationPolicy, orchestrator: ClaudeOrchestrator, log: (msg: string) => void, _extensionClient?: ExtensionClient, _workspace?: string);
|
|
509
|
+
/**
|
|
510
|
+
* Tracks whether `_lastRunPromise` represents a still-running interpreter
|
|
511
|
+
* call. When false, we know the prior promise is settled and can start the
|
|
512
|
+
* next run synchronously (preserving start-sync semantics tests depend on).
|
|
513
|
+
* When true, we chain via `.then()` so the new run reads the post-state of
|
|
514
|
+
* the prior one — fixing the race where two concurrent events both observed
|
|
515
|
+
* stale `_automationState` and the second writer clobbered the first.
|
|
516
|
+
*/
|
|
517
|
+
private _chainBusy;
|
|
518
|
+
/**
|
|
519
|
+
* Enqueue an interpreter run, serializing it after any prior in-flight run.
|
|
520
|
+
*
|
|
521
|
+
* Concurrent events (e.g. a file save and a diagnostic update in the same
|
|
522
|
+
* tick) used to overwrite `_lastRunPromise`, so both runs read the same
|
|
523
|
+
* stale `_automationState` and the second writer's `updatedState` clobbered
|
|
524
|
+
* the first's. Cooldown/dedup writes from one event were silently lost;
|
|
525
|
+
* orphaned tasks accumulated in `activeTasks`.
|
|
526
|
+
*
|
|
527
|
+
* Chain through `_lastRunPromise` so each run sees the post-state of the
|
|
528
|
+
* previous one. We start the next run synchronously when the chain is idle
|
|
529
|
+
* (preserves the contract that `_runInterpreter` body executes up to its
|
|
530
|
+
* first await before this call returns); only when a run is in flight do
|
|
531
|
+
* we defer via `.then()`.
|
|
532
|
+
*/
|
|
533
|
+
private _enqueueRun;
|
|
534
|
+
/**
|
|
535
|
+
* Enqueue a synchronous state mutation to run after any in-flight
|
|
536
|
+
* interpreter call. Without this, handler-side `_automationState =
|
|
537
|
+
* setLatestDiagnostics(...)` writes done synchronously between
|
|
538
|
+
* `_enqueueRun` calls would be overwritten when the first run's
|
|
539
|
+
* `result.value.updatedState` writeback fires (the writeback replaces
|
|
540
|
+
* the full state, including the handler's incremental change).
|
|
541
|
+
*
|
|
542
|
+
* When the chain is idle, run the mutation synchronously to preserve
|
|
543
|
+
* test ergonomics that assume state is observable on the next line.
|
|
544
|
+
*/
|
|
545
|
+
private _enqueueMutation;
|
|
509
546
|
private _runInterpreter;
|
|
510
547
|
/**
|
|
511
548
|
* Returns a Promise that resolves once all in-flight interpreter runs finish.
|