@minionry/minion 0.7.36 → 0.7.39
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/bin/commands/minions.js +19 -0
- package/bin/minion.js +33 -4
- package/dist/server/cli/headless/claude-invoker-process.js +23 -7
- package/dist/server/cli/headless/claude-invoker-stream.js +8 -3
- package/dist/server/cli/headless/haiku-assessments.js +2 -1
- package/dist/server/cli/headless/mcp-config.js +2 -1
- package/dist/server/engines/claude/claude-command.js +1 -0
- package/dist/server/engines/factory.js +2 -0
- package/dist/server/engines/hermes/HermesEngine.js +112 -219
- package/dist/server/engines/hermes/hermes-gateway-registry.js +467 -0
- package/dist/server/index.js +10 -4
- package/dist/server/mcp/bouncer-integration.js +3 -2
- package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js +35 -8
- package/dist/server/mcp/classifier/shadow-eval-scheduler.js +42 -0
- package/dist/server/mcp/classifier/telemetry.js +0 -0
- package/dist/server/mcp/permission-channels.js +176 -0
- package/dist/server/mcp/security-analysis.js +9 -1
- package/dist/server/mcp/security-audit.js +22 -2
- package/dist/server/mcp/security-patterns.js +73 -1
- package/dist/server/mcp/server.js +80 -50
- package/dist/server/routes/internal.js +6 -1
- package/dist/server/routes/notifications.js +6 -18
- package/dist/server/server-setup.js +24 -48
- package/dist/server/services/analytics.js +4 -2
- package/dist/server/services/browser/host-mock.js +6 -1
- package/dist/server/services/browser/host.js +27 -3
- package/dist/server/services/browser/reasoning-agent.js +3 -1
- package/dist/server/services/chain/chain-engine.js +494 -0
- package/dist/server/services/chain/chain-store.js +542 -0
- package/dist/server/services/git/haiku.js +2 -2
- package/dist/server/services/plan/agents/check-injection.md +43 -2
- package/dist/server/services/plan/agents/review-code.md +62 -3
- package/dist/server/services/plan/agents/review-quality.md +52 -2
- package/dist/server/services/plan/board-export.js +4 -0
- package/dist/server/services/plan/composer-prompt.js +27 -8
- package/dist/server/services/plan/composer.js +10 -4
- package/dist/server/services/plan/config-installer.js +4 -83
- package/dist/server/services/plan/executor.js +155 -73
- package/dist/server/services/plan/issue-effort.js +9 -21
- package/dist/server/services/plan/issue-prompt-builder.js +72 -9
- package/dist/server/services/plan/parser-core.js +6 -1
- package/dist/server/services/plan/quality-delta.js +6 -24
- package/dist/server/services/plan/readiness-planner.js +12 -4
- package/dist/server/services/plan/record-file.js +52 -0
- package/dist/server/services/plan/review-approval.js +64 -0
- package/dist/server/services/plan/review-gate.js +186 -77
- package/dist/server/services/plan/review-outcome.js +146 -0
- package/dist/server/services/plan/review-report.js +28 -1
- package/dist/server/services/plan/review-target.js +57 -0
- package/dist/server/services/plan/state-reconciler.js +3 -3
- package/dist/server/services/plan/template-diff.js +2 -0
- package/dist/server/services/plan/template-instantiator.js +4 -0
- package/dist/server/services/plan/watcher.js +11 -1
- package/dist/server/services/platform-reconnect.js +7 -0
- package/dist/server/services/platform-token-lifecycle.js +2 -1
- package/dist/server/services/platform.js +293 -50
- package/dist/server/services/relay-http-client.js +107 -0
- package/dist/server/services/schedule/agent-schedule-store.js +69 -8
- package/dist/server/services/schedule/agent-scheduler.js +48 -7
- package/dist/server/services/schedule/daily-timing.js +52 -0
- package/dist/server/services/schedule/prompt-file.js +55 -0
- package/dist/server/services/schedule/run-bootstrap.js +20 -5
- package/dist/server/services/schedule/schedule-store.js +54 -4
- package/dist/server/services/schedule/schedule-wire.js +17 -0
- package/dist/server/services/schedule/scheduler.js +43 -9
- package/dist/server/services/sdk/agent-schedule.js +294 -0
- package/dist/server/services/sdk/agents.js +95 -13
- package/dist/server/services/sdk/app-tools.js +44 -7
- package/dist/server/services/sdk/atomic-write.js +75 -0
- package/dist/server/services/sdk/browser.js +5 -1
- package/dist/server/services/sdk/composer.js +2 -0
- package/dist/server/services/sdk/file-change-notify.js +60 -13
- package/dist/server/services/sdk/files-app.js +12 -7
- package/dist/server/services/sdk/files-search.js +14 -0
- package/dist/server/services/sdk/files-transfer.js +1 -1
- package/dist/server/services/sdk/files-watch.js +721 -0
- package/dist/server/services/sdk/files-workspace.js +37 -11
- package/dist/server/services/sdk/inference-claude.js +4 -2
- package/dist/server/services/sdk/inference.js +16 -2
- package/dist/server/services/sdk/pm-chain.js +0 -0
- package/dist/server/services/sdk/pm-schedule.js +72 -15
- package/dist/server/services/sdk/pm.js +22 -0
- package/dist/server/services/sdk/rate-limits.js +32 -0
- package/dist/server/services/sdk/registry.js +40 -2
- package/dist/server/services/sdk/terminal.js +1 -1
- package/dist/server/services/sentry.js +5 -1
- package/dist/server/services/terminal/pty-manager.js +26 -11
- package/dist/server/services/terminal/pty-utils.js +12 -1
- package/dist/server/services/terminal/terminal-modes.js +87 -0
- package/dist/server/services/timeline/assembler.js +2 -2
- package/dist/server/services/timeline/index.js +1 -1
- package/dist/server/services/websocket/agent-schedule-handlers.js +62 -4
- package/dist/server/services/websocket/ask-user-question-bridge.js +2 -2
- package/dist/server/services/websocket/browser-agent-runtime.js +3 -1
- package/dist/server/services/websocket/browser-handlers.js +5 -2
- package/dist/server/services/websocket/browser-viewer-lifecycle.js +8 -0
- package/dist/server/services/websocket/file-explorer-handlers.js +17 -0
- package/dist/server/services/websocket/file-transfer-http.js +12 -6
- package/dist/server/services/websocket/handler.js +185 -33
- package/dist/server/services/websocket/msg-id-tracker.js +94 -19
- package/dist/server/services/websocket/plan-execution-handlers.js +33 -5
- package/dist/server/services/websocket/plan-handlers.js +10 -5
- package/dist/server/services/websocket/plan-helpers.js +23 -0
- package/dist/server/services/websocket/plan-issue-handlers.js +161 -4
- package/dist/server/services/websocket/plan-sprint-handlers.js +3 -2
- package/dist/server/services/websocket/schedule-handlers.js +8 -2
- package/dist/server/services/websocket/sdk-agent-host.js +5 -1
- package/dist/server/services/websocket/sdk-agent-schedule-host.js +30 -0
- package/dist/server/services/websocket/sdk-browser-host.js +11 -4
- package/dist/server/services/websocket/sdk-handlers.js +6 -1
- package/dist/server/services/websocket/sdk-pm-chain-host.js +20 -0
- package/dist/server/services/websocket/sdk-pm-execution-host.js +21 -14
- package/dist/server/services/websocket/sdk-pm-schedule-host.js +9 -0
- package/dist/server/services/websocket/sdk-terminal-host.js +7 -0
- package/dist/server/services/websocket/settings-handlers.js +6 -18
- package/dist/server/services/websocket/tab-broadcast.js +5 -1
- package/dist/server/services/websocket/terminal-handlers.js +51 -6
- package/dist/server/services/websocket/types.js +6 -5
- package/dist/server/services/websocket/viewer-interest.js +30 -0
- package/package.json +6 -6
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
// SPDX-License-Identifier: LicenseRef-Minionry-Software
|
|
3
3
|
import { readdirSync, realpathSync, statSync } from 'node:fs';
|
|
4
4
|
import { basename, resolve, sep } from 'node:path';
|
|
5
|
-
import {
|
|
5
|
+
import { validatePathWithinRoots } from '../file-explorer-ops.js';
|
|
6
|
+
import { createDirectory, deleteFile, listDirectory, renameFile } from '../files.js';
|
|
7
|
+
import { containsDangerousPatterns } from '../pathUtils.js';
|
|
6
8
|
import { SPACE_DIRNAME, SPACE_LAYOUT } from '../space-layout.js';
|
|
7
9
|
import { readFileContent } from '../websocket/file-utils.js';
|
|
10
|
+
import { parseExpectedModifiedAt, writeFileAtomic } from './atomic-write.js';
|
|
8
11
|
import { notifyFileChangedAbsolute } from './file-change-notify.js';
|
|
9
|
-
import { FILES_APP_MAX_WRITE_BYTES, filesDeleteHandler, filesListHandler, filesMkdirHandler, filesReadEntryHandler, filesReadHandler, filesRenameHandler, filesWriteHandler, } from './files-app.js';
|
|
12
|
+
import { FILES_APP_MAX_WRITE_BYTES, filesDeleteHandler, filesListHandler, filesMkdirHandler, filesReadEntryHandler, filesReadHandler, filesRenameHandler, filesWriteHandler, readEntryResult, } from './files-app.js';
|
|
10
13
|
import { SdkMethodError } from './registry.js';
|
|
11
14
|
import { resolveWithinRoot } from './sandbox.js';
|
|
12
15
|
export const WORKSPACE_PATH_PREFIX = 'workspace:';
|
|
@@ -45,7 +48,7 @@ function inSpaceAppArea(segments) {
|
|
|
45
48
|
}
|
|
46
49
|
return false;
|
|
47
50
|
}
|
|
48
|
-
export function guardWorkspacePath(workingDir, rel) {
|
|
51
|
+
export function guardWorkspacePath(workingDir, rel, opts) {
|
|
49
52
|
let realWorkingDir;
|
|
50
53
|
try {
|
|
51
54
|
realWorkingDir = realpathSync(resolve(workingDir));
|
|
@@ -53,7 +56,7 @@ export function guardWorkspacePath(workingDir, rel) {
|
|
|
53
56
|
catch (error) {
|
|
54
57
|
throw new SdkMethodError('INTERNAL', `Space working directory unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
55
58
|
}
|
|
56
|
-
const guarded = resolveWithinRoot(realWorkingDir, rel);
|
|
59
|
+
const guarded = resolveWithinRoot(realWorkingDir, rel, opts);
|
|
57
60
|
if (!guarded.valid) {
|
|
58
61
|
throw new SdkMethodError('SCOPE_DENIED', 'Access denied: path escapes the Space workspace');
|
|
59
62
|
}
|
|
@@ -127,6 +130,17 @@ export function filesReadRoutedHandler(args) {
|
|
|
127
130
|
throw serviceError(result.error);
|
|
128
131
|
return result.content;
|
|
129
132
|
}
|
|
133
|
+
function refuseLikeWriteFile(resolvedPath, realWorkingDir) {
|
|
134
|
+
if (containsDangerousPatterns(resolvedPath)) {
|
|
135
|
+
console.error(`[FileService] SECURITY: Dangerous pattern in path: "${resolvedPath}"`);
|
|
136
|
+
throw serviceError('Invalid path: contains dangerous patterns');
|
|
137
|
+
}
|
|
138
|
+
const validation = validatePathWithinRoots(resolvedPath, realWorkingDir, []);
|
|
139
|
+
if (!validation.valid)
|
|
140
|
+
throw serviceError(validation.error);
|
|
141
|
+
if (statOrUndefined(resolvedPath)?.isDirectory())
|
|
142
|
+
throw serviceError('Cannot write to a directory');
|
|
143
|
+
}
|
|
130
144
|
export function filesWriteRoutedHandler(args) {
|
|
131
145
|
const target = parseWorkspacePath(args.params);
|
|
132
146
|
if (target === null)
|
|
@@ -135,14 +149,21 @@ export function filesWriteRoutedHandler(args) {
|
|
|
135
149
|
if (typeof raw.content !== 'string') {
|
|
136
150
|
throw new SdkMethodError('BAD_REQUEST', `${args.method} requires params { path, content } with string content`);
|
|
137
151
|
}
|
|
152
|
+
const expectedModifiedAt = parseExpectedModifiedAt(raw, args.method);
|
|
138
153
|
const bytes = Buffer.byteLength(raw.content, 'utf-8');
|
|
139
154
|
if (bytes > FILES_APP_MAX_WRITE_BYTES) {
|
|
140
155
|
throw new SdkMethodError('BAD_REQUEST', `content is ${bytes} bytes — exceeds the ${FILES_APP_MAX_WRITE_BYTES}-byte per-file files.write cap`);
|
|
141
156
|
}
|
|
142
157
|
const { realWorkingDir, resolvedPath } = guardWorkspacePath(args.workingDir, target.rel);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
158
|
+
refuseLikeWriteFile(resolvedPath, realWorkingDir);
|
|
159
|
+
try {
|
|
160
|
+
writeFileAtomic({ target: resolvedPath, content: raw.content, expectedModifiedAt, displayPath: target.original });
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
if (error instanceof SdkMethodError)
|
|
164
|
+
throw error;
|
|
165
|
+
throw serviceError(error instanceof Error ? error.message : undefined);
|
|
166
|
+
}
|
|
146
167
|
notifyFileChangedAbsolute(args.spaceKey, resolvedPath, 'changed');
|
|
147
168
|
return null;
|
|
148
169
|
}
|
|
@@ -182,9 +203,7 @@ export function filesReadEntryRoutedHandler(args) {
|
|
|
182
203
|
const result = readFileContent(resolvedPath, realWorkingDir);
|
|
183
204
|
if (result.error)
|
|
184
205
|
throw serviceError(result.error);
|
|
185
|
-
return result
|
|
186
|
-
? { kind: 'binary', content: result.content, mimeType: result.mimeType ?? 'application/octet-stream' }
|
|
187
|
-
: { kind: 'text', content: result.content };
|
|
206
|
+
return readEntryResult(result, stat);
|
|
188
207
|
}
|
|
189
208
|
export function filesMkdirRoutedHandler(args) {
|
|
190
209
|
const target = parseWorkspacePath(args.params);
|
|
@@ -194,6 +213,7 @@ export function filesMkdirRoutedHandler(args) {
|
|
|
194
213
|
const result = createDirectory(resolvedPath, realWorkingDir);
|
|
195
214
|
if (!result.success)
|
|
196
215
|
throw serviceError(result.error);
|
|
216
|
+
notifyFileChangedAbsolute(args.spaceKey, resolvedPath, 'created', { dir: true });
|
|
197
217
|
return null;
|
|
198
218
|
}
|
|
199
219
|
export function filesRenameRoutedHandler(args) {
|
|
@@ -216,6 +236,10 @@ export function filesRenameRoutedHandler(args) {
|
|
|
216
236
|
const result = renameFile(resolvedOld, resolvedNew, realWorkingDir);
|
|
217
237
|
if (!result.success)
|
|
218
238
|
throw serviceError(result.error);
|
|
239
|
+
notifyFileChangedAbsolute(args.spaceKey, resolvedOld, 'renamed', {
|
|
240
|
+
newPath: resolvedNew,
|
|
241
|
+
dir: statOrUndefined(resolvedNew)?.isDirectory(),
|
|
242
|
+
});
|
|
219
243
|
return null;
|
|
220
244
|
}
|
|
221
245
|
export function filesDeleteRoutedHandler(args) {
|
|
@@ -223,11 +247,13 @@ export function filesDeleteRoutedHandler(args) {
|
|
|
223
247
|
if (target === null)
|
|
224
248
|
return filesDeleteHandler(args);
|
|
225
249
|
const { realWorkingDir, resolvedPath } = guardWorkspacePath(args.workingDir, target.rel);
|
|
226
|
-
|
|
250
|
+
const stat = statOrUndefined(resolvedPath);
|
|
251
|
+
if (!stat)
|
|
227
252
|
throw notFound(target.original);
|
|
228
253
|
refuseAppAreaTree(resolvedPath);
|
|
229
254
|
const result = deleteFile(resolvedPath, realWorkingDir);
|
|
230
255
|
if (!result.success)
|
|
231
256
|
throw serviceError(result.error);
|
|
257
|
+
notifyFileChangedAbsolute(args.spaceKey, resolvedPath, 'deleted', { dir: stat.isDirectory() });
|
|
232
258
|
return null;
|
|
233
259
|
}
|
|
@@ -6,7 +6,7 @@ import { tmpdir } from 'node:os';
|
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { buildSpawnEnv, resolveClaudeEffort } from '../../cli/headless/claude-invoker-process.js';
|
|
8
8
|
import { classifyEngineNotice, isSyntheticModel } from '../../cli/headless/engine-notice.js';
|
|
9
|
-
import { resolveClaudeCommand } from '../../engines/claude/claude-command.js';
|
|
9
|
+
import { resolveClaudeCommand, TOOL_LESS_CLAUDE_ARGS } from '../../engines/claude/claude-command.js';
|
|
10
10
|
import { resolveModelProfile } from '../../engines/credentials/model-profiles.js';
|
|
11
11
|
import { SdkInferenceError, } from './inference.js';
|
|
12
12
|
const DEFAULT_SYSTEM_PROMPT = 'You are a helpful assistant.';
|
|
@@ -36,7 +36,9 @@ export function buildClaudeInferenceArgs(exec) {
|
|
|
36
36
|
const effort = resolveClaudeEffort(exec.model, exec.effortLevel ?? 'auto', true);
|
|
37
37
|
if (effort)
|
|
38
38
|
args.push('--effort', effort);
|
|
39
|
-
|
|
39
|
+
if (exec.fastMode)
|
|
40
|
+
args.push('--settings', JSON.stringify({ fastMode: true }));
|
|
41
|
+
args.push(...TOOL_LESS_CLAUDE_ARGS, '--no-session-persistence', '--system-prompt', exec.system?.trim() ? exec.system : DEFAULT_SYSTEM_PROMPT);
|
|
40
42
|
return args;
|
|
41
43
|
}
|
|
42
44
|
function parseStreamLine(line) {
|
|
@@ -79,7 +79,7 @@ function settleRun(record, result) {
|
|
|
79
79
|
}
|
|
80
80
|
function paramsObject(params) {
|
|
81
81
|
if (typeof params !== 'object' || params === null || Array.isArray(params)) {
|
|
82
|
-
throw new SdkMethodError('BAD_REQUEST', 'inference.chat requires params { messages, system?, engine?, model?, effortLevel? }');
|
|
82
|
+
throw new SdkMethodError('BAD_REQUEST', 'inference.chat requires params { messages, system?, engine?, model?, effortLevel?, fastMode? }');
|
|
83
83
|
}
|
|
84
84
|
return params;
|
|
85
85
|
}
|
|
@@ -122,6 +122,17 @@ function parseEffortLevel(raw, engine) {
|
|
|
122
122
|
}
|
|
123
123
|
return raw;
|
|
124
124
|
}
|
|
125
|
+
function parseFastMode(raw, engine) {
|
|
126
|
+
if (raw === undefined)
|
|
127
|
+
return false;
|
|
128
|
+
if (typeof raw !== 'boolean') {
|
|
129
|
+
throw new SdkMethodError('BAD_REQUEST', 'inference.chat fastMode must be a boolean');
|
|
130
|
+
}
|
|
131
|
+
if (raw && engine !== 'claude-code') {
|
|
132
|
+
throw new SdkMethodError('BAD_REQUEST', `inference.chat: fastMode is a claude-code setting — ${engine} has no fast mode`);
|
|
133
|
+
}
|
|
134
|
+
return raw;
|
|
135
|
+
}
|
|
125
136
|
export function inferenceBouncerInput(params) {
|
|
126
137
|
const raw = typeof params === 'object' && params !== null && !Array.isArray(params) ? params : {};
|
|
127
138
|
const messages = Array.isArray(raw.messages) ? raw.messages : [];
|
|
@@ -133,6 +144,7 @@ export function inferenceBouncerInput(params) {
|
|
|
133
144
|
engine: typeof raw.engine === 'string' ? raw.engine : 'default',
|
|
134
145
|
model: typeof raw.model === 'string' ? raw.model : 'default',
|
|
135
146
|
effortLevel: typeof raw.effortLevel === 'string' ? raw.effortLevel : 'auto',
|
|
147
|
+
...(raw.fastMode === true ? { fastMode: true } : {}),
|
|
136
148
|
messages: messages.length,
|
|
137
149
|
chars: contentChars + (typeof raw.system === 'string' ? raw.system.length : 0),
|
|
138
150
|
};
|
|
@@ -145,6 +157,7 @@ export function inferenceChatHandler(args) {
|
|
|
145
157
|
const messages = parseMessages(raw.messages);
|
|
146
158
|
const system = parseSystem(raw.system);
|
|
147
159
|
const { engine, model } = resolveAutomationTarget(raw.engine, raw.model, 'inference.chat');
|
|
160
|
+
const fastMode = parseFastMode(raw.fastMode, engine);
|
|
148
161
|
const executor = executors[engine];
|
|
149
162
|
if (!executor) {
|
|
150
163
|
throw new SdkMethodError('UNSUPPORTED', `inference.chat: ${engine} cannot run a tool-less completion on this host — choose one of ${inferenceEngines().join(', ') || '(none)'}`);
|
|
@@ -186,7 +199,7 @@ export function inferenceChatHandler(args) {
|
|
|
186
199
|
decision: 'allow',
|
|
187
200
|
requestId: args.requestId,
|
|
188
201
|
spaceKey: args.spaceKey,
|
|
189
|
-
reason: `run ${runId} engine=${engine} model=${model ?? 'default'} effort=${effortLevel ?? 'auto'} messages=${messages.length} [tool-less]`,
|
|
202
|
+
reason: `run ${runId} engine=${engine} model=${model ?? 'default'} effort=${effortLevel ?? 'auto'}${fastMode ? ' fastMode' : ''} messages=${messages.length} [tool-less]`,
|
|
190
203
|
});
|
|
191
204
|
setImmediate(() => {
|
|
192
205
|
if (record.outcome)
|
|
@@ -207,6 +220,7 @@ export function inferenceChatHandler(args) {
|
|
|
207
220
|
engine,
|
|
208
221
|
...(model ? { model } : {}),
|
|
209
222
|
...(effortLevel ? { effortLevel } : {}),
|
|
223
|
+
...(fastMode ? { fastMode } : {}),
|
|
210
224
|
...(system !== undefined ? { system } : {}),
|
|
211
225
|
messages,
|
|
212
226
|
signal: record.controller.signal,
|
|
Binary file
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Minionry, Inc.
|
|
2
2
|
// SPDX-License-Identifier: LicenseRef-Minionry-Software
|
|
3
|
+
import { basename } from 'node:path';
|
|
3
4
|
import { resolvePmDir } from '../plan/parser.js';
|
|
4
|
-
import { cleanupScheduleSource, isValidTiming, parseUpdatePatch, resolveCreateSource } from '../schedule/schedule-wire.js';
|
|
5
|
+
import { cleanupScheduleSource, isValidTiming, parseMaxLatenessMs, parseUpdatePatch, resolveCreateSource } from '../schedule/schedule-wire.js';
|
|
6
|
+
import { scaffoldPmDirectory } from '../websocket/plan-helpers.js';
|
|
5
7
|
import { logAppAudit } from './audit.js';
|
|
8
|
+
import { guardWorkspacePath, WORKSPACE_PATH_PREFIX } from './files-workspace.js';
|
|
6
9
|
import { parseBoardRunInput } from './pm.js';
|
|
7
10
|
import { SdkMethodError } from './registry.js';
|
|
8
11
|
function toScheduleSnapshot(record) {
|
|
@@ -21,6 +24,8 @@ function toScheduleSnapshot(record) {
|
|
|
21
24
|
snapshot.input = record.input;
|
|
22
25
|
if (record.lastFire !== undefined)
|
|
23
26
|
snapshot.lastFire = { ...record.lastFire };
|
|
27
|
+
if (record.maxLatenessMs !== undefined)
|
|
28
|
+
snapshot.maxLatenessMs = record.maxLatenessMs;
|
|
24
29
|
return snapshot;
|
|
25
30
|
}
|
|
26
31
|
let scheduleHost = null;
|
|
@@ -51,19 +56,41 @@ function parseScheduleIdParams(params, method) {
|
|
|
51
56
|
function scheduleNotFound(scheduleId) {
|
|
52
57
|
return new SdkMethodError('BAD_REQUEST', `schedule '${scheduleId}' not found in this Space`);
|
|
53
58
|
}
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
function resolveBoardZipPathParam(workingDir, raw, method) {
|
|
60
|
+
if (typeof raw !== 'string' || !raw.startsWith(WORKSPACE_PATH_PREFIX)) {
|
|
61
|
+
throw new SdkMethodError('BAD_REQUEST', `${method}: a board-zip source.path must be a workspace-rooted path ('${WORKSPACE_PATH_PREFIX}/<path>')`);
|
|
62
|
+
}
|
|
63
|
+
const rel = raw.slice(WORKSPACE_PATH_PREFIX.length).replace(/^\/+/, '');
|
|
64
|
+
if (rel === '' || rel.split('/').some((segment) => segment === '..')) {
|
|
65
|
+
throw new SdkMethodError('BAD_REQUEST', `${method}: source.path '${raw}' must not contain a '..' segment`);
|
|
66
|
+
}
|
|
67
|
+
return guardWorkspacePath(workingDir, rel).resolvedPath;
|
|
68
|
+
}
|
|
69
|
+
function parseCreateSourceParam(rawSource, workingDir, method) {
|
|
70
|
+
const source = paramsObject(rawSource);
|
|
71
|
+
if (source.kind === 'template-json') {
|
|
72
|
+
if (typeof source.content !== 'string' || source.content === '') {
|
|
73
|
+
throw new SdkMethodError('BAD_REQUEST', `${method} requires params.source.content for a 'template-json' source`);
|
|
74
|
+
}
|
|
75
|
+
return { kind: 'template-json', content: source.content };
|
|
59
76
|
}
|
|
77
|
+
if (source.kind === 'board-zip') {
|
|
78
|
+
return { kind: 'board-zip', path: resolveBoardZipPathParam(workingDir, source.path, method) };
|
|
79
|
+
}
|
|
80
|
+
throw new SdkMethodError('BAD_REQUEST', `${method} requires params.source of kind 'template-json' (with content) or 'board-zip' (with a workspace:-rooted path)`);
|
|
81
|
+
}
|
|
82
|
+
export function resolvePmSchedulesCreateScopes(params) {
|
|
83
|
+
const raw = paramsObject(params);
|
|
84
|
+
const source = paramsObject(raw.source);
|
|
85
|
+
return source.kind === 'board-zip' ? ['pm:schedule', 'files:workspace:read'] : ['pm:schedule'];
|
|
86
|
+
}
|
|
87
|
+
function parseCreateParams(args) {
|
|
88
|
+
const raw = paramsObject(args.params);
|
|
89
|
+
const source = parseCreateSourceParam(raw.source, args.workingDir, args.method);
|
|
60
90
|
if (!isValidTiming(raw.timing)) {
|
|
61
91
|
throw new SdkMethodError('BAD_REQUEST', 'pm.schedules.create requires a valid params.timing');
|
|
62
92
|
}
|
|
63
|
-
const parsed = {
|
|
64
|
-
source: { kind: 'template-json', content: rawSource.content },
|
|
65
|
-
timing: raw.timing,
|
|
66
|
-
};
|
|
93
|
+
const parsed = { source, timing: raw.timing };
|
|
67
94
|
if (typeof raw.name === 'string')
|
|
68
95
|
parsed.name = raw.name;
|
|
69
96
|
if (raw.enabled !== undefined) {
|
|
@@ -73,6 +100,12 @@ function parseCreateParams(params) {
|
|
|
73
100
|
}
|
|
74
101
|
if (raw.input !== undefined)
|
|
75
102
|
parsed.input = parseBoardRunInput(raw.input, 'pm.schedules.create');
|
|
103
|
+
const maxLateness = parseMaxLatenessMs(raw.maxLatenessMs);
|
|
104
|
+
if (!maxLateness.ok) {
|
|
105
|
+
throw new SdkMethodError('BAD_REQUEST', 'pm.schedules.create: maxLatenessMs must be an integer >= the schedule minimum');
|
|
106
|
+
}
|
|
107
|
+
if (maxLateness.value !== undefined)
|
|
108
|
+
parsed.maxLatenessMs = maxLateness.value;
|
|
76
109
|
return parsed;
|
|
77
110
|
}
|
|
78
111
|
function parseUpdateParams(params) {
|
|
@@ -99,10 +132,14 @@ export function pmSchedulesListHandler(args) {
|
|
|
99
132
|
}
|
|
100
133
|
export function pmSchedulesCreateHandler(args) {
|
|
101
134
|
const host = requireScheduleHost(args.method);
|
|
102
|
-
const { name, source, timing, input, enabled } = parseCreateParams(args
|
|
103
|
-
|
|
135
|
+
const { name, source, timing, input, enabled, maxLatenessMs } = parseCreateParams(args);
|
|
136
|
+
let pmDir = resolvePmDir(args.workingDir);
|
|
137
|
+
if (!pmDir) {
|
|
138
|
+
scaffoldPmDirectory(args.workingDir, basename(args.workingDir) || 'My Project');
|
|
139
|
+
pmDir = resolvePmDir(args.workingDir);
|
|
140
|
+
}
|
|
104
141
|
if (!pmDir) {
|
|
105
|
-
throw new SdkMethodError('
|
|
142
|
+
throw new SdkMethodError('INTERNAL', 'pm.schedules.create: failed to initialize the Space PM directory');
|
|
106
143
|
}
|
|
107
144
|
let resolved;
|
|
108
145
|
try {
|
|
@@ -113,9 +150,9 @@ export function pmSchedulesCreateHandler(args) {
|
|
|
113
150
|
}
|
|
114
151
|
const store = host.getStore(args.workingDir);
|
|
115
152
|
if (!store) {
|
|
116
|
-
throw new SdkMethodError('
|
|
153
|
+
throw new SdkMethodError('INTERNAL', 'pm.schedules.create: PM directory initialization did not take effect');
|
|
117
154
|
}
|
|
118
|
-
const record = store.create({ name, source: resolved.source, input, timing, enabled });
|
|
155
|
+
const record = store.create({ name, source: resolved.source, input, timing, enabled, maxLatenessMs });
|
|
119
156
|
host.notifyAndBroadcast(args.workingDir);
|
|
120
157
|
logAppAudit({
|
|
121
158
|
appId: args.appId,
|
|
@@ -163,3 +200,23 @@ export function pmSchedulesDeleteHandler(args) {
|
|
|
163
200
|
});
|
|
164
201
|
return null;
|
|
165
202
|
}
|
|
203
|
+
export function pmSchedulesRunNowHandler(args) {
|
|
204
|
+
const host = requireScheduleHost(args.method);
|
|
205
|
+
const scheduleId = parseScheduleIdParams(args.params, args.method);
|
|
206
|
+
const result = host.fireNow(args.workingDir, scheduleId);
|
|
207
|
+
if (result.kind === 'not-found')
|
|
208
|
+
throw scheduleNotFound(scheduleId);
|
|
209
|
+
if (result.kind === 'disabled') {
|
|
210
|
+
throw new SdkMethodError('BAD_REQUEST', `schedule '${scheduleId}' is disabled — enable it before running it now`);
|
|
211
|
+
}
|
|
212
|
+
logAppAudit({
|
|
213
|
+
appId: args.appId,
|
|
214
|
+
origin: args.origin,
|
|
215
|
+
method: 'pm.schedules.runNow.fired',
|
|
216
|
+
decision: 'allow',
|
|
217
|
+
requestId: args.requestId,
|
|
218
|
+
spaceKey: args.spaceKey,
|
|
219
|
+
reason: `schedule ${scheduleId} fired on demand`,
|
|
220
|
+
});
|
|
221
|
+
return toScheduleSnapshot(result.record);
|
|
222
|
+
}
|
|
@@ -265,6 +265,27 @@ function issueNotFound(boardId, issueId) {
|
|
|
265
265
|
function badRequest(message) {
|
|
266
266
|
return new SdkMethodError('BAD_REQUEST', message);
|
|
267
267
|
}
|
|
268
|
+
function assertReviewGateNotBypassed(args, issue, fields) {
|
|
269
|
+
if (issue.reviewGate !== 'required' || fields.status === undefined)
|
|
270
|
+
return;
|
|
271
|
+
const completesGate = fields.status === 'done';
|
|
272
|
+
const parked = issue.status === 'awaiting_approval';
|
|
273
|
+
if (!completesGate && !parked)
|
|
274
|
+
return;
|
|
275
|
+
const reason = completesGate
|
|
276
|
+
? `${issue.id} is review_gate: required — an app cannot set it to done; approval is only possible from the Specs view`
|
|
277
|
+
: `${issue.id} is awaiting approval — its status can only be changed from the Specs view`;
|
|
278
|
+
logAppAudit({
|
|
279
|
+
appId: args.appId,
|
|
280
|
+
origin: args.origin,
|
|
281
|
+
method: 'pm.updateIssue.blocked',
|
|
282
|
+
decision: 'deny',
|
|
283
|
+
requestId: args.requestId,
|
|
284
|
+
spaceKey: args.spaceKey,
|
|
285
|
+
reason,
|
|
286
|
+
});
|
|
287
|
+
throw badRequest(reason);
|
|
288
|
+
}
|
|
268
289
|
function assertYamlSafe(value, field, method, listItem = false) {
|
|
269
290
|
if (/[\r\n]/.test(value))
|
|
270
291
|
throw badRequest(`${method} ${field} must not contain line breaks`);
|
|
@@ -589,6 +610,7 @@ export function pmUpdateIssueHandler(args) {
|
|
|
589
610
|
const issue = full.issues.find((candidate) => candidate.id === issueId);
|
|
590
611
|
if (!issue)
|
|
591
612
|
throw issueNotFound(boardId, issueId);
|
|
613
|
+
assertReviewGateNotBypassed(args, issue, fields);
|
|
592
614
|
const fullPath = resolvePlanPath(args.workingDir, issue.path);
|
|
593
615
|
if (!fullPath || !existsSync(fullPath))
|
|
594
616
|
throw issueNotFound(boardId, issueId);
|
|
@@ -10,6 +10,8 @@ export const REQUEST_BUCKET_REFILL_PER_SECOND = 15;
|
|
|
10
10
|
export const AI_ASSIST_BUCKET_CAPACITY = 3;
|
|
11
11
|
export const AI_ASSIST_REFILL_PER_SECOND = 1 / 30;
|
|
12
12
|
export const DEFAULT_INFERENCE_CONCURRENCY = 2;
|
|
13
|
+
export const CHAIN_WRITE_BUCKET_CAPACITY = 10;
|
|
14
|
+
export const CHAIN_WRITE_REFILL_PER_SECOND = 1 / 6;
|
|
13
15
|
function ledgerKey(appId, spaceKey) {
|
|
14
16
|
return `${appId}\u0000${spaceKey}`;
|
|
15
17
|
}
|
|
@@ -20,12 +22,14 @@ const pendingStarts = new Map();
|
|
|
20
22
|
const pendingInferenceStarts = new Map();
|
|
21
23
|
const requestBuckets = new Map();
|
|
22
24
|
const aiAssistBuckets = new Map();
|
|
25
|
+
const chainWriteBuckets = new Map();
|
|
23
26
|
const sessionUsage = new Map();
|
|
24
27
|
export function resetSdkRateLimitState() {
|
|
25
28
|
pendingStarts.clear();
|
|
26
29
|
pendingInferenceStarts.clear();
|
|
27
30
|
requestBuckets.clear();
|
|
28
31
|
aiAssistBuckets.clear();
|
|
32
|
+
chainWriteBuckets.clear();
|
|
29
33
|
sessionUsage.clear();
|
|
30
34
|
}
|
|
31
35
|
function intKnob(value, fallback) {
|
|
@@ -79,6 +83,25 @@ function throttleAiAssist(appId, nowMs) {
|
|
|
79
83
|
bucket.tokens -= 1;
|
|
80
84
|
return false;
|
|
81
85
|
}
|
|
86
|
+
function throttleChainWrite(appId, nowMs) {
|
|
87
|
+
let bucket = chainWriteBuckets.get(appId);
|
|
88
|
+
if (!bucket) {
|
|
89
|
+
bucket = { tokens: CHAIN_WRITE_BUCKET_CAPACITY, lastRefillMs: nowMs };
|
|
90
|
+
chainWriteBuckets.set(appId, bucket);
|
|
91
|
+
}
|
|
92
|
+
const elapsedMs = Math.max(0, nowMs - bucket.lastRefillMs);
|
|
93
|
+
bucket.tokens = Math.min(CHAIN_WRITE_BUCKET_CAPACITY, bucket.tokens + (elapsedMs / 1000) * CHAIN_WRITE_REFILL_PER_SECOND);
|
|
94
|
+
bucket.lastRefillMs = nowMs;
|
|
95
|
+
if (bucket.tokens < 1)
|
|
96
|
+
return true;
|
|
97
|
+
bucket.tokens -= 1;
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
export const CHAIN_WRITE_METHODS = new Set([
|
|
101
|
+
'pm.chains.create',
|
|
102
|
+
'pm.chains.update',
|
|
103
|
+
'pm.chains.delete',
|
|
104
|
+
]);
|
|
82
105
|
const ENGINE_RUN_METHODS = new Set([
|
|
83
106
|
'agents.run',
|
|
84
107
|
'browser.automate',
|
|
@@ -183,6 +206,15 @@ export function checkSdkRateLimit(op, now = Date.now) {
|
|
|
183
206
|
}
|
|
184
207
|
return null;
|
|
185
208
|
}
|
|
209
|
+
if (CHAIN_WRITE_METHODS.has(op.method)) {
|
|
210
|
+
if (throttleChainWrite(op.appId, now())) {
|
|
211
|
+
return {
|
|
212
|
+
limited: true,
|
|
213
|
+
message: `pm.chains write rate limit reached (burst ${CHAIN_WRITE_BUCKET_CAPACITY}, then one every 6s); retry shortly`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
186
218
|
if (throttleRequest(op.appId, now())) {
|
|
187
219
|
return {
|
|
188
220
|
limited: true,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Minionry, Inc.
|
|
2
2
|
// SPDX-License-Identifier: LicenseRef-Minionry-Software
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
+
import { agentSchedulesCreateHandler, agentSchedulesDeleteHandler, agentSchedulesListHandler, agentSchedulesRunNowHandler, agentSchedulesUpdateHandler, resolveAgentSchedulesScopes, } from './agent-schedule.js';
|
|
4
5
|
import { agentsSessionsAnswerQuestionHandler, agentsSessionsApproveHandler, agentsSessionsAttachHandler, agentsSessionsCloseHandler, agentsSessionsCreateHandler, agentsSessionsHistoryHandler, agentsSessionsListHandler, agentsSessionsPromptHandler, agentsSessionsRenameHandler, agentsSessionsReplayHandler, agentsSessionsStopHandler, } from './agent-sessions.js';
|
|
5
6
|
import { agentsResultHandler, agentsRunAdmit, agentsRunBouncerInput, agentsRunHandler, resolveAgentsRunScopes, sdkCancelHandler, } from './agents.js';
|
|
6
7
|
import { toolsRegisterHandler, toolsResultHandler } from './app-tools.js';
|
|
@@ -16,8 +17,9 @@ import { auditRecentHandler, listGrantsHandler } from './host-ops.js';
|
|
|
16
17
|
import { inferenceBouncerInput, inferenceChatHandler, inferenceResultHandler } from './inference.js';
|
|
17
18
|
import { getLiveSubscriptionsForApp } from './live-registry.js';
|
|
18
19
|
import { pmCreateBoardHandler, pmCreateIssueHandler, pmDeleteIssueHandler, pmGetBoardHandler, pmListBoardsHandler, pmOnBoardUpdateHandler, pmUpdateBoardHandler, pmUpdateIssueHandler, } from './pm.js';
|
|
20
|
+
import { pmChainsCreateHandler, pmChainsDeleteHandler, pmChainsListHandler, pmChainsUpdateHandler, } from './pm-chain.js';
|
|
19
21
|
import { pmExecuteHandler, pmPauseHandler, pmResumeHandler, pmStopHandler } from './pm-execute.js';
|
|
20
|
-
import { pmSchedulesCreateHandler, pmSchedulesDeleteHandler, pmSchedulesListHandler, pmSchedulesUpdateHandler, } from './pm-schedule.js';
|
|
22
|
+
import { pmSchedulesCreateHandler, pmSchedulesDeleteHandler, pmSchedulesListHandler, pmSchedulesRunNowHandler, pmSchedulesUpdateHandler, resolvePmSchedulesCreateScopes, } from './pm-schedule.js';
|
|
21
23
|
import { qualityCancelHandler, qualityDetectToolsHandler, qualityInstallToolsHandler, qualityResultHandler, qualityRunHandler, qualitySaveDirectoriesHandler, qualitySetFindingStateHandler, qualitySetSettingsHandler, qualitySettingsHandler, qualityStateHandler, qualitySubscribeChangesHandler, } from './quality.js';
|
|
22
24
|
import { qualitySchedulesCreateHandler, qualitySchedulesDeleteHandler, qualitySchedulesListHandler, qualitySchedulesUpdateHandler, } from './quality-schedule.js';
|
|
23
25
|
import { dropSubscription, revokeGrantsHandler } from './revoke.js';
|
|
@@ -29,6 +31,7 @@ export const SDK_WRITE_CAPABILITY = {
|
|
|
29
31
|
sdkPmWrite: 'editPlan',
|
|
30
32
|
sdkPmExecute: 'editPlan',
|
|
31
33
|
sdkPmSchedule: 'editPlan',
|
|
34
|
+
sdkPmChain: 'editPlan',
|
|
32
35
|
sdkFilesWrite: 'editFiles',
|
|
33
36
|
sdkStorageWrite: 'editFiles',
|
|
34
37
|
sdkCancel: 'execute',
|
|
@@ -85,7 +88,17 @@ function sdkFeaturesHandler() {
|
|
|
85
88
|
'agentsAppEndpoint',
|
|
86
89
|
'qualityFindingState',
|
|
87
90
|
'agentsModelSelection',
|
|
91
|
+
'filesChangeKinds',
|
|
92
|
+
'filesWatch',
|
|
93
|
+
'filesConditionalWrite',
|
|
94
|
+
'inferenceFastMode',
|
|
88
95
|
'qualitySchedules',
|
|
96
|
+
'pmChains',
|
|
97
|
+
'agentsEngineSelection',
|
|
98
|
+
'agentSchedules',
|
|
99
|
+
'scheduleWallClock',
|
|
100
|
+
'pmScheduleZip',
|
|
101
|
+
'pmScheduleRunNow',
|
|
89
102
|
];
|
|
90
103
|
}
|
|
91
104
|
function notificationsUnsupportedHandler() {
|
|
@@ -264,6 +277,8 @@ export const SDK_METHOD_REGISTRY = {
|
|
|
264
277
|
'pm.listBoards': { scopes: ['pm:read'], type: 'sdkRequest', handler: pmListBoardsHandler },
|
|
265
278
|
'pm.subscribeBoard': { scopes: ['pm:read'], type: 'sdkRequest', handler: pmOnBoardUpdateHandler },
|
|
266
279
|
'pm.schedules.list': { scopes: ['pm:schedule'], type: 'sdkRequest', handler: pmSchedulesListHandler },
|
|
280
|
+
'pm.chains.list': { scopes: ['pm:chain'], type: 'sdkRequest', handler: pmChainsListHandler },
|
|
281
|
+
'agents.schedules.list': { scopes: ['agents:schedule'], type: 'sdkRequest', handler: agentSchedulesListHandler },
|
|
267
282
|
'agents.result': { scopes: ['agents:run'], type: 'sdkRequest', handler: agentsResultHandler },
|
|
268
283
|
'inference.result': { scopes: ['inference:run'], type: 'sdkRequest', handler: inferenceResultHandler },
|
|
269
284
|
'endpoints.list': { scopes: ['endpoints:register'], type: 'sdkRequest', handler: endpointsListHandler },
|
|
@@ -367,9 +382,18 @@ export const SDK_METHOD_REGISTRY = {
|
|
|
367
382
|
'pm.pause': { scopes: ['pm:execute'], type: 'sdkPmExecute', handler: pmPauseHandler },
|
|
368
383
|
'pm.resume': { scopes: ['pm:execute'], type: 'sdkPmExecute', handler: pmResumeHandler },
|
|
369
384
|
'pm.stop': { scopes: ['pm:execute'], type: 'sdkPmExecute', handler: pmStopHandler },
|
|
370
|
-
'pm.schedules.create': {
|
|
385
|
+
'pm.schedules.create': {
|
|
386
|
+
scopes: ['pm:schedule'],
|
|
387
|
+
resolveScopes: resolvePmSchedulesCreateScopes,
|
|
388
|
+
type: 'sdkPmSchedule',
|
|
389
|
+
handler: pmSchedulesCreateHandler,
|
|
390
|
+
},
|
|
371
391
|
'pm.schedules.update': { scopes: ['pm:schedule'], type: 'sdkPmSchedule', handler: pmSchedulesUpdateHandler },
|
|
372
392
|
'pm.schedules.delete': { scopes: ['pm:schedule'], type: 'sdkPmSchedule', handler: pmSchedulesDeleteHandler },
|
|
393
|
+
'pm.schedules.runNow': { scopes: ['pm:schedule'], type: 'sdkPmSchedule', handler: pmSchedulesRunNowHandler },
|
|
394
|
+
'pm.chains.create': { scopes: ['pm:chain'], type: 'sdkPmChain', handler: pmChainsCreateHandler },
|
|
395
|
+
'pm.chains.update': { scopes: ['pm:chain'], type: 'sdkPmChain', handler: pmChainsUpdateHandler },
|
|
396
|
+
'pm.chains.delete': { scopes: ['pm:chain'], type: 'sdkPmChain', handler: pmChainsDeleteHandler },
|
|
373
397
|
'files.write': {
|
|
374
398
|
scopes: ['files:app'],
|
|
375
399
|
resolveScopes: resolveFilesWriteScopes,
|
|
@@ -415,6 +439,20 @@ export const SDK_METHOD_REGISTRY = {
|
|
|
415
439
|
},
|
|
416
440
|
'agents.sessions.prompt': { scopes: ['agents:sessions'], type: 'sdkAgentInteract', handler: agentsSessionsPromptHandler },
|
|
417
441
|
'agents.sessions.stop': { scopes: ['agents:sessions'], type: 'sdkAgentInteract', handler: agentsSessionsStopHandler },
|
|
442
|
+
'agents.schedules.create': {
|
|
443
|
+
scopes: ['agents:schedule'],
|
|
444
|
+
resolveScopes: resolveAgentSchedulesScopes,
|
|
445
|
+
type: 'sdkAgentSession',
|
|
446
|
+
handler: agentSchedulesCreateHandler,
|
|
447
|
+
},
|
|
448
|
+
'agents.schedules.update': {
|
|
449
|
+
scopes: ['agents:schedule'],
|
|
450
|
+
resolveScopes: resolveAgentSchedulesScopes,
|
|
451
|
+
type: 'sdkAgentSession',
|
|
452
|
+
handler: agentSchedulesUpdateHandler,
|
|
453
|
+
},
|
|
454
|
+
'agents.schedules.delete': { scopes: ['agents:schedule'], type: 'sdkAgentSession', handler: agentSchedulesDeleteHandler },
|
|
455
|
+
'agents.schedules.runNow': { scopes: ['agents:schedule'], type: 'sdkAgentSession', handler: agentSchedulesRunNowHandler },
|
|
418
456
|
'git.stage': { scopes: ['git:write'], type: 'sdkGitWrite', handler: gitStageHandler, bouncerToolName: 'git_stage' },
|
|
419
457
|
'git.unstage': { scopes: ['git:write'], type: 'sdkGitWrite', handler: gitUnstageHandler, bouncerToolName: 'git_unstage' },
|
|
420
458
|
'git.commit': { scopes: ['git:write'], type: 'sdkGitWrite', handler: gitCommitHandler, bouncerToolName: 'git_commit' },
|
|
@@ -149,7 +149,7 @@ export async function terminalOpenHandler(args) {
|
|
|
149
149
|
origin: args.origin,
|
|
150
150
|
spaceKey: args.spaceKey,
|
|
151
151
|
kind: 'terminal.open',
|
|
152
|
-
viewerId:
|
|
152
|
+
viewerId: args.viewerId,
|
|
153
153
|
teardown: () => {
|
|
154
154
|
unsubscribeRoster();
|
|
155
155
|
if (isSdkOpenedTerminalId(opened.id))
|
|
@@ -4,6 +4,8 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
4
4
|
import { createStackParser, createTransport, initAndBind, nodeStackLineParser, ServerRuntimeClient, captureException as sentryCaptureException, captureMessage as sentryCaptureMessage, flush as sentryFlush, } from '@sentry/core';
|
|
5
5
|
import { machineFile } from '../../bin/machine-layout.js';
|
|
6
6
|
import { resolvePlatformUrl } from './platform-url.js';
|
|
7
|
+
import { relayHttp } from './relay-http-client.js';
|
|
8
|
+
const CONFIG_FETCH_BUDGET_MS = 5_000;
|
|
7
9
|
const SENTRY_TUNNEL_DSN = 'https://tunnel@sentry.io/0';
|
|
8
10
|
const PLATFORM_URL = resolvePlatformUrl();
|
|
9
11
|
let initialized = false;
|
|
@@ -27,7 +29,9 @@ function isTelemetryEnabled() {
|
|
|
27
29
|
}
|
|
28
30
|
async function fetchSentryEnabled() {
|
|
29
31
|
try {
|
|
30
|
-
const response = await fetch(`${PLATFORM_URL}/api/config/client
|
|
32
|
+
const response = await relayHttp.fetch(`${PLATFORM_URL}/api/config/client`, undefined, {
|
|
33
|
+
budgetMs: CONFIG_FETCH_BUDGET_MS,
|
|
34
|
+
});
|
|
31
35
|
if (!response.ok)
|
|
32
36
|
return false;
|
|
33
37
|
const data = await response.json();
|