cyrus-edge-worker 0.2.52 → 0.2.53
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/ChatSessionHandler.d.ts +19 -1
- package/dist/ChatSessionHandler.d.ts.map +1 -1
- package/dist/ChatSessionHandler.js +12 -0
- package/dist/ChatSessionHandler.js.map +1 -1
- package/dist/ConfigManager.d.ts.map +1 -1
- package/dist/ConfigManager.js +12 -2
- package/dist/ConfigManager.js.map +1 -1
- package/dist/EdgeWorker.d.ts +42 -0
- package/dist/EdgeWorker.d.ts.map +1 -1
- package/dist/EdgeWorker.js +202 -22
- package/dist/EdgeWorker.js.map +1 -1
- package/dist/McpConfigService.d.ts +8 -4
- package/dist/McpConfigService.d.ts.map +1 -1
- package/dist/McpConfigService.js +12 -5
- package/dist/McpConfigService.js.map +1 -1
- package/dist/RunnerConfigBuilder.d.ts +53 -17
- package/dist/RunnerConfigBuilder.d.ts.map +1 -1
- package/dist/RunnerConfigBuilder.js +93 -41
- package/dist/RunnerConfigBuilder.js.map +1 -1
- package/dist/ToolPermissionResolver.d.ts +41 -18
- package/dist/ToolPermissionResolver.d.ts.map +1 -1
- package/dist/ToolPermissionResolver.js +77 -47
- package/dist/ToolPermissionResolver.js.map +1 -1
- package/dist/hooks/IntentToAddHook.d.ts +56 -0
- package/dist/hooks/IntentToAddHook.d.ts.map +1 -0
- package/dist/hooks/IntentToAddHook.js +150 -0
- package/dist/hooks/IntentToAddHook.js.map +1 -0
- package/dist/prompts/failureModePromptAddendum.d.ts +26 -0
- package/dist/prompts/failureModePromptAddendum.d.ts.map +1 -0
- package/dist/prompts/failureModePromptAddendum.js +67 -0
- package/dist/prompts/failureModePromptAddendum.js.map +1 -0
- package/package.json +14 -14
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { getAllTools, getCoordinatorTools,
|
|
1
|
+
import { getAllTools, getCoordinatorTools, getSafeTools, } from "cyrus-claude-runner";
|
|
2
|
+
import { GITHUB_DEFAULT_ALLOWED_TOOLS, LINEAR_DEFAULT_ALLOWED_TOOLS, SLACK_DEFAULT_ALLOWED_TOOLS, } from "cyrus-core";
|
|
2
3
|
/**
|
|
3
|
-
* Unified tool permission resolver for
|
|
4
|
+
* Unified tool permission resolver for issue, chat, and webhook-triggered
|
|
5
|
+
* sessions.
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The resolver is **additive only**: it never appends or strips tools after
|
|
8
|
+
* the explicit list is chosen. The per-platform defaults live in cyrus-core
|
|
9
|
+
* (`LINEAR_DEFAULT_ALLOWED_TOOLS`, `SLACK_DEFAULT_ALLOWED_TOOLS`,
|
|
10
|
+
* `GITHUB_DEFAULT_ALLOWED_TOOLS`) and include workspace MCP prefixes
|
|
11
|
+
* (`mcp__linear`, `mcp__cyrus-tools`, etc.) explicitly. Callers that want a
|
|
12
|
+
* tighter list pass `linearAllowedTools` / `slackAllowedTools` /
|
|
13
|
+
* `githubAllowedTools` on `EdgeWorkerConfig`, or set repo-level
|
|
14
|
+
* `allowedTools`. The repo override is a verbatim replacement, not an
|
|
15
|
+
* intersection.
|
|
9
16
|
*/
|
|
10
17
|
export class ToolPermissionResolver {
|
|
11
18
|
config;
|
|
@@ -29,7 +36,9 @@ export class ToolPermissionResolver {
|
|
|
29
36
|
}
|
|
30
37
|
switch (preset) {
|
|
31
38
|
case "readOnly":
|
|
32
|
-
|
|
39
|
+
// Read-only preset for chat sessions falls back to the Slack default
|
|
40
|
+
// (which encodes the curated read-only set including MCP prefixes).
|
|
41
|
+
return [...SLACK_DEFAULT_ALLOWED_TOOLS];
|
|
33
42
|
case "safe":
|
|
34
43
|
return getSafeTools();
|
|
35
44
|
case "all":
|
|
@@ -42,64 +51,84 @@ export class ToolPermissionResolver {
|
|
|
42
51
|
}
|
|
43
52
|
}
|
|
44
53
|
/**
|
|
45
|
-
* Build allowed tools for chat sessions.
|
|
54
|
+
* Build allowed tools for Slack chat sessions.
|
|
46
55
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
56
|
+
* Returns the team-configured `slackAllowedTools` if set, otherwise the
|
|
57
|
+
* built-in `SLACK_DEFAULT_ALLOWED_TOOLS`. Additionally merges any
|
|
58
|
+
* user-configured MCP tool entries the caller threads through (used by
|
|
59
|
+
* `RunnerConfigBuilder` when a repo declares custom MCP server tools).
|
|
49
60
|
*
|
|
50
|
-
* @param mcpConfigKeys - Built-in MCP server names
|
|
51
|
-
*
|
|
61
|
+
* @param mcpConfigKeys - Built-in MCP server names. Folded in as
|
|
62
|
+
* `mcp__<key>` prefixes only if not already present in the explicit
|
|
63
|
+
* list — the defaults already include the standard prefixes, so this
|
|
64
|
+
* is purely additive for non-standard servers.
|
|
65
|
+
* @param userMcpTools - User-configured MCP tool entries from repository
|
|
66
|
+
* `allowedTools` (already `mcp__*` prefixed).
|
|
52
67
|
*/
|
|
53
68
|
buildChatAllowedTools(mcpConfigKeys, userMcpTools) {
|
|
69
|
+
const baseChatTools = this.config.slackAllowedTools && this.config.slackAllowedTools.length > 0
|
|
70
|
+
? this.config.slackAllowedTools
|
|
71
|
+
: [...SLACK_DEFAULT_ALLOWED_TOOLS];
|
|
54
72
|
const mcpToolPermissions = (mcpConfigKeys ?? []).map((server) => `mcp__${server}`);
|
|
55
73
|
return Array.from(new Set([
|
|
56
|
-
...
|
|
74
|
+
...baseChatTools,
|
|
57
75
|
...mcpToolPermissions,
|
|
58
76
|
...(userMcpTools ?? []),
|
|
59
|
-
"Bash(git -C * pull)",
|
|
60
77
|
]));
|
|
61
78
|
}
|
|
62
79
|
/**
|
|
63
|
-
* Build allowed tools list
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
80
|
+
* Build allowed tools list for Linear-triggered sessions.
|
|
81
|
+
*
|
|
82
|
+
* Accepts a single repository or an array for multi-repo sessions. For
|
|
83
|
+
* multiple repositories the result is the **union** of each repo's
|
|
84
|
+
* resolved list (per-repo presets resolved first, then unioned). When no
|
|
85
|
+
* repos are passed, falls back to the workspace `linearAllowedTools`
|
|
86
|
+
* (or the Linear platform default when neither is set).
|
|
68
87
|
*/
|
|
69
88
|
buildAllowedTools(repositories, promptType) {
|
|
70
89
|
const repoArray = Array.isArray(repositories)
|
|
71
90
|
? repositories
|
|
72
91
|
: [repositories];
|
|
73
92
|
if (repoArray.length === 0) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
93
|
+
const baseTools = this.config.linearAllowedTools ?? [
|
|
94
|
+
...LINEAR_DEFAULT_ALLOWED_TOOLS,
|
|
95
|
+
];
|
|
96
|
+
return [...new Set(baseTools)];
|
|
77
97
|
}
|
|
78
|
-
// For each repo, resolve its allowed tools (without MCP — those are added once at the end)
|
|
79
98
|
const perRepoTools = repoArray.map((repo) => this.buildAllowedToolsForRepo(repo, promptType));
|
|
80
|
-
// Union across all repos
|
|
81
99
|
const unionTools = [...new Set(perRepoTools.flat())];
|
|
82
|
-
// Workspace-level MCP tools added once regardless of repo count
|
|
83
|
-
const allTools = [
|
|
84
|
-
...new Set([...unionTools, ...this.getWorkspaceMcpTools()]),
|
|
85
|
-
];
|
|
86
100
|
const repoNames = repoArray.map((r) => r.name).join(", ");
|
|
87
|
-
this.logger.debug(`
|
|
88
|
-
return
|
|
101
|
+
this.logger.debug(`Linear tool selection for [${repoNames}]: ${unionTools.length} tools (union of ${repoArray.length} repo(s))`);
|
|
102
|
+
return unionTools;
|
|
89
103
|
}
|
|
90
104
|
/**
|
|
91
|
-
*
|
|
105
|
+
* Build allowed tools list for GitHub-triggered sessions.
|
|
106
|
+
*
|
|
107
|
+
* GitHub `@mentions` target a single repository via a single PR, so this
|
|
108
|
+
* does not perform multi-repo union — it expects exactly one repo. When
|
|
109
|
+
* the workspace defines `githubAllowedTools` it is used as the global
|
|
110
|
+
* default for resolution (in place of `linearAllowedTools`); otherwise
|
|
111
|
+
* we fall back to `GITHUB_DEFAULT_ALLOWED_TOOLS`. Per-repository
|
|
112
|
+
* `allowedTools` overrides still take precedence — same priority chain
|
|
113
|
+
* as Linear, just with a different platform default at the bottom.
|
|
92
114
|
*/
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
buildGithubAllowedTools(repository, promptType) {
|
|
116
|
+
const platformDefault = this.config.githubAllowedTools &&
|
|
117
|
+
this.config.githubAllowedTools.length > 0
|
|
118
|
+
? this.config.githubAllowedTools
|
|
119
|
+
: [...GITHUB_DEFAULT_ALLOWED_TOOLS];
|
|
120
|
+
const originalDefault = this.config.linearAllowedTools;
|
|
121
|
+
this.config.linearAllowedTools = platformDefault;
|
|
122
|
+
try {
|
|
123
|
+
return this.buildAllowedTools(repository, promptType);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
this.config.linearAllowedTools = originalDefault;
|
|
98
127
|
}
|
|
99
|
-
return tools;
|
|
100
128
|
}
|
|
101
129
|
/**
|
|
102
|
-
* Resolve allowed tools for a single repository (
|
|
130
|
+
* Resolve allowed tools for a single repository (Linear/GitHub priority
|
|
131
|
+
* chain — chat sessions go through `buildChatAllowedTools`).
|
|
103
132
|
*/
|
|
104
133
|
buildAllowedToolsForRepo(repository, promptType) {
|
|
105
134
|
const effectivePromptType = promptType === "graphite-orchestrator" ? "orchestrator" : promptType;
|
|
@@ -119,16 +148,20 @@ export class ToolPermissionResolver {
|
|
|
119
148
|
this.config.promptDefaults?.[effectivePromptType]?.allowedTools) {
|
|
120
149
|
return this.resolveToolPreset(this.config.promptDefaults[effectivePromptType].allowedTools);
|
|
121
150
|
}
|
|
122
|
-
// 3. Repository-level allowed tools
|
|
151
|
+
// 3. Repository-level allowed tools (verbatim — no platform-default
|
|
152
|
+
// merging; if the operator narrows the list, they get the narrow
|
|
153
|
+
// list).
|
|
123
154
|
if (repository.allowedTools) {
|
|
124
155
|
return repository.allowedTools;
|
|
125
156
|
}
|
|
126
|
-
// 4.
|
|
127
|
-
|
|
128
|
-
|
|
157
|
+
// 4. Workspace default allowed tools (the platform default the
|
|
158
|
+
// surrounding `buildAllowedTools` / `buildGithubAllowedTools`
|
|
159
|
+
// swapped in, if any).
|
|
160
|
+
if (this.config.linearAllowedTools) {
|
|
161
|
+
return this.config.linearAllowedTools;
|
|
129
162
|
}
|
|
130
|
-
// 5.
|
|
131
|
-
return
|
|
163
|
+
// 5. Final fallback — Linear platform default.
|
|
164
|
+
return [...LINEAR_DEFAULT_ALLOWED_TOOLS];
|
|
132
165
|
}
|
|
133
166
|
/**
|
|
134
167
|
* Build disallowed tools list from repository and global config.
|
|
@@ -141,12 +174,9 @@ export class ToolPermissionResolver {
|
|
|
141
174
|
? repositories
|
|
142
175
|
: [repositories];
|
|
143
176
|
if (repoArray.length === 0) {
|
|
144
|
-
// No repos — fall back to global defaults
|
|
145
177
|
return this.config.defaultDisallowedTools || [];
|
|
146
178
|
}
|
|
147
|
-
// For each repo, resolve its disallowed tools
|
|
148
179
|
const perRepoTools = repoArray.map((repo) => this.buildDisallowedToolsForRepo(repo, promptType));
|
|
149
|
-
// Intersection: only block a tool if ALL repos block it
|
|
150
180
|
let intersection;
|
|
151
181
|
if (perRepoTools.length === 1) {
|
|
152
182
|
intersection = perRepoTools[0];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToolPermissionResolver.js","sourceRoot":"","sources":["../src/ToolPermissionResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,WAAW,EACX,mBAAmB,EACnB,
|
|
1
|
+
{"version":3,"file":"ToolPermissionResolver.js","sourceRoot":"","sources":["../src/ToolPermissionResolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { HookCallbackMatcher, HookEvent } from "cyrus-claude-runner";
|
|
2
|
+
import type { ILogger } from "cyrus-core";
|
|
3
|
+
/**
|
|
4
|
+
* Abstraction over the git/filesystem calls used by the hook so tests can
|
|
5
|
+
* stub them without spawning real git processes.
|
|
6
|
+
*/
|
|
7
|
+
export interface IntentToAddGitClient {
|
|
8
|
+
/** Returns true when `cwd` is inside a git working tree. */
|
|
9
|
+
isGitRepo(cwd: string): boolean;
|
|
10
|
+
/** Returns true when `path` exists on disk. */
|
|
11
|
+
pathExists(path: string): boolean;
|
|
12
|
+
/** Returns true when `path` matches a `.gitignore` rule relative to `cwd`. */
|
|
13
|
+
isIgnored(cwd: string, path: string): boolean;
|
|
14
|
+
/** Returns true when `path` is already tracked by git in `cwd`. */
|
|
15
|
+
isTracked(cwd: string, path: string): boolean;
|
|
16
|
+
/** Runs `git add --intent-to-add` for `path` in `cwd`. */
|
|
17
|
+
intentToAdd(cwd: string, path: string): void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Production implementation backed by the local `git` binary and `fs`.
|
|
21
|
+
* All operations are designed to fail silently — a missing/broken git
|
|
22
|
+
* environment must not turn the hook into an error.
|
|
23
|
+
*/
|
|
24
|
+
export declare class DefaultIntentToAddGitClient implements IntentToAddGitClient {
|
|
25
|
+
isGitRepo(cwd: string): boolean;
|
|
26
|
+
pathExists(path: string): boolean;
|
|
27
|
+
isIgnored(cwd: string, path: string): boolean;
|
|
28
|
+
isTracked(cwd: string, path: string): boolean;
|
|
29
|
+
intentToAdd(cwd: string, path: string): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Extract the path argument from a Write/Edit/MultiEdit/NotebookEdit tool
|
|
33
|
+
* input. Returns `undefined` when no string path is present — keeps the hook
|
|
34
|
+
* a no-op for malformed or unexpected inputs.
|
|
35
|
+
*/
|
|
36
|
+
export declare function extractToolPath(toolInput: unknown): string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Apply `git add --intent-to-add` for `path` in `cwd` when, and only when,
|
|
39
|
+
* all of the following hold:
|
|
40
|
+
* - `cwd` is a git repo
|
|
41
|
+
* - `path` exists on disk
|
|
42
|
+
* - `path` is not gitignored
|
|
43
|
+
* - `path` is not already tracked
|
|
44
|
+
*
|
|
45
|
+
* Any other state is a deliberate no-op. The function never throws.
|
|
46
|
+
*/
|
|
47
|
+
export declare function applyIntentToAdd(client: IntentToAddGitClient, cwd: string, path: string, log: ILogger): void;
|
|
48
|
+
/**
|
|
49
|
+
* Build the PostToolUse hook that marks brand-new files created by
|
|
50
|
+
* Write/Edit-style tools with `git add --intent-to-add`. Combined with the
|
|
51
|
+
* Stop-hook guardrail's `--untracked-files=no`, this preserves the
|
|
52
|
+
* "forgot-to-commit a new file" check while ignoring pre-existing untracked
|
|
53
|
+
* files in the customer's worktree (which would otherwise wedge the agent).
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildIntentToAddHook(log: ILogger, client?: IntentToAddGitClient): Partial<Record<HookEvent, HookCallbackMatcher[]>>;
|
|
56
|
+
//# sourceMappingURL=IntentToAddHook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntentToAddHook.d.ts","sourceRoot":"","sources":["../../src/hooks/IntentToAddHook.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACX,mBAAmB,EACnB,SAAS,EAET,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAW1C;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACpC,4DAA4D;IAC5D,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,+CAA+C;IAC/C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,8EAA8E;IAC9E,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9C,mEAAmE;IACnE,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9C,0DAA0D;IAC1D,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7C;AAED;;;;GAIG;AACH,qBAAa,2BAA4B,YAAW,oBAAoB;IACvE,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAY/B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAQjC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAY7C,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAY7C,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;CAM5C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAYtE;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,oBAAoB,EAC5B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,OAAO,GACV,IAAI,CAuBN;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CACnC,GAAG,EAAE,OAAO,EACZ,MAAM,GAAE,oBAAwD,GAC9D,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,mBAAmB,EAAE,CAAC,CAAC,CAwBnD"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
/**
|
|
4
|
+
* Tool names whose successful invocation may have produced a brand-new file
|
|
5
|
+
* that the agent intends to ship. Marking that file with
|
|
6
|
+
* `git add --intent-to-add` ensures the Stop-hook guardrail (which uses
|
|
7
|
+
* `git status --untracked-files=no`) still flags the file as unshipped if
|
|
8
|
+
* the agent forgets to commit it before ending the session.
|
|
9
|
+
*/
|
|
10
|
+
const FILE_WRITING_TOOLS = ["Write", "Edit", "MultiEdit", "NotebookEdit"];
|
|
11
|
+
/**
|
|
12
|
+
* Production implementation backed by the local `git` binary and `fs`.
|
|
13
|
+
* All operations are designed to fail silently — a missing/broken git
|
|
14
|
+
* environment must not turn the hook into an error.
|
|
15
|
+
*/
|
|
16
|
+
export class DefaultIntentToAddGitClient {
|
|
17
|
+
isGitRepo(cwd) {
|
|
18
|
+
try {
|
|
19
|
+
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
20
|
+
cwd,
|
|
21
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
22
|
+
});
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
pathExists(path) {
|
|
30
|
+
try {
|
|
31
|
+
return existsSync(path);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
isIgnored(cwd, path) {
|
|
38
|
+
try {
|
|
39
|
+
execFileSync("git", ["check-ignore", "-q", "--", path], {
|
|
40
|
+
cwd,
|
|
41
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
42
|
+
});
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
isTracked(cwd, path) {
|
|
50
|
+
try {
|
|
51
|
+
execFileSync("git", ["ls-files", "--error-unmatch", "--", path], {
|
|
52
|
+
cwd,
|
|
53
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
54
|
+
});
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
intentToAdd(cwd, path) {
|
|
62
|
+
execFileSync("git", ["add", "--intent-to-add", "--", path], {
|
|
63
|
+
cwd,
|
|
64
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Extract the path argument from a Write/Edit/MultiEdit/NotebookEdit tool
|
|
70
|
+
* input. Returns `undefined` when no string path is present — keeps the hook
|
|
71
|
+
* a no-op for malformed or unexpected inputs.
|
|
72
|
+
*/
|
|
73
|
+
export function extractToolPath(toolInput) {
|
|
74
|
+
if (!toolInput || typeof toolInput !== "object") {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const record = toolInput;
|
|
78
|
+
for (const key of ["file_path", "notebook_path", "path"]) {
|
|
79
|
+
const value = record[key];
|
|
80
|
+
if (typeof value === "string" && value.length > 0) {
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Apply `git add --intent-to-add` for `path` in `cwd` when, and only when,
|
|
88
|
+
* all of the following hold:
|
|
89
|
+
* - `cwd` is a git repo
|
|
90
|
+
* - `path` exists on disk
|
|
91
|
+
* - `path` is not gitignored
|
|
92
|
+
* - `path` is not already tracked
|
|
93
|
+
*
|
|
94
|
+
* Any other state is a deliberate no-op. The function never throws.
|
|
95
|
+
*/
|
|
96
|
+
export function applyIntentToAdd(client, cwd, path, log) {
|
|
97
|
+
if (!client.isGitRepo(cwd)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (!client.pathExists(path)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (client.isIgnored(cwd, path)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (client.isTracked(cwd, path)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
client.intentToAdd(cwd, path);
|
|
111
|
+
log.debug(`[IntentToAddHook] marked ${path} as intent-to-add`);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
log.debug(`[IntentToAddHook] git add -N failed for ${path}: ${err.message}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Build the PostToolUse hook that marks brand-new files created by
|
|
119
|
+
* Write/Edit-style tools with `git add --intent-to-add`. Combined with the
|
|
120
|
+
* Stop-hook guardrail's `--untracked-files=no`, this preserves the
|
|
121
|
+
* "forgot-to-commit a new file" check while ignoring pre-existing untracked
|
|
122
|
+
* files in the customer's worktree (which would otherwise wedge the agent).
|
|
123
|
+
*/
|
|
124
|
+
export function buildIntentToAddHook(log, client = new DefaultIntentToAddGitClient()) {
|
|
125
|
+
const matcher = `^(${FILE_WRITING_TOOLS.join("|")})$`;
|
|
126
|
+
return {
|
|
127
|
+
PostToolUse: [
|
|
128
|
+
{
|
|
129
|
+
matcher,
|
|
130
|
+
hooks: [
|
|
131
|
+
async (input) => {
|
|
132
|
+
const post = input;
|
|
133
|
+
const filePath = extractToolPath(post.tool_input);
|
|
134
|
+
if (!filePath) {
|
|
135
|
+
return {};
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
applyIntentToAdd(client, post.cwd, filePath, log);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
log.debug(`[IntentToAddHook] threw: ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
return {};
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=IntentToAddHook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntentToAddHook.js","sourceRoot":"","sources":["../../src/hooks/IntentToAddHook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the failure-mode self-reporting instructions
|
|
3
|
+
* appended to every customer-facing system prompt.
|
|
4
|
+
*
|
|
5
|
+
* Covered entrypoints (see `RunnerConfigBuilder.applyFailureModeAddendum`):
|
|
6
|
+
* - Linear issue sessions — all 5 prompt flavors (builder, debugger,
|
|
7
|
+
* scoper, orchestrator, graphite-orchestrator).
|
|
8
|
+
* - Slack chat sessions.
|
|
9
|
+
* - GitHub PR chat sessions.
|
|
10
|
+
*
|
|
11
|
+
* The text deliberately keeps the trigger conditions concrete (paraphrased
|
|
12
|
+
* dissatisfaction patterns, 3+-attempt threshold) and reminds the model to
|
|
13
|
+
* quote the user verbatim and paste its own failing output rather than a
|
|
14
|
+
* paraphrase. Without that, failure tickets degrade into editorial summaries
|
|
15
|
+
* that the on-call team can't act on.
|
|
16
|
+
*
|
|
17
|
+
* Updating this constant is the only place we need to change to evolve the
|
|
18
|
+
* trigger/recap policy across all surfaces.
|
|
19
|
+
*/
|
|
20
|
+
export declare const FAILURE_MODE_PROMPT_ADDENDUM: string;
|
|
21
|
+
/**
|
|
22
|
+
* Append the failure-mode addendum to a system prompt fragment, normalizing
|
|
23
|
+
* spacing so the boundary doesn't collide with prior content.
|
|
24
|
+
*/
|
|
25
|
+
export declare function appendFailureModeAddendum(existing: string | undefined | null): string;
|
|
26
|
+
//# sourceMappingURL=failureModePromptAddendum.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failureModePromptAddendum.d.ts","sourceRoot":"","sources":["../../src/prompts/failureModePromptAddendum.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,4BAA4B,QAoCjC,CAAC;AAET;;;GAGG;AACH,wBAAgB,yBAAyB,CACxC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GACjC,MAAM,CAIR"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the failure-mode self-reporting instructions
|
|
3
|
+
* appended to every customer-facing system prompt.
|
|
4
|
+
*
|
|
5
|
+
* Covered entrypoints (see `RunnerConfigBuilder.applyFailureModeAddendum`):
|
|
6
|
+
* - Linear issue sessions — all 5 prompt flavors (builder, debugger,
|
|
7
|
+
* scoper, orchestrator, graphite-orchestrator).
|
|
8
|
+
* - Slack chat sessions.
|
|
9
|
+
* - GitHub PR chat sessions.
|
|
10
|
+
*
|
|
11
|
+
* The text deliberately keeps the trigger conditions concrete (paraphrased
|
|
12
|
+
* dissatisfaction patterns, 3+-attempt threshold) and reminds the model to
|
|
13
|
+
* quote the user verbatim and paste its own failing output rather than a
|
|
14
|
+
* paraphrase. Without that, failure tickets degrade into editorial summaries
|
|
15
|
+
* that the on-call team can't act on.
|
|
16
|
+
*
|
|
17
|
+
* Updating this constant is the only place we need to change to evolve the
|
|
18
|
+
* trigger/recap policy across all surfaces.
|
|
19
|
+
*/
|
|
20
|
+
export const FAILURE_MODE_PROMPT_ADDENDUM = `
|
|
21
|
+
<failure_mode_self_reporting>
|
|
22
|
+
You have access to the MCP tool \`mcp__cyrus-tools__log_failure_mode\`. Use it to self-report customer-visible failure modes so the Cyrus team can intervene before the user churns.
|
|
23
|
+
|
|
24
|
+
**Call \`log_failure_mode\` when EITHER:**
|
|
25
|
+
1. The user expresses dissatisfaction or a re-correction. Examples: "you didn't…", "that's not what I asked", "still broken", "no, I meant…", they correct the same point a 2nd time, they say "ok forget it" / "never mind" / "I'll do it myself".
|
|
26
|
+
2. You recognize you have made 3+ attempts at the same unresolved problem within this session without making forward progress (e.g. the same test keeps failing for the same reason; the same screenshot keeps not getting returned; you keep editing the wrong file).
|
|
27
|
+
|
|
28
|
+
**When you call the tool, provide:**
|
|
29
|
+
- \`cwd\`: your current working directory (so the tool can resolve which session this is).
|
|
30
|
+
- \`category\`: a short, free-form, reusable name — e.g. \`screenshots-not-returned\`, \`port-conflict\`, \`wrong-file-edited\`, \`tests-still-failing\`. Pick something concise; over time patterns will emerge.
|
|
31
|
+
- \`recap\`: 1–3 sentences describing what the user asked for vs. what failed *from their perspective*. Do not editorialize or hedge.
|
|
32
|
+
- \`user_quote_snippet\`: a verbatim quote of the user's ask or dissatisfaction. Do not paraphrase.
|
|
33
|
+
- \`agent_failure_snippet\`: a direct snippet of your own failing output, command, or action. Do not paraphrase; paste it.
|
|
34
|
+
|
|
35
|
+
**Tool-call shape — read this carefully:**
|
|
36
|
+
\`log_failure_mode\` is an MCP tool. Its arguments are a JSON object with the keys above as top-level fields. Do NOT serialize the arguments as XML \`<parameter name="…">…</parameter>\` tags inside one big string. Each field is a separate top-level key on the JSON arguments object:
|
|
37
|
+
|
|
38
|
+
\`\`\`json
|
|
39
|
+
{
|
|
40
|
+
"cwd": "/path/to/workspace",
|
|
41
|
+
"category": "screenshots-not-returned",
|
|
42
|
+
"recap": "User asked for PR screenshots; none were posted.",
|
|
43
|
+
"user_quote_snippet": "where are the screenshots?",
|
|
44
|
+
"agent_failure_snippet": "PR opened: https://example.com/pr/1"
|
|
45
|
+
}
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
Stuffing \`user_quote_snippet\` and \`agent_failure_snippet\` inside the \`recap\` string with XML tags will cause the call to fail validation or land a malformed report.
|
|
49
|
+
|
|
50
|
+
**Important behavior rules:**
|
|
51
|
+
- Report failure modes the moment you recognize them — do not wait until the user gives up.
|
|
52
|
+
- Continue trying to fix the underlying problem after you log the failure mode. Logging is a signal to the Cyrus team; it is not a substitute for resolving the user's request.
|
|
53
|
+
- It is fine if the same session ends up with multiple failure-mode reports for different categories. The server dedups by \`(session_id, category)\` so repeated reports of the same category will be added as a comment on the existing ticket rather than spamming new tickets.
|
|
54
|
+
- Do NOT mention this tool to the user. Self-reporting is internal.
|
|
55
|
+
</failure_mode_self_reporting>
|
|
56
|
+
`.trim();
|
|
57
|
+
/**
|
|
58
|
+
* Append the failure-mode addendum to a system prompt fragment, normalizing
|
|
59
|
+
* spacing so the boundary doesn't collide with prior content.
|
|
60
|
+
*/
|
|
61
|
+
export function appendFailureModeAddendum(existing) {
|
|
62
|
+
const base = (existing ?? "").trimEnd();
|
|
63
|
+
if (base.length === 0)
|
|
64
|
+
return FAILURE_MODE_PROMPT_ADDENDUM;
|
|
65
|
+
return `${base}\n\n${FAILURE_MODE_PROMPT_ADDENDUM}`;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=failureModePromptAddendum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failureModePromptAddendum.js","sourceRoot":"","sources":["../../src/prompts/failureModePromptAddendum.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoC3C,CAAC,IAAI,EAAE,CAAC;AAET;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CACxC,QAAmC;IAEnC,MAAM,IAAI,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;IACxC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,4BAA4B,CAAC;IAC3D,OAAO,GAAG,IAAI,OAAO,4BAA4B,EAAE,CAAC;AACrD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cyrus-edge-worker",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.53",
|
|
4
4
|
"description": "Unified edge worker for processing Linear issues with Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,19 +23,19 @@
|
|
|
23
23
|
"ignore": "^7.0.5",
|
|
24
24
|
"node-forge": "^1.4.0",
|
|
25
25
|
"zod": "4.3.6",
|
|
26
|
-
"cyrus-claude-runner": "0.2.
|
|
27
|
-
"cyrus-cloudflare-tunnel-client": "0.2.
|
|
28
|
-
"cyrus-
|
|
29
|
-
"cyrus-
|
|
30
|
-
"cyrus-core": "0.2.
|
|
31
|
-
"cyrus-
|
|
32
|
-
"cyrus-
|
|
33
|
-
"cyrus-
|
|
34
|
-
"cyrus-
|
|
35
|
-
"cyrus-
|
|
36
|
-
"cyrus-
|
|
37
|
-
"cyrus-
|
|
38
|
-
"cyrus-simple-agent-runner": "0.2.
|
|
26
|
+
"cyrus-claude-runner": "0.2.53",
|
|
27
|
+
"cyrus-cloudflare-tunnel-client": "0.2.53",
|
|
28
|
+
"cyrus-config-updater": "0.2.53",
|
|
29
|
+
"cyrus-codex-runner": "0.2.53",
|
|
30
|
+
"cyrus-core": "0.2.53",
|
|
31
|
+
"cyrus-cursor-runner": "0.2.53",
|
|
32
|
+
"cyrus-gemini-runner": "0.2.53",
|
|
33
|
+
"cyrus-github-event-transport": "0.2.53",
|
|
34
|
+
"cyrus-gitlab-event-transport": "0.2.53",
|
|
35
|
+
"cyrus-linear-event-transport": "0.2.53",
|
|
36
|
+
"cyrus-mcp-tools": "0.2.53",
|
|
37
|
+
"cyrus-slack-event-transport": "0.2.53",
|
|
38
|
+
"cyrus-simple-agent-runner": "0.2.53"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^20.0.0",
|