auto-mode 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/classify-edit-D8GC6b91.js +17848 -0
- package/dist/cli.js +320 -10
- package/dist/get-edit-fields-BtAvHIsq.js +20 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{render-verdict-1xtCI2is.js → render-verdict-FhSKxlO9.js} +804 -602
- package/mods/auto-mode/hooks/register.claude-check.ts +97 -0
- package/mods/auto-mode/hooks/register.ts +36 -2
- package/mods/auto-mode/hooks/types.ts +13 -2
- package/package.json +2 -1
|
@@ -1127,3 +1127,100 @@ test('it identifies a subprocess failure without copying the exception', async (
|
|
|
1127
1127
|
'auto-mode action unavailable: manual approval retained; subprocess failure',
|
|
1128
1128
|
]);
|
|
1129
1129
|
});
|
|
1130
|
+
|
|
1131
|
+
test('it records a finished scope-creating Bash call with its result', async ($, on) => {
|
|
1132
|
+
let invocation: unknown;
|
|
1133
|
+
let stdin = '';
|
|
1134
|
+
|
|
1135
|
+
on('session.cwd', () => ({ value: '/repo' }));
|
|
1136
|
+
on('classic.SessionStart', () => ({}));
|
|
1137
|
+
|
|
1138
|
+
on('tool.call', () => ({
|
|
1139
|
+
result: { stdout: 'https://github.com/dev/app/pull/3\n', stderr: '' },
|
|
1140
|
+
text: 'https://github.com/dev/app/pull/3\n',
|
|
1141
|
+
}));
|
|
1142
|
+
|
|
1143
|
+
on('process.run', (_api, e) => {
|
|
1144
|
+
invocation = e;
|
|
1145
|
+
stdin = e.init?.stdin ?? '';
|
|
1146
|
+
|
|
1147
|
+
return {
|
|
1148
|
+
value: {
|
|
1149
|
+
exitCode: 0,
|
|
1150
|
+
stdout: '',
|
|
1151
|
+
stderr: '',
|
|
1152
|
+
isStdoutTruncated: false,
|
|
1153
|
+
isStderrTruncated: false,
|
|
1154
|
+
},
|
|
1155
|
+
};
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
// oxlint-disable-next-line new-cap -- The host event API retains its event spelling.
|
|
1159
|
+
await $.classic.SessionStart({
|
|
1160
|
+
source: 'startup',
|
|
1161
|
+
session_id: 'session-1',
|
|
1162
|
+
cwd: '/repo',
|
|
1163
|
+
transcript_path: '/repo/transcript.jsonl',
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
const result = await $.tool.call({
|
|
1167
|
+
tool: 'Bash',
|
|
1168
|
+
tool_use_id: 'call-1',
|
|
1169
|
+
command: 'gh pr create --fill',
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
expect(result).toMatchObject({ text: 'https://github.com/dev/app/pull/3\n' });
|
|
1173
|
+
|
|
1174
|
+
expect(invocation).toMatchObject({
|
|
1175
|
+
argv: ['auto-mode', 'record'],
|
|
1176
|
+
init: { timeoutMs: 8000, stdin: expect.any(String) },
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
expect(JSON.parse(stdin)).toStrictEqual({
|
|
1180
|
+
sessionID: 'session-1',
|
|
1181
|
+
cwd: '/repo',
|
|
1182
|
+
startedAt: expect.any(Number),
|
|
1183
|
+
command: 'gh pr create --fill',
|
|
1184
|
+
resultText: 'https://github.com/dev/app/pull/3\n',
|
|
1185
|
+
});
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
test('it records nothing for a Bash call that cannot create scope or a call that was denied', async ($, on) => {
|
|
1189
|
+
let calls = 0;
|
|
1190
|
+
|
|
1191
|
+
on('session.cwd', () => ({ value: '/repo' }));
|
|
1192
|
+
on('classic.SessionStart', () => ({}));
|
|
1193
|
+
|
|
1194
|
+
on('tool.call', (_api, e) =>
|
|
1195
|
+
e.command === 'git checkout -b x'
|
|
1196
|
+
? { deny: 'no' }
|
|
1197
|
+
: { result: { stdout: '', stderr: '' }, text: '' },
|
|
1198
|
+
);
|
|
1199
|
+
|
|
1200
|
+
on('process.run', () => {
|
|
1201
|
+
calls += 1;
|
|
1202
|
+
|
|
1203
|
+
return {
|
|
1204
|
+
value: {
|
|
1205
|
+
exitCode: 0,
|
|
1206
|
+
stdout: '',
|
|
1207
|
+
stderr: '',
|
|
1208
|
+
isStdoutTruncated: false,
|
|
1209
|
+
isStderrTruncated: false,
|
|
1210
|
+
},
|
|
1211
|
+
};
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
// oxlint-disable-next-line new-cap -- The host event API retains its event spelling.
|
|
1215
|
+
await $.classic.SessionStart({
|
|
1216
|
+
source: 'startup',
|
|
1217
|
+
session_id: 'session-1',
|
|
1218
|
+
cwd: '/repo',
|
|
1219
|
+
transcript_path: '/repo/transcript.jsonl',
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
await $.tool.call({ tool: 'Bash', tool_use_id: 'call-1', command: 'bun test' });
|
|
1223
|
+
await $.tool.call({ tool: 'Bash', tool_use_id: 'call-2', command: 'git checkout -b x' });
|
|
1224
|
+
|
|
1225
|
+
expect(calls).toBe(0);
|
|
1226
|
+
});
|
|
@@ -2,6 +2,15 @@ import { buildPromptContext } from './build-prompt-context.ts';
|
|
|
2
2
|
import { parseDecision } from './parse-decision.ts';
|
|
3
3
|
import type { ModOn, ModOptions, PromptContext } from './types.ts';
|
|
4
4
|
|
|
5
|
+
// Commands that can create a worktree, a branch, or a PR. Any other call skips
|
|
6
|
+
// the record subprocess, so ordinary calls pay nothing for the session scope.
|
|
7
|
+
const SCOPE_COMMAND =
|
|
8
|
+
/\bgit\s[^\n]*?\b(?:worktree\s+add|checkout|switch|branch)\b|\bgh\s+pr\s+create\b/u;
|
|
9
|
+
|
|
10
|
+
// The record may look up a created PR's head branch with gh, which the CLI
|
|
11
|
+
// bounds at 5 seconds.
|
|
12
|
+
const RECORD_TIMEOUT_MS = 8000;
|
|
13
|
+
|
|
5
14
|
export function register(on: ModOn, options: ModOptions): void {
|
|
6
15
|
let sessionID: string | null = null;
|
|
7
16
|
|
|
@@ -52,18 +61,43 @@ export function register(on: ModOn, options: ModOptions): void {
|
|
|
52
61
|
return result;
|
|
53
62
|
});
|
|
54
63
|
|
|
55
|
-
on('tool.call', async (
|
|
64
|
+
on('tool.call', async ($, e, next) => {
|
|
56
65
|
if (e.tool_use_id === undefined) {
|
|
57
66
|
return next(e);
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
activeCalls.set(e.tool_use_id, e.agentId ?? null);
|
|
61
70
|
|
|
71
|
+
const command =
|
|
72
|
+
e.tool === 'Bash' && typeof e.command === 'string' && SCOPE_COMMAND.test(e.command)
|
|
73
|
+
? e.command
|
|
74
|
+
: null;
|
|
75
|
+
|
|
76
|
+
const startedAt = Date.now();
|
|
77
|
+
const cwd = command === null || sessionID === null ? null : await $.session.cwd();
|
|
78
|
+
let result;
|
|
79
|
+
|
|
62
80
|
try {
|
|
63
|
-
|
|
81
|
+
result = await next(e);
|
|
64
82
|
} finally {
|
|
65
83
|
activeCalls.delete(e.tool_use_id);
|
|
66
84
|
}
|
|
85
|
+
|
|
86
|
+
if (command !== null && cwd !== null && sessionID !== null && result.deny === undefined) {
|
|
87
|
+
const executable = typeof options.command === 'string' ? options.command : 'auto-mode';
|
|
88
|
+
const request = { sessionID, cwd, startedAt, command, resultText: result.text ?? '' };
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await $.process.run([executable, 'record'], {
|
|
92
|
+
timeoutMs: RECORD_TIMEOUT_MS,
|
|
93
|
+
stdin: JSON.stringify(request),
|
|
94
|
+
});
|
|
95
|
+
} catch {
|
|
96
|
+
$.ui.log('auto-mode: session scope not recorded; subprocess failure', { to: 'debug' });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result;
|
|
67
101
|
});
|
|
68
102
|
|
|
69
103
|
on('tool.check', async ($, e, next) => {
|
|
@@ -35,6 +35,14 @@ interface CallInput {
|
|
|
35
35
|
readonly tool: string;
|
|
36
36
|
readonly tool_use_id?: string;
|
|
37
37
|
readonly agentId?: string;
|
|
38
|
+
readonly command?: unknown;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface CallResult {
|
|
42
|
+
readonly result?: unknown;
|
|
43
|
+
readonly deny?: string;
|
|
44
|
+
readonly text?: string;
|
|
45
|
+
readonly isError?: true;
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
export interface UserTask {
|
|
@@ -70,7 +78,7 @@ interface ModEvents {
|
|
|
70
78
|
readonly input: SpawnInput;
|
|
71
79
|
readonly result: { readonly agentId?: string; readonly model?: string; readonly deny?: string };
|
|
72
80
|
};
|
|
73
|
-
readonly 'tool.call': { readonly input: CallInput; readonly result:
|
|
81
|
+
readonly 'tool.call': { readonly input: CallInput; readonly result: CallResult };
|
|
74
82
|
readonly 'tool.check': { readonly input: CheckInput; readonly result: PermissionDecision };
|
|
75
83
|
readonly 'process.run': {
|
|
76
84
|
readonly input: ProcessInput;
|
|
@@ -100,7 +108,10 @@ export interface ModAPI {
|
|
|
100
108
|
readonly run: (argv: readonly string[], init?: ProcessInput['init']) => Promise<ProcessResult>;
|
|
101
109
|
};
|
|
102
110
|
readonly prompt: { readonly submit: (input: PromptInput) => Promise<object> };
|
|
103
|
-
readonly tool: {
|
|
111
|
+
readonly tool: {
|
|
112
|
+
readonly check: (input: CheckInput) => Promise<PermissionDecision>;
|
|
113
|
+
readonly call: (input: CallInput) => Promise<CallResult>;
|
|
114
|
+
};
|
|
104
115
|
readonly classic: {
|
|
105
116
|
readonly SessionStart: (
|
|
106
117
|
input: Partial<SessionContext> & { readonly source: string },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auto-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "A permission classifier library and Claude Code mod, so agents that are not Claude can run unattended",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"oxlint": "1.82.0",
|
|
76
76
|
"oxlint-tsgolint": "7.0.2001",
|
|
77
77
|
"publint": "0.3.24",
|
|
78
|
+
"re2js": "2.8.6",
|
|
78
79
|
"tiny-invariant": "1.3.3",
|
|
79
80
|
"ts-pattern": "5.9.0",
|
|
80
81
|
"tsdown": "0.23.0",
|