@nocville/npc 0.0.0-stage → 0.2.1
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 +268 -2
- package/dist/cjs/cli.js +584 -0
- package/dist/cjs/core/api-key.js +69 -0
- package/dist/cjs/core/base-npc.js +160 -0
- package/dist/cjs/core/config.js +108 -0
- package/dist/cjs/core/event-npc.js +14 -0
- package/dist/cjs/core/logger.js +39 -0
- package/dist/cjs/core/polling-npc.js +65 -0
- package/dist/cjs/core/screen.js +28 -0
- package/dist/cjs/core/session-filter.js +150 -0
- package/dist/cjs/core/text.js +15 -0
- package/dist/cjs/core/thresholds.js +48 -0
- package/dist/cjs/core/types.js +10 -0
- package/dist/cjs/core/user-config.js +92 -0
- package/dist/cjs/doctor.js +130 -0
- package/dist/cjs/index.js +95 -0
- package/dist/cjs/npcs/agent-common/activity.js +291 -0
- package/dist/cjs/npcs/agent-common/git-stats.js +71 -0
- package/dist/cjs/npcs/agent-common/hook-runner.js +252 -0
- package/dist/cjs/npcs/agent-common/hook-settings.js +279 -0
- package/dist/cjs/npcs/agent-common/liveness.js +112 -0
- package/dist/cjs/npcs/agent-common/lock.js +99 -0
- package/dist/cjs/npcs/agent-common/runtime.js +154 -0
- package/dist/cjs/npcs/agent-common/session.js +412 -0
- package/dist/cjs/npcs/agent-common/shim.js +96 -0
- package/dist/cjs/npcs/agent-common/watchdog.js +149 -0
- package/dist/cjs/npcs/claude-code/defaults.js +20 -0
- package/dist/cjs/npcs/claude-code/hook-handler.js +139 -0
- package/dist/cjs/npcs/claude-code/index.js +104 -0
- package/dist/cjs/npcs/claude-code/setup.js +133 -0
- package/dist/cjs/npcs/claude-code/state-machine.js +130 -0
- package/dist/cjs/npcs/codex/defaults.js +32 -0
- package/dist/cjs/npcs/codex/hook-handler.js +116 -0
- package/dist/cjs/npcs/codex/index.js +92 -0
- package/dist/cjs/npcs/codex/rollout.js +246 -0
- package/dist/cjs/npcs/codex/setup.js +104 -0
- package/dist/cjs/npcs/codex/watcher.js +361 -0
- package/dist/cjs/npcs/cursor/defaults.js +15 -0
- package/dist/cjs/npcs/cursor/hook-handler.js +65 -0
- package/dist/cjs/npcs/cursor/index.js +72 -0
- package/dist/cjs/npcs/cursor/normalize.js +54 -0
- package/dist/cjs/npcs/cursor/setup.js +98 -0
- package/dist/cjs/npcs/gemini/defaults.js +15 -0
- package/dist/cjs/npcs/gemini/hook-handler.js +93 -0
- package/dist/cjs/npcs/gemini/index.js +69 -0
- package/dist/cjs/npcs/gemini/setup.js +98 -0
- package/dist/cjs/npcs/github-watcher/defaults.js +11 -0
- package/dist/cjs/npcs/github-watcher/format.js +21 -0
- package/dist/cjs/npcs/github-watcher/github-api.js +126 -0
- package/dist/cjs/npcs/github-watcher/index.js +148 -0
- package/dist/cjs/npcs/internet-connectivity/checks.js +92 -0
- package/dist/cjs/npcs/internet-connectivity/defaults.js +22 -0
- package/dist/cjs/npcs/internet-connectivity/index.js +251 -0
- package/dist/cjs/npcs/log-watcher/buffer.js +40 -0
- package/dist/cjs/npcs/log-watcher/defaults.js +12 -0
- package/dist/cjs/npcs/log-watcher/index.js +161 -0
- package/dist/cjs/npcs/log-watcher/tail.js +81 -0
- package/dist/cjs/npcs/process-watcher/defaults.js +9 -0
- package/dist/cjs/npcs/process-watcher/index.js +140 -0
- package/dist/cjs/npcs/process-watcher/process-scanner.js +114 -0
- package/dist/cjs/npcs/receptionist/defaults.js +9 -0
- package/dist/cjs/npcs/receptionist/index.js +120 -0
- package/dist/cjs/npcs/receptionist/weather.js +129 -0
- package/dist/cjs/npcs/system-monitor/collectors.js +68 -0
- package/dist/cjs/npcs/system-monitor/defaults.js +13 -0
- package/dist/cjs/npcs/system-monitor/index.js +105 -0
- package/dist/cjs/npcs/web-page-watcher/basic-mode.js +58 -0
- package/dist/cjs/npcs/web-page-watcher/capture.js +80 -0
- package/dist/cjs/npcs/web-page-watcher/defaults.js +31 -0
- package/dist/cjs/npcs/web-page-watcher/index.js +234 -0
- package/dist/cjs/npcs/web-page-watcher/pixelate.js +181 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +549 -0
- package/dist/core/api-key.d.ts +34 -0
- package/dist/core/api-key.js +64 -0
- package/dist/core/base-npc.d.ts +41 -0
- package/dist/core/base-npc.js +156 -0
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.js +101 -0
- package/dist/core/event-npc.d.ts +8 -0
- package/dist/core/event-npc.js +10 -0
- package/dist/core/logger.d.ts +11 -0
- package/dist/core/logger.js +35 -0
- package/dist/core/polling-npc.d.ts +18 -0
- package/dist/core/polling-npc.js +61 -0
- package/dist/core/screen.d.ts +11 -0
- package/dist/core/screen.js +24 -0
- package/dist/core/session-filter.d.ts +59 -0
- package/dist/core/session-filter.js +143 -0
- package/dist/core/text.d.ts +6 -0
- package/dist/core/text.js +12 -0
- package/dist/core/thresholds.d.ts +10 -0
- package/dist/core/thresholds.js +44 -0
- package/dist/core/types.d.ts +60 -0
- package/dist/core/types.js +7 -0
- package/dist/core/user-config.d.ts +43 -0
- package/dist/core/user-config.js +85 -0
- package/dist/doctor.d.ts +8 -0
- package/dist/doctor.js +126 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +26 -0
- package/dist/npcs/agent-common/activity.d.ts +42 -0
- package/dist/npcs/agent-common/activity.js +280 -0
- package/dist/npcs/agent-common/git-stats.d.ts +11 -0
- package/dist/npcs/agent-common/git-stats.js +67 -0
- package/dist/npcs/agent-common/hook-runner.d.ts +62 -0
- package/dist/npcs/agent-common/hook-runner.js +245 -0
- package/dist/npcs/agent-common/hook-settings.d.ts +75 -0
- package/dist/npcs/agent-common/hook-settings.js +265 -0
- package/dist/npcs/agent-common/liveness.d.ts +12 -0
- package/dist/npcs/agent-common/liveness.js +105 -0
- package/dist/npcs/agent-common/lock.d.ts +6 -0
- package/dist/npcs/agent-common/lock.js +95 -0
- package/dist/npcs/agent-common/runtime.d.ts +16 -0
- package/dist/npcs/agent-common/runtime.js +149 -0
- package/dist/npcs/agent-common/session.d.ts +192 -0
- package/dist/npcs/agent-common/session.js +390 -0
- package/dist/npcs/agent-common/shim.d.ts +4 -0
- package/dist/npcs/agent-common/shim.js +90 -0
- package/dist/npcs/agent-common/watchdog.d.ts +16 -0
- package/dist/npcs/agent-common/watchdog.js +144 -0
- package/dist/npcs/claude-code/defaults.d.ts +15 -0
- package/dist/npcs/claude-code/defaults.js +17 -0
- package/dist/npcs/claude-code/hook-handler.d.ts +40 -0
- package/dist/npcs/claude-code/hook-handler.js +131 -0
- package/dist/npcs/claude-code/index.d.ts +36 -0
- package/dist/npcs/claude-code/index.js +92 -0
- package/dist/npcs/claude-code/setup.d.ts +58 -0
- package/dist/npcs/claude-code/setup.js +121 -0
- package/dist/npcs/claude-code/state-machine.d.ts +28 -0
- package/dist/npcs/claude-code/state-machine.js +125 -0
- package/dist/npcs/codex/defaults.d.ts +24 -0
- package/dist/npcs/codex/defaults.js +28 -0
- package/dist/npcs/codex/hook-handler.d.ts +24 -0
- package/dist/npcs/codex/hook-handler.js +110 -0
- package/dist/npcs/codex/index.d.ts +21 -0
- package/dist/npcs/codex/index.js +76 -0
- package/dist/npcs/codex/rollout.d.ts +39 -0
- package/dist/npcs/codex/rollout.js +242 -0
- package/dist/npcs/codex/setup.d.ts +37 -0
- package/dist/npcs/codex/setup.js +96 -0
- package/dist/npcs/codex/watcher.d.ts +65 -0
- package/dist/npcs/codex/watcher.js +356 -0
- package/dist/npcs/cursor/defaults.d.ts +9 -0
- package/dist/npcs/cursor/defaults.js +11 -0
- package/dist/npcs/cursor/hook-handler.d.ts +8 -0
- package/dist/npcs/cursor/hook-handler.js +60 -0
- package/dist/npcs/cursor/index.d.ts +18 -0
- package/dist/npcs/cursor/index.js +57 -0
- package/dist/npcs/cursor/normalize.d.ts +6 -0
- package/dist/npcs/cursor/normalize.js +49 -0
- package/dist/npcs/cursor/setup.d.ts +29 -0
- package/dist/npcs/cursor/setup.js +90 -0
- package/dist/npcs/gemini/defaults.d.ts +9 -0
- package/dist/npcs/gemini/defaults.js +11 -0
- package/dist/npcs/gemini/hook-handler.d.ts +10 -0
- package/dist/npcs/gemini/hook-handler.js +87 -0
- package/dist/npcs/gemini/index.d.ts +17 -0
- package/dist/npcs/gemini/index.js +56 -0
- package/dist/npcs/gemini/setup.d.ts +29 -0
- package/dist/npcs/gemini/setup.js +90 -0
- package/dist/npcs/github-watcher/defaults.d.ts +8 -0
- package/dist/npcs/github-watcher/defaults.js +8 -0
- package/dist/npcs/github-watcher/format.d.ts +3 -0
- package/dist/npcs/github-watcher/format.js +18 -0
- package/dist/npcs/github-watcher/github-api.d.ts +55 -0
- package/dist/npcs/github-watcher/github-api.js +120 -0
- package/dist/npcs/github-watcher/index.d.ts +35 -0
- package/dist/npcs/github-watcher/index.js +144 -0
- package/dist/npcs/internet-connectivity/checks.d.ts +24 -0
- package/dist/npcs/internet-connectivity/checks.js +84 -0
- package/dist/npcs/internet-connectivity/defaults.d.ts +20 -0
- package/dist/npcs/internet-connectivity/defaults.js +19 -0
- package/dist/npcs/internet-connectivity/index.d.ts +55 -0
- package/dist/npcs/internet-connectivity/index.js +246 -0
- package/dist/npcs/log-watcher/buffer.d.ts +18 -0
- package/dist/npcs/log-watcher/buffer.js +34 -0
- package/dist/npcs/log-watcher/defaults.d.ts +9 -0
- package/dist/npcs/log-watcher/defaults.js +9 -0
- package/dist/npcs/log-watcher/index.d.ts +45 -0
- package/dist/npcs/log-watcher/index.js +157 -0
- package/dist/npcs/log-watcher/tail.d.ts +9 -0
- package/dist/npcs/log-watcher/tail.js +78 -0
- package/dist/npcs/process-watcher/defaults.d.ts +6 -0
- package/dist/npcs/process-watcher/defaults.js +6 -0
- package/dist/npcs/process-watcher/index.d.ts +26 -0
- package/dist/npcs/process-watcher/index.js +136 -0
- package/dist/npcs/process-watcher/process-scanner.d.ts +19 -0
- package/dist/npcs/process-watcher/process-scanner.js +110 -0
- package/dist/npcs/receptionist/defaults.d.ts +6 -0
- package/dist/npcs/receptionist/defaults.js +6 -0
- package/dist/npcs/receptionist/index.d.ts +29 -0
- package/dist/npcs/receptionist/index.js +116 -0
- package/dist/npcs/receptionist/weather.d.ts +37 -0
- package/dist/npcs/receptionist/weather.js +121 -0
- package/dist/npcs/system-monitor/collectors.d.ts +45 -0
- package/dist/npcs/system-monitor/collectors.js +60 -0
- package/dist/npcs/system-monitor/defaults.d.ts +7 -0
- package/dist/npcs/system-monitor/defaults.js +10 -0
- package/dist/npcs/system-monitor/index.d.ts +27 -0
- package/dist/npcs/system-monitor/index.js +101 -0
- package/dist/npcs/web-page-watcher/basic-mode.d.ts +16 -0
- package/dist/npcs/web-page-watcher/basic-mode.js +52 -0
- package/dist/npcs/web-page-watcher/capture.d.ts +49 -0
- package/dist/npcs/web-page-watcher/capture.js +75 -0
- package/dist/npcs/web-page-watcher/defaults.d.ts +19 -0
- package/dist/npcs/web-page-watcher/defaults.js +27 -0
- package/dist/npcs/web-page-watcher/index.d.ts +42 -0
- package/dist/npcs/web-page-watcher/index.js +230 -0
- package/dist/npcs/web-page-watcher/pixelate.d.ts +36 -0
- package/dist/npcs/web-page-watcher/pixelate.js +172 -0
- package/package.json +73 -3
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-friendly activity strings for AI coding agents (Claude Code, Codex).
|
|
3
|
+
*
|
|
4
|
+
* These end up on the NPC's speech bubble and desk-sign ticker, which teammates
|
|
5
|
+
* see — so they must be short, readable, and never leak secrets or prompt text.
|
|
6
|
+
*/
|
|
7
|
+
/** Max length of a full activity line ("Running pnpm test", "Editing src/x.ts"). */
|
|
8
|
+
export declare const ACTIVITY_MAX = 60;
|
|
9
|
+
export declare const THINKING = "Thinking\u2026";
|
|
10
|
+
/**
|
|
11
|
+
* Make a path short and relative: strip the project cwd (or the home dir to
|
|
12
|
+
* `~`), then keep only the trailing segments if it is still long.
|
|
13
|
+
*/
|
|
14
|
+
export declare function shortPath(p: string, cwd?: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Redact anything secret-looking from a shell command: env assignments,
|
|
17
|
+
* `--token=…`-style flags, bearer headers, URL credentials, and long opaque
|
|
18
|
+
* strings (API keys, hashes). Conservative by design — false positives are fine.
|
|
19
|
+
*/
|
|
20
|
+
export declare function redactSecrets(cmd: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Shorten a shell command for display: first line only, drop a leading
|
|
23
|
+
* `cd … &&`, drop leading `VAR=value` env assignments (often secrets), unwrap
|
|
24
|
+
* `bash -lc "…"` wrappers, redact secrets, and truncate.
|
|
25
|
+
*/
|
|
26
|
+
export declare function shortCommand(raw: unknown, max?: number): string;
|
|
27
|
+
/** Files touched by an `apply_patch` envelope ("*** Update File: path"). */
|
|
28
|
+
export declare function patchFiles(patch: string): string[];
|
|
29
|
+
/** "Editing a.ts" / "Editing a.ts +2 more" for a list of files. */
|
|
30
|
+
export declare function editingFiles(files: string[], cwd?: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Describe a tool invocation in a few words. Covers Claude Code tool names and
|
|
33
|
+
* Codex tool names (Bash/shell/exec_command, apply_patch, update_plan, …);
|
|
34
|
+
* unknown tools fall back to "Using <tool>". Never includes file contents,
|
|
35
|
+
* edit strings, or prompts.
|
|
36
|
+
*/
|
|
37
|
+
export declare function describeToolUse(toolName: string | undefined, rawInput: unknown, cwd?: string): string;
|
|
38
|
+
/** Tools whose PreToolUse means the agent is now waiting on the human. */
|
|
39
|
+
export declare function isQuestionTool(toolName: string | undefined): boolean;
|
|
40
|
+
/** Short failure label: "A command failed", "Edit failed". */
|
|
41
|
+
export declare function failureText(toolName: string | undefined): string;
|
|
42
|
+
//# sourceMappingURL=activity.d.ts.map
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { truncate } from '../../core/text.js';
|
|
3
|
+
/**
|
|
4
|
+
* Human-friendly activity strings for AI coding agents (Claude Code, Codex).
|
|
5
|
+
*
|
|
6
|
+
* These end up on the NPC's speech bubble and desk-sign ticker, which teammates
|
|
7
|
+
* see — so they must be short, readable, and never leak secrets or prompt text.
|
|
8
|
+
*/
|
|
9
|
+
/** Max length of a full activity line ("Running pnpm test", "Editing src/x.ts"). */
|
|
10
|
+
export const ACTIVITY_MAX = 60;
|
|
11
|
+
/** Max length of an embedded shell command. */
|
|
12
|
+
const COMMAND_MAX = 44;
|
|
13
|
+
/** Max length of an embedded path. */
|
|
14
|
+
const PATH_MAX = 40;
|
|
15
|
+
/** Max length of an embedded search pattern / query. */
|
|
16
|
+
const PATTERN_MAX = 28;
|
|
17
|
+
export const THINKING = 'Thinking…';
|
|
18
|
+
function str(input, ...keys) {
|
|
19
|
+
for (const k of keys) {
|
|
20
|
+
const v = input[k];
|
|
21
|
+
if (typeof v === 'string' && v.trim())
|
|
22
|
+
return v.trim();
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
function asInput(raw) {
|
|
27
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw))
|
|
28
|
+
return raw;
|
|
29
|
+
if (typeof raw === 'string') {
|
|
30
|
+
// Some agents pass JSON-encoded arguments.
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(raw);
|
|
33
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Not JSON — treat as a bare command string.
|
|
38
|
+
}
|
|
39
|
+
return { command: raw };
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(raw))
|
|
42
|
+
return { command: raw };
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Make a path short and relative: strip the project cwd (or the home dir to
|
|
47
|
+
* `~`), then keep only the trailing segments if it is still long.
|
|
48
|
+
*/
|
|
49
|
+
export function shortPath(p, cwd) {
|
|
50
|
+
let out = p.trim().replace(/^file:\/\//, '');
|
|
51
|
+
const norm = (s) => s.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
52
|
+
out = norm(out);
|
|
53
|
+
const base = cwd ? norm(cwd) : '';
|
|
54
|
+
if (base && (out === base || out.startsWith(base + '/'))) {
|
|
55
|
+
out = out.slice(base.length + 1) || '.';
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const home = norm(homedir());
|
|
59
|
+
if (home && out.startsWith(home + '/'))
|
|
60
|
+
out = '~' + out.slice(home.length);
|
|
61
|
+
}
|
|
62
|
+
if (out.length <= PATH_MAX)
|
|
63
|
+
return out;
|
|
64
|
+
const parts = out.split('/');
|
|
65
|
+
// Keep the last two segments; fall back to truncating the filename.
|
|
66
|
+
const tail = parts.slice(-2).join('/');
|
|
67
|
+
if (tail.length + 2 <= PATH_MAX)
|
|
68
|
+
return '…/' + tail;
|
|
69
|
+
return truncate(parts[parts.length - 1] ?? out, PATH_MAX);
|
|
70
|
+
}
|
|
71
|
+
const SECRET_FLAG = /(--?[\w-]*(?:token|key|secret|password|passwd|pwd|auth|credential)[\w-]*)(=|\s+)("[^"]*"|'[^']*'|\S+)/gi;
|
|
72
|
+
/**
|
|
73
|
+
* Redact anything secret-looking from a shell command: env assignments,
|
|
74
|
+
* `--token=…`-style flags, bearer headers, URL credentials, and long opaque
|
|
75
|
+
* strings (API keys, hashes). Conservative by design — false positives are fine.
|
|
76
|
+
*/
|
|
77
|
+
export function redactSecrets(cmd) {
|
|
78
|
+
return (cmd
|
|
79
|
+
// URL credentials: scheme://user:pass@host
|
|
80
|
+
.replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi, '$1***@')
|
|
81
|
+
// Authorization headers
|
|
82
|
+
.replace(/(bearer|basic|token)\s+[A-Za-z0-9._~+/=-]{8,}/gi, '$1 ***')
|
|
83
|
+
// --api-key=…, --password …, -token …
|
|
84
|
+
.replace(SECRET_FLAG, (_m, flag, sep) => `${flag}${sep}***`)
|
|
85
|
+
// Well-known key prefixes
|
|
86
|
+
.replace(/\b(sk|pk|rk)-[A-Za-z0-9_-]{8,}/g, '***')
|
|
87
|
+
.replace(/\b(ghp|gho|ghs|ghu|github_pat|xox[abprs]|glpat|AKIA)[A-Za-z0-9_-]{8,}/g, '***')
|
|
88
|
+
// Long opaque tokens (≥32 chars of key-ish alphabet, no path separators)
|
|
89
|
+
.replace(/\b[A-Za-z0-9_+=-]{32,}\b/g, '***'));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Shorten a shell command for display: first line only, drop a leading
|
|
93
|
+
* `cd … &&`, drop leading `VAR=value` env assignments (often secrets), unwrap
|
|
94
|
+
* `bash -lc "…"` wrappers, redact secrets, and truncate.
|
|
95
|
+
*/
|
|
96
|
+
export function shortCommand(raw, max = COMMAND_MAX) {
|
|
97
|
+
let cmd;
|
|
98
|
+
if (Array.isArray(raw)) {
|
|
99
|
+
const parts = raw.filter((x) => typeof x === 'string');
|
|
100
|
+
// Unwrap ["bash", "-lc", "<script>"] style argv.
|
|
101
|
+
const shellIdx = parts.findIndex((p) => p === '-lc' || p === '-c');
|
|
102
|
+
cmd = shellIdx >= 0 && parts[shellIdx + 1] ? parts[shellIdx + 1] : parts.join(' ');
|
|
103
|
+
}
|
|
104
|
+
else if (typeof raw === 'string') {
|
|
105
|
+
cmd = raw;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
return '';
|
|
109
|
+
}
|
|
110
|
+
cmd = cmd.trim();
|
|
111
|
+
// Unwrap a string-form shell wrapper: bash -lc 'pnpm test'
|
|
112
|
+
const wrapped = cmd.match(/^(?:\/\S*\/)?(?:ba|z)?sh\s+-l?c\s+(['"])([\s\S]*)\1$/);
|
|
113
|
+
if (wrapped)
|
|
114
|
+
cmd = wrapped[2].trim();
|
|
115
|
+
cmd = cmd.split('\n')[0].trim();
|
|
116
|
+
// Drop leading "cd <dir> &&" / "cd <dir>;" hops.
|
|
117
|
+
cmd = cmd.replace(/^(?:cd\s+("[^"]*"|'[^']*'|\S+)\s*(?:&&|;)\s*)+/, '');
|
|
118
|
+
// Drop leading env assignments.
|
|
119
|
+
cmd = cmd.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=("[^"]*"|'[^']*'|\S*)\s+)+/, '');
|
|
120
|
+
cmd = redactSecrets(cmd).replace(/\s+/g, ' ').trim();
|
|
121
|
+
return truncate(cmd, max);
|
|
122
|
+
}
|
|
123
|
+
function quote(s, max = PATTERN_MAX) {
|
|
124
|
+
return `'${truncate(s.replace(/\s+/g, ' '), max)}'`;
|
|
125
|
+
}
|
|
126
|
+
function hostOf(url) {
|
|
127
|
+
try {
|
|
128
|
+
return new URL(url).hostname.replace(/^www\./, '');
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return truncate(url, PATTERN_MAX);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** Files touched by an `apply_patch` envelope ("*** Update File: path"). */
|
|
135
|
+
export function patchFiles(patch) {
|
|
136
|
+
const files = [];
|
|
137
|
+
const re = /^\*\*\* (?:Update|Add|Delete) File:\s*(.+)$/gm;
|
|
138
|
+
let m;
|
|
139
|
+
while ((m = re.exec(patch)) !== null) {
|
|
140
|
+
const f = m[1].trim();
|
|
141
|
+
if (f && !files.includes(f))
|
|
142
|
+
files.push(f);
|
|
143
|
+
}
|
|
144
|
+
return files;
|
|
145
|
+
}
|
|
146
|
+
/** "Editing a.ts" / "Editing a.ts +2 more" for a list of files. */
|
|
147
|
+
export function editingFiles(files, cwd) {
|
|
148
|
+
if (files.length === 0)
|
|
149
|
+
return 'Editing files';
|
|
150
|
+
const first = shortPath(files[0], cwd);
|
|
151
|
+
return files.length === 1 ? `Editing ${first}` : `Editing ${first} +${files.length - 1} more`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Describe a tool invocation in a few words. Covers Claude Code tool names and
|
|
155
|
+
* Codex tool names (Bash/shell/exec_command, apply_patch, update_plan, …);
|
|
156
|
+
* unknown tools fall back to "Using <tool>". Never includes file contents,
|
|
157
|
+
* edit strings, or prompts.
|
|
158
|
+
*/
|
|
159
|
+
export function describeToolUse(toolName, rawInput, cwd) {
|
|
160
|
+
const name = (toolName ?? '').trim();
|
|
161
|
+
const input = asInput(rawInput);
|
|
162
|
+
const key = name.toLowerCase();
|
|
163
|
+
// MCP tools: mcp__server__tool → "Using server (tool)"
|
|
164
|
+
if (key.startsWith('mcp__')) {
|
|
165
|
+
const [, server = 'mcp', tool = ''] = name.split('__');
|
|
166
|
+
return truncate(tool ? `Using ${server} (${tool})` : `Using ${server}`, ACTIVITY_MAX);
|
|
167
|
+
}
|
|
168
|
+
switch (key) {
|
|
169
|
+
case 'replace':
|
|
170
|
+
case 'write_file':
|
|
171
|
+
case 'edit':
|
|
172
|
+
case 'multiedit':
|
|
173
|
+
case 'write':
|
|
174
|
+
case 'notebookedit': {
|
|
175
|
+
const file = str(input, 'file_path', 'absolute_path', 'notebook_path', 'path');
|
|
176
|
+
if (file)
|
|
177
|
+
return truncate(`Editing ${shortPath(file, cwd)}`, ACTIVITY_MAX);
|
|
178
|
+
// Codex aliases Edit/Write to apply_patch; fall through to patch parsing.
|
|
179
|
+
const patch = str(input, 'command', 'input', 'patch');
|
|
180
|
+
return truncate(editingFiles(patch ? patchFiles(patch) : [], cwd), ACTIVITY_MAX);
|
|
181
|
+
}
|
|
182
|
+
case 'delete': {
|
|
183
|
+
const file = str(input, 'file_path', 'path');
|
|
184
|
+
return file ? truncate(`Deleting ${shortPath(file, cwd)}`, ACTIVITY_MAX) : 'Deleting a file';
|
|
185
|
+
}
|
|
186
|
+
case 'apply_patch':
|
|
187
|
+
case 'applypatch': {
|
|
188
|
+
const patch = str(input, 'command', 'input', 'patch') ?? '';
|
|
189
|
+
return truncate(editingFiles(patchFiles(patch), cwd), ACTIVITY_MAX);
|
|
190
|
+
}
|
|
191
|
+
case 'run_shell_command':
|
|
192
|
+
case 'bash':
|
|
193
|
+
case 'shell':
|
|
194
|
+
case 'local_shell':
|
|
195
|
+
case 'exec_command':
|
|
196
|
+
case 'container.exec':
|
|
197
|
+
case 'unified_exec': {
|
|
198
|
+
const cmd = shortCommand(input['command'] ?? input['cmd']);
|
|
199
|
+
return cmd ? `Running ${cmd}` : 'Running a command';
|
|
200
|
+
}
|
|
201
|
+
case 'bashoutput':
|
|
202
|
+
case 'write_stdin':
|
|
203
|
+
return 'Checking a running command';
|
|
204
|
+
case 'killshell':
|
|
205
|
+
case 'killbash':
|
|
206
|
+
return 'Stopping a command';
|
|
207
|
+
case 'read_file':
|
|
208
|
+
case 'read':
|
|
209
|
+
case 'notebookread':
|
|
210
|
+
case 'view': {
|
|
211
|
+
const file = str(input, 'file_path', 'absolute_path', 'notebook_path', 'path');
|
|
212
|
+
return file ? truncate(`Reading ${shortPath(file, cwd)}`, ACTIVITY_MAX) : 'Reading files';
|
|
213
|
+
}
|
|
214
|
+
case 'view_image': {
|
|
215
|
+
const file = str(input, 'path', 'file_path');
|
|
216
|
+
return file ? truncate(`Viewing ${shortPath(file, cwd)}`, ACTIVITY_MAX) : 'Viewing an image';
|
|
217
|
+
}
|
|
218
|
+
case 'ls': {
|
|
219
|
+
const dir = str(input, 'path');
|
|
220
|
+
return dir ? truncate(`Listing ${shortPath(dir, cwd)}`, ACTIVITY_MAX) : 'Listing files';
|
|
221
|
+
}
|
|
222
|
+
case 'grep': {
|
|
223
|
+
const pattern = str(input, 'pattern', 'query');
|
|
224
|
+
return pattern ? `Searching for ${quote(pattern)}` : 'Searching the code';
|
|
225
|
+
}
|
|
226
|
+
case 'glob': {
|
|
227
|
+
const pattern = str(input, 'pattern');
|
|
228
|
+
return pattern ? `Finding files ${quote(pattern)}` : 'Finding files';
|
|
229
|
+
}
|
|
230
|
+
case 'toolsearch':
|
|
231
|
+
return 'Looking up tools';
|
|
232
|
+
case 'webfetch': {
|
|
233
|
+
const url = str(input, 'url');
|
|
234
|
+
return url ? `Browsing ${hostOf(url)}` : 'Browsing the web';
|
|
235
|
+
}
|
|
236
|
+
case 'websearch':
|
|
237
|
+
case 'web_search':
|
|
238
|
+
case 'web_search_preview': {
|
|
239
|
+
const q = str(input, 'query', 'q');
|
|
240
|
+
return q ? `Searching the web for ${quote(q)}` : 'Searching the web';
|
|
241
|
+
}
|
|
242
|
+
case 'task':
|
|
243
|
+
case 'agent':
|
|
244
|
+
case 'spawn_agent':
|
|
245
|
+
return 'Delegating to a subagent';
|
|
246
|
+
case 'todowrite':
|
|
247
|
+
case 'update_plan':
|
|
248
|
+
case 'taskcreate':
|
|
249
|
+
case 'taskupdate':
|
|
250
|
+
return 'Planning';
|
|
251
|
+
case 'exitplanmode':
|
|
252
|
+
return 'Plan ready for review';
|
|
253
|
+
case 'askuserquestion':
|
|
254
|
+
case 'request_user_input':
|
|
255
|
+
return 'Has a question for you';
|
|
256
|
+
case 'skill': {
|
|
257
|
+
const skill = str(input, 'skill', 'name');
|
|
258
|
+
return skill ? truncate(`Using skill ${skill}`, ACTIVITY_MAX) : 'Using a skill';
|
|
259
|
+
}
|
|
260
|
+
case '':
|
|
261
|
+
return 'Working';
|
|
262
|
+
default:
|
|
263
|
+
return truncate(`Using ${name}`, ACTIVITY_MAX);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/** Tools whose PreToolUse means the agent is now waiting on the human. */
|
|
267
|
+
export function isQuestionTool(toolName) {
|
|
268
|
+
const k = (toolName ?? '').toLowerCase();
|
|
269
|
+
return k === 'askuserquestion' || k === 'exitplanmode' || k === 'request_user_input';
|
|
270
|
+
}
|
|
271
|
+
/** Short failure label: "A command failed", "Edit failed". */
|
|
272
|
+
export function failureText(toolName) {
|
|
273
|
+
const k = (toolName ?? '').toLowerCase();
|
|
274
|
+
if (k === 'bash' || k === 'shell' || k === 'exec_command')
|
|
275
|
+
return 'A command failed';
|
|
276
|
+
if (!toolName)
|
|
277
|
+
return 'Hit an error';
|
|
278
|
+
return truncate(`${toolName} failed`, ACTIVITY_MAX);
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=activity.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AgentStats } from '@nocville/node';
|
|
2
|
+
export type { AgentStats } from '@nocville/node';
|
|
3
|
+
/** Parse NUL-delimited numstat, including binary files and rename path pairs. */
|
|
4
|
+
export declare function parseNumstat(raw: string): Array<{
|
|
5
|
+
path: string;
|
|
6
|
+
insertions: number;
|
|
7
|
+
deletions: number;
|
|
8
|
+
}>;
|
|
9
|
+
/** Both commands share one wall-clock budget; call only from detached flushes. */
|
|
10
|
+
export declare function computeGitStats(cwd: string, timeoutMs?: number): AgentStats | null;
|
|
11
|
+
//# sourceMappingURL=git-stats.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { performance } from 'node:perf_hooks';
|
|
4
|
+
/** Parse NUL-delimited numstat, including binary files and rename path pairs. */
|
|
5
|
+
export function parseNumstat(raw) {
|
|
6
|
+
const fields = raw.split('\0'), files = [];
|
|
7
|
+
for (let i = 0; i < fields.length; i++) {
|
|
8
|
+
const record = fields[i];
|
|
9
|
+
if (!record)
|
|
10
|
+
continue;
|
|
11
|
+
const match = /^(\d+|-)\t(\d+|-)\t([\s\S]*)$/.exec(record);
|
|
12
|
+
if (!match)
|
|
13
|
+
continue;
|
|
14
|
+
let path = match[3];
|
|
15
|
+
if (!path) {
|
|
16
|
+
i++;
|
|
17
|
+
path = fields[++i] ?? '';
|
|
18
|
+
}
|
|
19
|
+
if (path)
|
|
20
|
+
files.push({
|
|
21
|
+
path,
|
|
22
|
+
insertions: match[1] === '-' ? 0 : Number(match[1]),
|
|
23
|
+
deletions: match[2] === '-' ? 0 : Number(match[2]),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return files;
|
|
27
|
+
}
|
|
28
|
+
/** Both commands share one wall-clock budget; call only from detached flushes. */
|
|
29
|
+
export function computeGitStats(cwd, timeoutMs = 1500) {
|
|
30
|
+
const deadline = performance.now() + Math.max(1, Math.min(1500, timeoutMs));
|
|
31
|
+
const git = (args) => {
|
|
32
|
+
const remaining = Math.floor(deadline - performance.now());
|
|
33
|
+
if (remaining <= 0)
|
|
34
|
+
return null;
|
|
35
|
+
const result = spawnSync('git', ['--no-optional-locks', '-c', 'core.fsmonitor=false', '-C', cwd, ...args], {
|
|
36
|
+
encoding: 'utf8',
|
|
37
|
+
timeout: remaining,
|
|
38
|
+
killSignal: 'SIGKILL',
|
|
39
|
+
maxBuffer: 1 << 20,
|
|
40
|
+
windowsHide: true,
|
|
41
|
+
});
|
|
42
|
+
return result.status === 0 && !result.error ? result.stdout : null;
|
|
43
|
+
};
|
|
44
|
+
const diff = git(['diff', '--no-ext-diff', '--no-textconv', '--numstat', '-z', 'HEAD', '--']);
|
|
45
|
+
if (diff === null)
|
|
46
|
+
return null;
|
|
47
|
+
const untracked = git(['ls-files', '--others', '--exclude-standard', '-z']);
|
|
48
|
+
if (untracked === null)
|
|
49
|
+
return null;
|
|
50
|
+
const files = parseNumstat(diff);
|
|
51
|
+
for (const path of untracked.split('\0'))
|
|
52
|
+
if (path)
|
|
53
|
+
files.push({ path, insertions: 0, deletions: 0 });
|
|
54
|
+
if (files.length > 100_000)
|
|
55
|
+
return null;
|
|
56
|
+
files.sort((a, b) => b.insertions + b.deletions - (a.insertions + a.deletions) || a.path.localeCompare(b.path));
|
|
57
|
+
return {
|
|
58
|
+
filesChanged: files.length,
|
|
59
|
+
insertions: files.reduce((n, file) => n + file.insertions, 0),
|
|
60
|
+
deletions: files.reduce((n, file) => n + file.deletions, 0),
|
|
61
|
+
lastFiles: files.slice(0, 5).map((file) => basename(file.path)
|
|
62
|
+
// eslint-disable-next-line no-control-regex -- Strip display controls from Git filenames.
|
|
63
|
+
.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, '')
|
|
64
|
+
.slice(0, 120)),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=git-stats.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { StatusUpdate } from '@nocville/node';
|
|
2
|
+
import type { NpcConfig } from '../../core/types.js';
|
|
3
|
+
import { computeGitStats } from './git-stats.js';
|
|
4
|
+
import type { AgentAdapter, AgentSessionState, AgentTransition } from './session.js';
|
|
5
|
+
/** A raw hook payload (Claude Code and Codex share the same envelope shape). */
|
|
6
|
+
export interface AgentHookEvent {
|
|
7
|
+
session_id?: string;
|
|
8
|
+
hook_event_name?: string;
|
|
9
|
+
/** Legacy alias accepted by older callers/tests. */
|
|
10
|
+
event?: string;
|
|
11
|
+
cwd?: string;
|
|
12
|
+
tool_name?: string;
|
|
13
|
+
tool_input?: unknown;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
export declare function hookEventName(event: AgentHookEvent): string;
|
|
17
|
+
/** Everything that differs between agents. */
|
|
18
|
+
export interface AgentHookSpec {
|
|
19
|
+
adapter: AgentAdapter;
|
|
20
|
+
/** Required neutral response for hosts whose hook protocol always expects JSON. */
|
|
21
|
+
stdoutOnSuccess?: string;
|
|
22
|
+
statsEvents?: readonly string[];
|
|
23
|
+
/** Cross-source pairs (native and imported hooks), deduplicated under the state lock. */
|
|
24
|
+
deliveryIdentity?(event: AgentHookEvent): {
|
|
25
|
+
key: string;
|
|
26
|
+
source: string;
|
|
27
|
+
};
|
|
28
|
+
serviceIdFor(sessionId: string, cwd: string): string;
|
|
29
|
+
transition(event: AgentHookEvent, prev: AgentSessionState | null): AgentTransition;
|
|
30
|
+
/** Optional user-facing session name (e.g. Claude Code session title). */
|
|
31
|
+
resolveName?(event: AgentHookEvent, prev: AgentSessionState | null): string | undefined;
|
|
32
|
+
}
|
|
33
|
+
export interface HookRunOptions {
|
|
34
|
+
/**
|
|
35
|
+
* 'inline' awaits the push in-process (library use, tests). 'detach' (the CLI
|
|
36
|
+
* default) hands pushes to a background process so the hook exits at once.
|
|
37
|
+
*/
|
|
38
|
+
mode?: 'inline' | 'detach';
|
|
39
|
+
minPushGapMs?: number;
|
|
40
|
+
now?: () => number;
|
|
41
|
+
/** Inline-mode push override (tests). */
|
|
42
|
+
push?: (serviceId: string, payload: StatusUpdate) => Promise<{
|
|
43
|
+
success: boolean;
|
|
44
|
+
error?: string;
|
|
45
|
+
}>;
|
|
46
|
+
/** Override how deferred flushes are scheduled (tests). */
|
|
47
|
+
scheduleFlush?: (sessionId: string, delayMs: number) => void;
|
|
48
|
+
/** cli.js path for detached flushes (default: process.argv[1]). */
|
|
49
|
+
cliPath?: string;
|
|
50
|
+
}
|
|
51
|
+
/** Read all of stdin synchronously; '' on error. */
|
|
52
|
+
export declare function readStdin(): string;
|
|
53
|
+
/**
|
|
54
|
+
* Process one hook invocation. Never throws. Only a configured neutral JSON response
|
|
55
|
+
* reaches stdout; other hosts receive no output.
|
|
56
|
+
*/
|
|
57
|
+
export declare function runAgentHook(spec: AgentHookSpec, config: NpcConfig, rawInput: string | AgentHookEvent, opts?: HookRunOptions): Promise<void>;
|
|
58
|
+
/** `<agent> flush <session> --delay=ms`: the detached half of a hook push. */
|
|
59
|
+
export declare function runFlushCommand(adapter: AgentAdapter, config: NpcConfig, sessionId: string | undefined, delayMs: number | undefined, stats?: boolean): Promise<void>;
|
|
60
|
+
/** Git runs outside the state lock, and a newer turn can invalidate its result. */
|
|
61
|
+
export declare function collectSessionGitStats(adapter: AgentAdapter, sessionId: string, compute?: typeof computeGitStats): Promise<void>;
|
|
62
|
+
//# sourceMappingURL=hook-runner.d.ts.map
|