mstro-app 0.4.29 → 0.4.32
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/server/cli/headless/haiku-assessments.d.ts.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.js +20 -28
- package/dist/server/cli/headless/haiku-assessments.js.map +1 -1
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +17 -3
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/prompt-builders.d.ts.map +1 -1
- package/dist/server/cli/prompt-builders.js +35 -19
- package/dist/server/cli/prompt-builders.js.map +1 -1
- package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-haiku.js +5 -30
- package/dist/server/mcp/bouncer-haiku.js.map +1 -1
- package/dist/server/mcp/security-analysis.d.ts.map +1 -1
- package/dist/server/mcp/security-analysis.js +19 -11
- package/dist/server/mcp/security-analysis.js.map +1 -1
- package/dist/server/services/deploy/headless-session-handler.d.ts.map +1 -1
- package/dist/server/services/deploy/headless-session-handler.js +61 -69
- package/dist/server/services/deploy/headless-session-handler.js.map +1 -1
- package/dist/server/services/pathUtils.d.ts.map +1 -1
- package/dist/server/services/pathUtils.js +46 -38
- package/dist/server/services/pathUtils.js.map +1 -1
- package/dist/server/services/plan/agent-loader.d.ts +20 -4
- package/dist/server/services/plan/agent-loader.d.ts.map +1 -1
- package/dist/server/services/plan/agent-loader.js +85 -16
- package/dist/server/services/plan/agent-loader.js.map +1 -1
- package/dist/server/services/plan/issue-retry.d.ts +0 -8
- package/dist/server/services/plan/issue-retry.d.ts.map +1 -1
- package/dist/server/services/plan/issue-retry.js +72 -63
- package/dist/server/services/plan/issue-retry.js.map +1 -1
- package/dist/server/services/plan/review-gate.js +16 -88
- package/dist/server/services/plan/review-gate.js.map +1 -1
- package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-handlers.js +6 -19
- package/dist/server/services/websocket/git-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.js +5 -21
- package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
- package/dist/server/services/websocket/handlers/deploy-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/handlers/deploy-handlers.js +28 -33
- package/dist/server/services/websocket/handlers/deploy-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.js +31 -25
- package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-fix-agent.js +11 -18
- package/dist/server/services/websocket/quality-fix-agent.js.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.js +13 -150
- package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
- package/package.json +1 -1
- package/server/cli/headless/haiku-assessments.ts +21 -28
- package/server/cli/headless/stall-assessor.ts +17 -3
- package/server/cli/prompt-builders.ts +34 -23
- package/server/mcp/bouncer-haiku.ts +5 -30
- package/server/mcp/security-analysis.ts +19 -12
- package/server/services/deploy/headless-session-handler.ts +75 -76
- package/server/services/pathUtils.ts +55 -42
- package/server/services/plan/agent-loader.ts +88 -15
- package/server/services/plan/issue-retry.ts +93 -68
- package/server/services/plan/review-gate.ts +13 -89
- package/server/services/websocket/git-handlers.ts +6 -18
- package/server/services/websocket/git-pr-handlers.ts +5 -20
- package/server/services/websocket/handlers/deploy-handlers.ts +34 -37
- package/server/services/websocket/plan-board-handlers.ts +36 -21
- package/server/services/websocket/quality-fix-agent.ts +10 -17
- package/server/services/websocket/quality-review-agent.ts +12 -149
|
@@ -68,6 +68,57 @@ function composePrompt(systemPrompt, userPrompt) {
|
|
|
68
68
|
userPrompt,
|
|
69
69
|
].join('\n');
|
|
70
70
|
}
|
|
71
|
+
// ========== Validation ==========
|
|
72
|
+
/** Validate request fields and deployment config. Returns an error or null if valid. */
|
|
73
|
+
function validateRequest(request, config) {
|
|
74
|
+
if (!request.prompt || request.prompt.trim().length === 0) {
|
|
75
|
+
return { code: 'INVALID_REQUEST', message: 'prompt is required and must not be empty.' };
|
|
76
|
+
}
|
|
77
|
+
if (!request.endUserId || request.endUserId.trim().length === 0) {
|
|
78
|
+
return { code: 'INVALID_REQUEST', message: 'endUserId is required.' };
|
|
79
|
+
}
|
|
80
|
+
if (!config.aiEnabled) {
|
|
81
|
+
return { code: 'AI_DISABLED', message: 'AI features are not enabled for this deployment.' };
|
|
82
|
+
}
|
|
83
|
+
if (!config.allowedAiCapabilities.includes('headless')) {
|
|
84
|
+
return {
|
|
85
|
+
code: 'CAPABILITY_DENIED',
|
|
86
|
+
message: "This deployment does not have the 'headless' AI capability enabled.",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
/** Check estimated input tokens against the per-request cap. Returns an error or null. */
|
|
92
|
+
function checkTokenLimit(promptLength, maxTokensPerRequest) {
|
|
93
|
+
if (maxTokensPerRequest === null)
|
|
94
|
+
return null;
|
|
95
|
+
const estimatedInputTokens = Math.ceil(promptLength / 4);
|
|
96
|
+
if (estimatedInputTokens > maxTokensPerRequest) {
|
|
97
|
+
return {
|
|
98
|
+
code: 'RATE_LIMIT_EXCEEDED',
|
|
99
|
+
message: `Estimated input tokens (${estimatedInputTokens}) exceeds maxTokensPerRequest (${maxTokensPerRequest}). Shorten your prompt.`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
/** Emit health update and usage report callbacks after execution. */
|
|
105
|
+
function emitPostExecutionCallbacks(result, config, request, effectiveModel, callbacks) {
|
|
106
|
+
callbacks?.onUsageReport?.({
|
|
107
|
+
deploymentId: config.deploymentId,
|
|
108
|
+
endUserId: request.endUserId,
|
|
109
|
+
capability: 'headless',
|
|
110
|
+
tokensUsed: result.totalTokens,
|
|
111
|
+
model: effectiveModel,
|
|
112
|
+
durationMs: result.durationMs,
|
|
113
|
+
});
|
|
114
|
+
const healthStatus = detectAiHealthIssue(result.error);
|
|
115
|
+
if (healthStatus) {
|
|
116
|
+
callbacks?.onHealthUpdate?.({
|
|
117
|
+
deploymentId: config.deploymentId,
|
|
118
|
+
...healthStatus,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
71
122
|
// ========== Handler ==========
|
|
72
123
|
/**
|
|
73
124
|
* Handle a headless session request for an end user.
|
|
@@ -80,55 +131,17 @@ function composePrompt(systemPrompt, userPrompt) {
|
|
|
80
131
|
*/
|
|
81
132
|
export async function handleHeadlessSession(request, config, callbacks) {
|
|
82
133
|
// ── Validate request ───────────────────────────────────────
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
error: { code: 'INVALID_REQUEST', message: 'prompt is required and must not be empty.' },
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
if (!request.endUserId || request.endUserId.trim().length === 0) {
|
|
90
|
-
return {
|
|
91
|
-
ok: false,
|
|
92
|
-
error: { code: 'INVALID_REQUEST', message: 'endUserId is required.' },
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
// ── Validate AI is enabled ─────────────────────────────────
|
|
96
|
-
if (!config.aiEnabled) {
|
|
97
|
-
return {
|
|
98
|
-
ok: false,
|
|
99
|
-
error: { code: 'AI_DISABLED', message: 'AI features are not enabled for this deployment.' },
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
// ── Validate headless capability ───────────────────────────
|
|
103
|
-
if (!config.allowedAiCapabilities.includes('headless')) {
|
|
104
|
-
return {
|
|
105
|
-
ok: false,
|
|
106
|
-
error: {
|
|
107
|
-
code: 'CAPABILITY_DENIED',
|
|
108
|
-
message: "This deployment does not have the 'headless' AI capability enabled.",
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
}
|
|
134
|
+
const validationError = validateRequest(request, config);
|
|
135
|
+
if (validationError)
|
|
136
|
+
return { ok: false, error: validationError };
|
|
112
137
|
// ── Rate limit checks ─────────────────────────────────────
|
|
113
138
|
const rateLimitError = checkRateLimit(config);
|
|
114
|
-
if (rateLimitError)
|
|
139
|
+
if (rateLimitError)
|
|
115
140
|
return { ok: false, error: rateLimitError };
|
|
116
|
-
}
|
|
117
141
|
// ── Token limit pre-check ─────────────────────────────────
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const estimatedInputTokens = Math.ceil(request.prompt.length / 4);
|
|
122
|
-
if (estimatedInputTokens > config.maxTokensPerRequest) {
|
|
123
|
-
return {
|
|
124
|
-
ok: false,
|
|
125
|
-
error: {
|
|
126
|
-
code: 'RATE_LIMIT_EXCEEDED',
|
|
127
|
-
message: `Estimated input tokens (${estimatedInputTokens}) exceeds maxTokensPerRequest (${config.maxTokensPerRequest}). Shorten your prompt.`,
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
}
|
|
142
|
+
const tokenError = checkTokenLimit(request.prompt.length, config.maxTokensPerRequest);
|
|
143
|
+
if (tokenError)
|
|
144
|
+
return { ok: false, error: tokenError };
|
|
132
145
|
// ── Compose prompt ─────────────────────────────────────────
|
|
133
146
|
// Use per-request system prompt if provided, otherwise deployment default
|
|
134
147
|
const effectiveSystemPrompt = request.systemPrompt ?? config.defaultSystemPrompt;
|
|
@@ -154,30 +167,9 @@ export async function handleHeadlessSession(request, config, callbacks) {
|
|
|
154
167
|
? invertAllowedTools(request.allowedTools)
|
|
155
168
|
: undefined,
|
|
156
169
|
});
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// Session already ran — log but don't fail the response.
|
|
161
|
-
// The token overage is informational; the developer can use this
|
|
162
|
-
// for billing or to tighten limits.
|
|
163
|
-
}
|
|
164
|
-
// Emit usage report after successful execution
|
|
165
|
-
callbacks?.onUsageReport?.({
|
|
166
|
-
deploymentId: config.deploymentId,
|
|
167
|
-
endUserId: request.endUserId,
|
|
168
|
-
capability: 'headless',
|
|
169
|
-
tokensUsed: result.totalTokens,
|
|
170
|
-
model: effectiveModel,
|
|
171
|
-
durationMs: result.durationMs,
|
|
172
|
-
});
|
|
173
|
-
// Check for API key health issues from execution result
|
|
174
|
-
const healthStatus = detectAiHealthIssue(result.error);
|
|
175
|
-
if (healthStatus) {
|
|
176
|
-
callbacks?.onHealthUpdate?.({
|
|
177
|
-
deploymentId: config.deploymentId,
|
|
178
|
-
...healthStatus,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
170
|
+
// Token overage is informational — session already ran, don't fail the response.
|
|
171
|
+
// The developer can use usage reports for billing or to tighten limits.
|
|
172
|
+
emitPostExecutionCallbacks(result, config, request, effectiveModel, callbacks);
|
|
181
173
|
return { ok: true, result };
|
|
182
174
|
}
|
|
183
175
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headless-session-handler.js","sourceRoot":"","sources":["../../../../server/services/deploy/headless-session-handler.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,gEAAgE;AAehE,OAAO,EAAE,eAAe,EAA8B,MAAM,wBAAwB,CAAC;AA8ErF,MAAM,WAAW,GAAG,IAAI,GAAG,EAAsB,CAAC;AAElD,SAAS,SAAS,CAAC,YAAoB;IACrC,IAAI,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC;QAC/C,WAAW,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,eAAe,CAAC,MAAkB;IACzC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;IACzC,wCAAwC;IACxC,OAAO,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,YAAY,EAAE,CAAC;QAC3E,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CACrB,MAA0B;IAE1B,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAE9C,4BAA4B;IAC5B,IAAI,MAAM,CAAC,cAAc,IAAI,MAAM,CAAC,qBAAqB,EAAE,CAAC;QAC1D,OAAO;YACL,IAAI,EAAE,2BAA2B;YACjC,OAAO,EAAE,yCAAyC,MAAM,CAAC,qBAAqB,iEAAiE;SAChJ,CAAC;IACJ,CAAC;IAED,4BAA4B;IAC5B,IAAI,MAAM,CAAC,oBAAoB,KAAK,IAAI,EAAE,CAAC;QACzC,eAAe,CAAC,MAAM,CAAC,CAAC;QACxB,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,IAAI,MAAM,CAAC,oBAAoB,EAAE,CAAC;YAC5D,OAAO;gBACL,IAAI,EAAE,qBAAqB;gBAC3B,OAAO,EAAE,6CAA6C,MAAM,CAAC,oBAAoB,0CAA0C;aAC5H,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,kBAAkB,CAAC,YAAoB;IAC9C,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC;IACvC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACnC,MAAM,CAAC,cAAc,EAAE,CAAC;AAC1B,CAAC;AAED,SAAS,gBAAgB,CAAC,YAAoB;IAC5C,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC;IACvC,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,2CAA2C;AAE3C;;;;;;;GAOG;AACH,SAAS,aAAa,CAAC,YAA2B,EAAE,UAAkB;IACpE,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO;QACL,sBAAsB;QACtB,YAAY;QACZ,uBAAuB;QACvB,EAAE;QACF,UAAU;KACX,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"headless-session-handler.js","sourceRoot":"","sources":["../../../../server/services/deploy/headless-session-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathUtils.d.ts","sourceRoot":"","sources":["../../../server/services/pathUtils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pathUtils.d.ts","sourceRoot":"","sources":["../../../server/services/pathUtils.ts"],"names":[],"mappings":"AA6DA,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,oBAAoB,CAkDtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAC/C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB;IACD,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CA0BA;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAS/D"}
|
|
@@ -8,6 +8,44 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync, lstatSync, realpathSync } from 'node:fs';
|
|
10
10
|
import { dirname, isAbsolute, normalize, relative, resolve } from 'node:path';
|
|
11
|
+
/** Append a trailing separator to a directory path if not already present. */
|
|
12
|
+
function ensureTrailingSep(dir) {
|
|
13
|
+
return dir.endsWith('/') ? dir : `${dir}/`;
|
|
14
|
+
}
|
|
15
|
+
/** Resolve symlinks for an existing path. Returns the real path if it's a symlink. */
|
|
16
|
+
function resolveExistingSymlink(resolvedPath) {
|
|
17
|
+
const stat = lstatSync(resolvedPath);
|
|
18
|
+
if (stat.isSymbolicLink()) {
|
|
19
|
+
return realpathSync(resolvedPath);
|
|
20
|
+
}
|
|
21
|
+
return resolvedPath;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Validate that the parent directory of a non-existent path hasn't escaped
|
|
25
|
+
* the working directory via symlink. Returns an error result or null if valid.
|
|
26
|
+
*/
|
|
27
|
+
function validateParentSymlink(resolvedPath, normalizedWorkingDir, targetPath) {
|
|
28
|
+
const parentDir = dirname(resolvedPath);
|
|
29
|
+
if (!existsSync(parentDir))
|
|
30
|
+
return null;
|
|
31
|
+
const realParent = realpathSync(parentDir);
|
|
32
|
+
const parentWithSep = ensureTrailingSep(normalizedWorkingDir);
|
|
33
|
+
if (realParent !== normalizedWorkingDir && !realParent.startsWith(parentWithSep)) {
|
|
34
|
+
console.error(`[PathUtils] SECURITY: Symlink traversal in parent directory blocked. ` +
|
|
35
|
+
`Target: "${targetPath}", RealParent: "${realParent}", WorkingDir: "${normalizedWorkingDir}"`);
|
|
36
|
+
return {
|
|
37
|
+
valid: false,
|
|
38
|
+
resolvedPath: '',
|
|
39
|
+
error: 'Access denied: parent directory resolves outside working directory'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
/** Check whether a resolved path is within the working directory boundary. */
|
|
45
|
+
function isPathWithinDir(resolvedPath, normalizedWorkingDir) {
|
|
46
|
+
return resolvedPath === normalizedWorkingDir ||
|
|
47
|
+
resolvedPath.startsWith(ensureTrailingSep(normalizedWorkingDir));
|
|
48
|
+
}
|
|
11
49
|
/**
|
|
12
50
|
* Validate that a path is within the allowed working directory.
|
|
13
51
|
* Prevents path traversal attacks using .. or absolute paths.
|
|
@@ -21,54 +59,24 @@ export function validatePathWithinWorkingDir(targetPath, workingDir) {
|
|
|
21
59
|
// Normalize the working directory to get canonical path
|
|
22
60
|
const normalizedWorkingDir = resolve(workingDir);
|
|
23
61
|
// Resolve the target path relative to working directory
|
|
24
|
-
let resolvedPath
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
resolvedPath = resolve(normalizedWorkingDir, targetPath);
|
|
30
|
-
}
|
|
62
|
+
let resolvedPath = isAbsolute(targetPath)
|
|
63
|
+
? resolve(targetPath)
|
|
64
|
+
: resolve(normalizedWorkingDir, targetPath);
|
|
31
65
|
// Normalize to remove any .. or . segments
|
|
32
66
|
resolvedPath = normalize(resolvedPath);
|
|
33
67
|
// Resolve symlinks to prevent symlink-based path traversal.
|
|
34
68
|
// A symlink at /project/link -> /etc/passwd would pass the string
|
|
35
69
|
// check below but actually read outside the working directory.
|
|
36
|
-
// For existing paths: resolve the full path via realpath.
|
|
37
|
-
// For new paths (create operations): resolve the parent directory.
|
|
38
70
|
if (existsSync(resolvedPath)) {
|
|
39
|
-
|
|
40
|
-
const stat = lstatSync(resolvedPath);
|
|
41
|
-
if (stat.isSymbolicLink()) {
|
|
42
|
-
resolvedPath = realpathSync(resolvedPath);
|
|
43
|
-
}
|
|
71
|
+
resolvedPath = resolveExistingSymlink(resolvedPath);
|
|
44
72
|
}
|
|
45
73
|
else {
|
|
46
74
|
// Path doesn't exist yet (create operation) — validate the parent
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
const parentWithSep = normalizedWorkingDir.endsWith('/')
|
|
51
|
-
? normalizedWorkingDir
|
|
52
|
-
: `${normalizedWorkingDir}/`;
|
|
53
|
-
if (realParent !== normalizedWorkingDir && !realParent.startsWith(parentWithSep)) {
|
|
54
|
-
console.error(`[PathUtils] SECURITY: Symlink traversal in parent directory blocked. ` +
|
|
55
|
-
`Target: "${targetPath}", RealParent: "${realParent}", WorkingDir: "${normalizedWorkingDir}"`);
|
|
56
|
-
return {
|
|
57
|
-
valid: false,
|
|
58
|
-
resolvedPath: '',
|
|
59
|
-
error: 'Access denied: parent directory resolves outside working directory'
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
}
|
|
75
|
+
const parentError = validateParentSymlink(resolvedPath, normalizedWorkingDir, targetPath);
|
|
76
|
+
if (parentError)
|
|
77
|
+
return parentError;
|
|
63
78
|
}
|
|
64
|
-
|
|
65
|
-
// Add trailing separator to prevent partial matches (e.g., /home/user vs /home/username)
|
|
66
|
-
const workingDirWithSep = normalizedWorkingDir.endsWith('/')
|
|
67
|
-
? normalizedWorkingDir
|
|
68
|
-
: `${normalizedWorkingDir}/`;
|
|
69
|
-
const isWithinWorkingDir = resolvedPath === normalizedWorkingDir ||
|
|
70
|
-
resolvedPath.startsWith(workingDirWithSep);
|
|
71
|
-
if (!isWithinWorkingDir) {
|
|
79
|
+
if (!isPathWithinDir(resolvedPath, normalizedWorkingDir)) {
|
|
72
80
|
// Log security violation for monitoring
|
|
73
81
|
console.error(`[PathUtils] SECURITY: Path traversal attempt blocked. ` +
|
|
74
82
|
`Target: "${targetPath}", Resolved: "${resolvedPath}", WorkingDir: "${normalizedWorkingDir}"`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathUtils.js","sourceRoot":"","sources":["../../../server/services/pathUtils.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,gEAAgE;AAEhE;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"pathUtils.js","sourceRoot":"","sources":["../../../server/services/pathUtils.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Load an agent prompt by name with layered resolution.
|
|
3
3
|
*
|
|
4
|
-
* @param agentName
|
|
5
|
-
* @param variables
|
|
6
|
-
* @param boardDir
|
|
4
|
+
* @param agentName - The agent file name without extension (e.g., "review-code")
|
|
5
|
+
* @param variables - Key-value map for {{variable}} substitution
|
|
6
|
+
* @param boardDir - Optional board directory for board-level overrides
|
|
7
|
+
* @param workingDir - Optional working directory for project-level Skill resolution
|
|
7
8
|
* @returns The interpolated prompt string, or null if no agent file found
|
|
8
9
|
*/
|
|
9
|
-
export declare function loadAgentPrompt(agentName: string, variables: Record<string, string>, boardDir?: string | null): string | null;
|
|
10
|
+
export declare function loadAgentPrompt(agentName: string, variables: Record<string, string>, boardDir?: string | null, workingDir?: string | null): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Load a Skill template body by name, stripping frontmatter.
|
|
13
|
+
* Looks in {workingDir}/.claude/skills/{skillName}/SKILL.md first,
|
|
14
|
+
* then falls back to the system agents directory.
|
|
15
|
+
*
|
|
16
|
+
* @param skillName - The skill directory name (e.g., "code-review")
|
|
17
|
+
* @param workingDir - Working directory for project-level Skill resolution
|
|
18
|
+
* @returns Raw template body (no frontmatter), or null if not found
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadSkillTemplate(skillName: string, workingDir?: string): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Load a Skill template and interpolate variables.
|
|
23
|
+
* Convenience wrapper combining loadSkillTemplate + interpolation.
|
|
24
|
+
*/
|
|
25
|
+
export declare function loadSkillPrompt(skillName: string, variables: Record<string, string>, workingDir?: string): string | null;
|
|
10
26
|
//# sourceMappingURL=agent-loader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-loader.d.ts","sourceRoot":"","sources":["../../../../server/services/plan/agent-loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-loader.d.ts","sourceRoot":"","sources":["../../../../server/services/plan/agent-loader.ts"],"names":[],"mappings":"AAgEA;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,EACxB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GACzB,MAAM,GAAG,IAAI,CAoBf;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsBvF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,GAAG,IAAI,CAIf"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
2
|
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
3
|
/**
|
|
4
|
-
* Agent Prompt Loader — loads review agent prompts from markdown files.
|
|
4
|
+
* Agent Prompt Loader — loads review agent prompts from Skills and markdown files.
|
|
5
5
|
*
|
|
6
6
|
* Resolution order (first match wins):
|
|
7
7
|
* 1. Board-level override: {boardDir}/agents/{agentName}.md
|
|
8
|
-
* 2.
|
|
8
|
+
* 2. Project Skill: {workingDir}/.claude/skills/{agentName}/SKILL.md
|
|
9
|
+
* 3. System default: cli/server/services/plan/agents/{agentName}.md
|
|
9
10
|
*
|
|
10
11
|
* Files use YAML frontmatter + markdown body with {{variable}} placeholders.
|
|
11
12
|
* Falls back to null when no file is found (caller should use hardcoded fallback).
|
|
@@ -30,36 +31,104 @@ function interpolate(template, variables) {
|
|
|
30
31
|
return key in variables ? variables[key] : match;
|
|
31
32
|
});
|
|
32
33
|
}
|
|
34
|
+
/** Try to load and interpolate a prompt file. Returns null on failure. */
|
|
35
|
+
function tryLoadFile(filePath, variables) {
|
|
36
|
+
if (!existsSync(filePath))
|
|
37
|
+
return null;
|
|
38
|
+
try {
|
|
39
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
40
|
+
return interpolate(stripFrontmatter(raw), variables);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the project root by walking up from a directory looking for `.claude/skills/`.
|
|
48
|
+
* Returns the `.claude/skills/` path if found, or null.
|
|
49
|
+
*/
|
|
50
|
+
function findSkillsDir(startDir) {
|
|
51
|
+
let dir = startDir;
|
|
52
|
+
for (let i = 0; i < 10; i++) {
|
|
53
|
+
const candidate = join(dir, '.claude', 'skills');
|
|
54
|
+
if (existsSync(candidate))
|
|
55
|
+
return candidate;
|
|
56
|
+
const parent = dirname(dir);
|
|
57
|
+
if (parent === dir)
|
|
58
|
+
break;
|
|
59
|
+
dir = parent;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
33
63
|
/**
|
|
34
64
|
* Load an agent prompt by name with layered resolution.
|
|
35
65
|
*
|
|
36
|
-
* @param agentName
|
|
37
|
-
* @param variables
|
|
38
|
-
* @param boardDir
|
|
66
|
+
* @param agentName - The agent file name without extension (e.g., "review-code")
|
|
67
|
+
* @param variables - Key-value map for {{variable}} substitution
|
|
68
|
+
* @param boardDir - Optional board directory for board-level overrides
|
|
69
|
+
* @param workingDir - Optional working directory for project-level Skill resolution
|
|
39
70
|
* @returns The interpolated prompt string, or null if no agent file found
|
|
40
71
|
*/
|
|
41
|
-
export function loadAgentPrompt(agentName, variables, boardDir) {
|
|
72
|
+
export function loadAgentPrompt(agentName, variables, boardDir, workingDir) {
|
|
42
73
|
const fileName = `${agentName}.md`;
|
|
43
74
|
// 1. Board-level override
|
|
44
75
|
if (boardDir) {
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
76
|
+
const result = tryLoadFile(join(boardDir, 'agents', fileName), variables);
|
|
77
|
+
if (result)
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
// 2. Project Skill: {workingDir}/.claude/skills/{agentName}/SKILL.md
|
|
81
|
+
if (workingDir) {
|
|
82
|
+
const skillsDir = findSkillsDir(workingDir);
|
|
83
|
+
if (skillsDir) {
|
|
84
|
+
const result = tryLoadFile(join(skillsDir, agentName, 'SKILL.md'), variables);
|
|
85
|
+
if (result)
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 3. System default
|
|
90
|
+
return tryLoadFile(join(SYSTEM_AGENTS_DIR, fileName), variables);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Load a Skill template body by name, stripping frontmatter.
|
|
94
|
+
* Looks in {workingDir}/.claude/skills/{skillName}/SKILL.md first,
|
|
95
|
+
* then falls back to the system agents directory.
|
|
96
|
+
*
|
|
97
|
+
* @param skillName - The skill directory name (e.g., "code-review")
|
|
98
|
+
* @param workingDir - Working directory for project-level Skill resolution
|
|
99
|
+
* @returns Raw template body (no frontmatter), or null if not found
|
|
100
|
+
*/
|
|
101
|
+
export function loadSkillTemplate(skillName, workingDir) {
|
|
102
|
+
if (workingDir) {
|
|
103
|
+
const skillsDir = findSkillsDir(workingDir);
|
|
104
|
+
if (skillsDir) {
|
|
105
|
+
const path = join(skillsDir, skillName, 'SKILL.md');
|
|
106
|
+
if (existsSync(path)) {
|
|
107
|
+
try {
|
|
108
|
+
return stripFrontmatter(readFileSync(path, 'utf-8'));
|
|
109
|
+
}
|
|
110
|
+
catch { /* fall through */ }
|
|
50
111
|
}
|
|
51
|
-
catch { /* fall through to system default */ }
|
|
52
112
|
}
|
|
53
113
|
}
|
|
54
|
-
//
|
|
55
|
-
const systemPath = join(SYSTEM_AGENTS_DIR,
|
|
114
|
+
// Fallback: system agents directory
|
|
115
|
+
const systemPath = join(SYSTEM_AGENTS_DIR, `${skillName}.md`);
|
|
56
116
|
if (existsSync(systemPath)) {
|
|
57
117
|
try {
|
|
58
|
-
|
|
59
|
-
return interpolate(stripFrontmatter(raw), variables);
|
|
118
|
+
return stripFrontmatter(readFileSync(systemPath, 'utf-8'));
|
|
60
119
|
}
|
|
61
120
|
catch { /* return null */ }
|
|
62
121
|
}
|
|
63
122
|
return null;
|
|
64
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Load a Skill template and interpolate variables.
|
|
126
|
+
* Convenience wrapper combining loadSkillTemplate + interpolation.
|
|
127
|
+
*/
|
|
128
|
+
export function loadSkillPrompt(skillName, variables, workingDir) {
|
|
129
|
+
const template = loadSkillTemplate(skillName, workingDir);
|
|
130
|
+
if (!template)
|
|
131
|
+
return null;
|
|
132
|
+
return interpolate(template, variables);
|
|
133
|
+
}
|
|
65
134
|
//# sourceMappingURL=agent-loader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-loader.js","sourceRoot":"","sources":["../../../../server/services/plan/agent-loader.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,gEAAgE;AAEhE
|
|
1
|
+
{"version":3,"file":"agent-loader.js","sourceRoot":"","sources":["../../../../server/services/plan/agent-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,13 +15,5 @@ export interface IssueRunnerConfig {
|
|
|
15
15
|
/** Signal to abort execution — when aborted, kills the running HeadlessRunner */
|
|
16
16
|
abortSignal?: AbortSignal;
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Execute a PM issue with retry logic.
|
|
20
|
-
*
|
|
21
|
-
* This wraps HeadlessRunner.run() with the same retry strategies as Chat view:
|
|
22
|
-
* 1. Tool timeout → checkpoint recovery with accumulated results
|
|
23
|
-
* 2. Signal crash → fresh start with preserved tool results
|
|
24
|
-
* 3. Premature completion → resume session with "continue"
|
|
25
|
-
*/
|
|
26
18
|
export declare function runIssueWithRetry(config: IssueRunnerConfig): Promise<SessionResult>;
|
|
27
19
|
//# sourceMappingURL=issue-retry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issue-retry.d.ts","sourceRoot":"","sources":["../../../../server/services/plan/issue-retry.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAuB,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAkCtF,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0DAA0D;IAC1D,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,iFAAiF;IACjF,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"issue-retry.d.ts","sourceRoot":"","sources":["../../../../server/services/plan/issue-retry.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAuB,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAkCtF,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0DAA0D;IAC1D,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,iFAAiF;IACjF,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAiGD,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,aAAa,CAAC,CAgCzF"}
|