patchrelay 0.36.13 → 0.36.14
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/build-info.json +3 -3
- package/dist/linear-session-sync.js +206 -47
- package/dist/linear-workflow.js +56 -6
- package/dist/presentation-text.js +10 -1
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -2,8 +2,10 @@ import { buildAgentSessionPlanForIssue } from "./agent-session-plan.js";
|
|
|
2
2
|
import { buildAgentSessionExternalUrls } from "./agent-session-presentation.js";
|
|
3
3
|
import { deriveIssueStatusNote } from "./status-note.js";
|
|
4
4
|
import { derivePatchRelayWaitingReason } from "./waiting-reason.js";
|
|
5
|
-
import { resolvePreferredReviewLinearState,
|
|
5
|
+
import { resolvePreferredDeployingLinearState, resolvePreferredHumanNeededLinearState, resolvePreferredImplementingLinearState, resolvePreferredReviewLinearState, resolvePreferredReviewingLinearState, } from "./linear-workflow.js";
|
|
6
|
+
import { sanitizeOperatorFacingCommand, sanitizeOperatorFacingText } from "./presentation-text.js";
|
|
6
7
|
const PROGRESS_THROTTLE_MS = 5_000;
|
|
8
|
+
const MAX_PROGRESS_TEXT_LENGTH = 220;
|
|
7
9
|
export class LinearSessionSync {
|
|
8
10
|
config;
|
|
9
11
|
db;
|
|
@@ -11,6 +13,9 @@ export class LinearSessionSync {
|
|
|
11
13
|
logger;
|
|
12
14
|
feed;
|
|
13
15
|
progressThrottle = new Map();
|
|
16
|
+
workingOnPublishedRuns = new Set();
|
|
17
|
+
agentMessageBuffers = new Map();
|
|
18
|
+
agentMessageProgressPublished = new Set();
|
|
14
19
|
constructor(config, db, linearProvider, logger, feed) {
|
|
15
20
|
this.config = config;
|
|
16
21
|
this.db = db;
|
|
@@ -166,21 +171,103 @@ export class LinearSessionSync {
|
|
|
166
171
|
}
|
|
167
172
|
}
|
|
168
173
|
maybeEmitProgress(notification, run) {
|
|
169
|
-
const
|
|
170
|
-
if (!
|
|
174
|
+
const issue = this.db.issues.getIssue(run.projectId, run.linearIssueId);
|
|
175
|
+
if (!issue)
|
|
171
176
|
return;
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
if (
|
|
177
|
+
const agentSentence = this.consumeAgentMessageSentence(notification, run);
|
|
178
|
+
const workingOn = this.resolveWorkingOnActivity(notification, agentSentence?.sentence);
|
|
179
|
+
if (workingOn && !this.workingOnPublishedRuns.has(run.id)) {
|
|
180
|
+
this.workingOnPublishedRuns.add(run.id);
|
|
181
|
+
void this.emitActivity(issue, workingOn);
|
|
182
|
+
}
|
|
183
|
+
const progress = this.resolveEphemeralProgressActivity(notification, agentSentence?.sentence);
|
|
184
|
+
if (!progress)
|
|
175
185
|
return;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
186
|
+
if (!progress.bypassThrottle) {
|
|
187
|
+
const now = Date.now();
|
|
188
|
+
const lastEmit = this.progressThrottle.get(run.id) ?? 0;
|
|
189
|
+
if (now - lastEmit < PROGRESS_THROTTLE_MS)
|
|
190
|
+
return;
|
|
191
|
+
this.progressThrottle.set(run.id, now);
|
|
180
192
|
}
|
|
193
|
+
void this.emitActivity(issue, progress.activity, { ephemeral: true });
|
|
181
194
|
}
|
|
182
195
|
clearProgress(runId) {
|
|
183
196
|
this.progressThrottle.delete(runId);
|
|
197
|
+
this.workingOnPublishedRuns.delete(runId);
|
|
198
|
+
for (const key of this.agentMessageBuffers.keys()) {
|
|
199
|
+
if (key.startsWith(`${runId}:`)) {
|
|
200
|
+
this.agentMessageBuffers.delete(key);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (const key of this.agentMessageProgressPublished) {
|
|
204
|
+
if (key.startsWith(`${runId}:`)) {
|
|
205
|
+
this.agentMessageProgressPublished.delete(key);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
resolveWorkingOnActivity(notification, agentSentence) {
|
|
210
|
+
const summary = resolveWorkingOnSummary(notification) ?? agentSentence;
|
|
211
|
+
if (!summary)
|
|
212
|
+
return undefined;
|
|
213
|
+
return { type: "response", body: `Working on: ${summary}` };
|
|
214
|
+
}
|
|
215
|
+
resolveEphemeralProgressActivity(notification, agentSentence) {
|
|
216
|
+
if (notification.method === "item/started") {
|
|
217
|
+
const item = notification.params.item;
|
|
218
|
+
if (!item)
|
|
219
|
+
return undefined;
|
|
220
|
+
const type = typeof item.type === "string" ? item.type : undefined;
|
|
221
|
+
if (type === "commandExecution") {
|
|
222
|
+
const cmd = item.command;
|
|
223
|
+
const cmdStr = Array.isArray(cmd)
|
|
224
|
+
? sanitizeOperatorFacingCommand(cmd.map((part) => String(part)).join(" "))
|
|
225
|
+
: sanitizeOperatorFacingCommand(typeof cmd === "string" ? cmd : undefined);
|
|
226
|
+
return { activity: { type: "action", action: "Running", parameter: truncateProgressText(cmdStr ?? "command", 120) } };
|
|
227
|
+
}
|
|
228
|
+
if (type === "mcpToolCall") {
|
|
229
|
+
const server = typeof item.server === "string" ? item.server : "";
|
|
230
|
+
const tool = typeof item.tool === "string" ? item.tool : "";
|
|
231
|
+
return { activity: { type: "action", action: "Using", parameter: `${server}/${tool}` } };
|
|
232
|
+
}
|
|
233
|
+
if (type === "dynamicToolCall") {
|
|
234
|
+
const tool = typeof item.tool === "string" ? item.tool : "tool";
|
|
235
|
+
return { activity: { type: "action", action: "Using", parameter: tool } };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (agentSentence) {
|
|
239
|
+
return {
|
|
240
|
+
activity: { type: "thought", body: agentSentence },
|
|
241
|
+
bypassThrottle: true,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
consumeAgentMessageSentence(notification, run) {
|
|
247
|
+
const messageKey = resolveAgentMessageKey(notification, run);
|
|
248
|
+
if (!messageKey)
|
|
249
|
+
return undefined;
|
|
250
|
+
if (this.agentMessageProgressPublished.has(messageKey))
|
|
251
|
+
return undefined;
|
|
252
|
+
const delta = resolveAgentMessageDelta(notification);
|
|
253
|
+
if (delta) {
|
|
254
|
+
const previous = this.agentMessageBuffers.get(messageKey) ?? "";
|
|
255
|
+
const next = `${previous}${delta}`;
|
|
256
|
+
this.agentMessageBuffers.set(messageKey, next);
|
|
257
|
+
const sentence = extractFirstSentence(next);
|
|
258
|
+
if (!sentence)
|
|
259
|
+
return undefined;
|
|
260
|
+
this.agentMessageProgressPublished.add(messageKey);
|
|
261
|
+
return { sentence };
|
|
262
|
+
}
|
|
263
|
+
const completedText = resolveCompletedAgentMessageText(notification);
|
|
264
|
+
if (!completedText)
|
|
265
|
+
return undefined;
|
|
266
|
+
const sentence = extractFirstSentence(completedText);
|
|
267
|
+
if (!sentence)
|
|
268
|
+
return undefined;
|
|
269
|
+
this.agentMessageProgressPublished.add(messageKey);
|
|
270
|
+
return { sentence };
|
|
184
271
|
}
|
|
185
272
|
async syncStatusComment(issue, linear, options) {
|
|
186
273
|
try {
|
|
@@ -205,29 +292,6 @@ export class LinearSessionSync {
|
|
|
205
292
|
}
|
|
206
293
|
}
|
|
207
294
|
}
|
|
208
|
-
function resolveProgressActivity(notification) {
|
|
209
|
-
if (notification.method === "item/started") {
|
|
210
|
-
const item = notification.params.item;
|
|
211
|
-
if (!item)
|
|
212
|
-
return undefined;
|
|
213
|
-
const type = typeof item.type === "string" ? item.type : undefined;
|
|
214
|
-
if (type === "commandExecution") {
|
|
215
|
-
const cmd = item.command;
|
|
216
|
-
const cmdStr = Array.isArray(cmd) ? cmd.join(" ") : typeof cmd === "string" ? cmd : undefined;
|
|
217
|
-
return { type: "action", action: "Running", parameter: cmdStr?.slice(0, 120) ?? "command" };
|
|
218
|
-
}
|
|
219
|
-
if (type === "mcpToolCall") {
|
|
220
|
-
const server = typeof item.server === "string" ? item.server : "";
|
|
221
|
-
const tool = typeof item.tool === "string" ? item.tool : "";
|
|
222
|
-
return { type: "action", action: "Using", parameter: `${server}/${tool}` };
|
|
223
|
-
}
|
|
224
|
-
if (type === "dynamicToolCall") {
|
|
225
|
-
const tool = typeof item.tool === "string" ? item.tool : "tool";
|
|
226
|
-
return { type: "action", action: "Using", parameter: tool };
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
return undefined;
|
|
230
|
-
}
|
|
231
295
|
function renderStatusComment(db, issue, trackedIssue, options) {
|
|
232
296
|
const activeRun = issue.activeRunId ? db.runs.getRunById(issue.activeRunId) : undefined;
|
|
233
297
|
const latestRun = db.runs.getLatestRunForIssue(issue.projectId, issue.linearIssueId);
|
|
@@ -343,32 +407,127 @@ function humanize(value) {
|
|
|
343
407
|
}
|
|
344
408
|
function shouldAutoAdvanceLinearState(issue) {
|
|
345
409
|
const normalizedType = issue.currentLinearStateType?.trim().toLowerCase();
|
|
346
|
-
if (normalizedType === "
|
|
347
|
-
return
|
|
410
|
+
if (normalizedType === "completed" || normalizedType === "canceled" || normalizedType === "cancelled") {
|
|
411
|
+
return false;
|
|
348
412
|
}
|
|
349
413
|
const normalizedName = issue.currentLinearState?.trim().toLowerCase();
|
|
350
|
-
return normalizedName
|
|
414
|
+
return normalizedName !== "done" && normalizedName !== "completed" && normalizedName !== "complete";
|
|
351
415
|
}
|
|
352
416
|
function resolveDesiredActiveWorkflowState(issue, trackedIssue, options, liveIssue) {
|
|
417
|
+
if (issue.factoryState === "awaiting_input" || issue.factoryState === "failed" || issue.factoryState === "escalated"
|
|
418
|
+
|| trackedIssue?.sessionState === "waiting_input" || trackedIssue?.sessionState === "failed") {
|
|
419
|
+
return resolvePreferredHumanNeededLinearState(liveIssue);
|
|
420
|
+
}
|
|
421
|
+
const activelyWorking = issue.activeRunId !== undefined
|
|
422
|
+
|| options?.activeRunType !== undefined
|
|
423
|
+
|| trackedIssue?.sessionState === "running"
|
|
424
|
+
|| issue.factoryState === "delegated"
|
|
425
|
+
|| issue.factoryState === "implementing"
|
|
426
|
+
|| issue.factoryState === "changes_requested"
|
|
427
|
+
|| issue.factoryState === "repairing_ci"
|
|
428
|
+
|| issue.factoryState === "repairing_queue";
|
|
429
|
+
if (activelyWorking) {
|
|
430
|
+
return resolvePreferredImplementingLinearState(liveIssue);
|
|
431
|
+
}
|
|
432
|
+
if (issue.factoryState === "awaiting_queue"
|
|
433
|
+
|| issue.prReviewState === "approved"
|
|
434
|
+
|| isApprovedAndGreen(issue.prReviewState, issue.prCheckStatus)) {
|
|
435
|
+
return resolvePreferredDeployingLinearState(liveIssue);
|
|
436
|
+
}
|
|
437
|
+
const reviewQuillActive = hasPendingReviewQuillVerdict(issue.lastGitHubCiSnapshotJson);
|
|
438
|
+
if (reviewQuillActive) {
|
|
439
|
+
return resolvePreferredReviewingLinearState(liveIssue);
|
|
440
|
+
}
|
|
353
441
|
const reviewBound = issue.prNumber !== undefined
|
|
354
442
|
|| Boolean(issue.prUrl)
|
|
355
443
|
|| issue.factoryState === "pr_open"
|
|
356
|
-
|| issue.factoryState === "awaiting_queue"
|
|
357
|
-
|| issue.factoryState === "changes_requested"
|
|
358
|
-
|| issue.factoryState === "repairing_ci"
|
|
359
|
-
|| issue.factoryState === "repairing_queue"
|
|
360
444
|
|| issue.prReviewState !== undefined
|
|
361
445
|
|| issue.prCheckStatus !== undefined;
|
|
362
446
|
if (reviewBound) {
|
|
363
447
|
return resolvePreferredReviewLinearState(liveIssue);
|
|
364
448
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
function isApprovedAndGreen(prReviewState, prCheckStatus) {
|
|
452
|
+
const normalizedReview = prReviewState?.trim().toLowerCase();
|
|
453
|
+
const normalizedChecks = prCheckStatus?.trim().toLowerCase();
|
|
454
|
+
return normalizedReview === "approved" && (normalizedChecks === "success" || normalizedChecks === "passed");
|
|
455
|
+
}
|
|
456
|
+
function hasPendingReviewQuillVerdict(snapshotJson) {
|
|
457
|
+
if (!snapshotJson)
|
|
458
|
+
return false;
|
|
459
|
+
try {
|
|
460
|
+
const parsed = JSON.parse(snapshotJson);
|
|
461
|
+
return Array.isArray(parsed.checks) && parsed.checks.some((check) => typeof check.name === "string"
|
|
462
|
+
&& check.name === "review-quill/verdict"
|
|
463
|
+
&& typeof check.status === "string"
|
|
464
|
+
&& check.status.toLowerCase() === "pending");
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function resolveWorkingOnSummary(notification) {
|
|
471
|
+
if (notification.method !== "turn/plan/updated") {
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
474
|
+
const plan = notification.params.plan;
|
|
475
|
+
if (!Array.isArray(plan))
|
|
476
|
+
return undefined;
|
|
477
|
+
const ranked = plan
|
|
478
|
+
.map((entry) => entry)
|
|
479
|
+
.filter((entry) => typeof entry.step === "string" && entry.step.trim().length > 0)
|
|
480
|
+
.sort((a, b) => rankPlanStatus(a.status) - rankPlanStatus(b.status));
|
|
481
|
+
const first = ranked[0];
|
|
482
|
+
return summarizeProgressSentence(typeof first?.step === "string" ? first.step : undefined);
|
|
483
|
+
}
|
|
484
|
+
function rankPlanStatus(status) {
|
|
485
|
+
return status === "inProgress" ? 0
|
|
486
|
+
: status === "pending" ? 1
|
|
487
|
+
: status === "completed" ? 2
|
|
488
|
+
: 3;
|
|
489
|
+
}
|
|
490
|
+
function resolveAgentMessageKey(notification, run) {
|
|
491
|
+
if (notification.method === "item/agentMessage/delta") {
|
|
492
|
+
const itemId = typeof notification.params.itemId === "string" ? notification.params.itemId : undefined;
|
|
493
|
+
return itemId ? `${run.id}:${itemId}` : undefined;
|
|
494
|
+
}
|
|
495
|
+
if (notification.method === "item/completed") {
|
|
496
|
+
const item = notification.params.item;
|
|
497
|
+
const itemId = typeof item?.id === "string" ? item.id : undefined;
|
|
498
|
+
const itemType = typeof item?.type === "string" ? item.type : undefined;
|
|
499
|
+
return itemId && itemType === "agentMessage" ? `${run.id}:${itemId}` : undefined;
|
|
372
500
|
}
|
|
373
501
|
return undefined;
|
|
374
502
|
}
|
|
503
|
+
function resolveAgentMessageDelta(notification) {
|
|
504
|
+
if (notification.method !== "item/agentMessage/delta") {
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
return typeof notification.params.delta === "string" ? notification.params.delta : undefined;
|
|
508
|
+
}
|
|
509
|
+
function resolveCompletedAgentMessageText(notification) {
|
|
510
|
+
if (notification.method !== "item/completed") {
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
const item = notification.params.item;
|
|
514
|
+
if (!item || item.type !== "agentMessage")
|
|
515
|
+
return undefined;
|
|
516
|
+
return typeof item.text === "string" ? item.text : undefined;
|
|
517
|
+
}
|
|
518
|
+
function extractFirstSentence(text) {
|
|
519
|
+
const sanitized = sanitizeOperatorFacingText(text)?.replace(/\s+/g, " ").trim();
|
|
520
|
+
if (!sanitized)
|
|
521
|
+
return undefined;
|
|
522
|
+
const match = sanitized.match(/^(.+?[.!?])(?:\s|$)/);
|
|
523
|
+
return truncateProgressText((match?.[1] ?? sanitized).trim(), MAX_PROGRESS_TEXT_LENGTH);
|
|
524
|
+
}
|
|
525
|
+
function summarizeProgressSentence(text) {
|
|
526
|
+
const summary = extractFirstSentence(text);
|
|
527
|
+
if (!summary)
|
|
528
|
+
return undefined;
|
|
529
|
+
return summary.endsWith(".") || summary.endsWith("!") || summary.endsWith("?") ? summary : `${summary}.`;
|
|
530
|
+
}
|
|
531
|
+
function truncateProgressText(text, maxLength) {
|
|
532
|
+
return text.length <= maxLength ? text : `${text.slice(0, maxLength - 3).trimEnd()}...`;
|
|
533
|
+
}
|
package/dist/linear-workflow.js
CHANGED
|
@@ -2,6 +2,20 @@ function normalizeLinearState(value) {
|
|
|
2
2
|
const trimmed = value?.trim();
|
|
3
3
|
return trimmed ? trimmed.toLowerCase() : undefined;
|
|
4
4
|
}
|
|
5
|
+
function includesAny(normalized, candidates) {
|
|
6
|
+
return Boolean(normalized && candidates.includes(normalized));
|
|
7
|
+
}
|
|
8
|
+
function resolvePreferredLinearState(issue, params) {
|
|
9
|
+
const match = issue.workflowStates.find((state) => {
|
|
10
|
+
const normalizedType = normalizeLinearState(state.type);
|
|
11
|
+
const normalizedName = normalizeLinearState(state.name);
|
|
12
|
+
if (params.types && !params.types.includes(normalizedType ?? "")) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return includesAny(normalizedName, params.names);
|
|
16
|
+
});
|
|
17
|
+
return match?.name ?? params.fallback;
|
|
18
|
+
}
|
|
5
19
|
export function resolvePreferredStartedLinearState(issue) {
|
|
6
20
|
const startedStates = issue.workflowStates.filter((state) => normalizeLinearState(state.type) === "started");
|
|
7
21
|
const preferred = startedStates.find((state) => {
|
|
@@ -10,14 +24,50 @@ export function resolvePreferredStartedLinearState(issue) {
|
|
|
10
24
|
});
|
|
11
25
|
return preferred?.name ?? startedStates[0]?.name;
|
|
12
26
|
}
|
|
27
|
+
export function resolvePreferredImplementingLinearState(issue) {
|
|
28
|
+
return resolvePreferredLinearState(issue, {
|
|
29
|
+
names: ["implementing", "in progress", "in-progress", "started", "doing"],
|
|
30
|
+
types: ["started"],
|
|
31
|
+
fallback: resolvePreferredStartedLinearState(issue),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
13
34
|
export function resolvePreferredReviewLinearState(issue) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
return resolvePreferredLinearState(issue, {
|
|
36
|
+
names: ["review", "awaiting review"],
|
|
37
|
+
types: ["unstarted"],
|
|
38
|
+
fallback: resolvePreferredLinearState(issue, {
|
|
39
|
+
names: ["reviewing", "in review", "review"],
|
|
40
|
+
types: ["started"],
|
|
41
|
+
fallback: resolvePreferredStartedLinearState(issue),
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export function resolvePreferredReviewingLinearState(issue) {
|
|
46
|
+
return resolvePreferredLinearState(issue, {
|
|
47
|
+
names: ["reviewing", "in review", "review"],
|
|
48
|
+
types: ["started"],
|
|
49
|
+
fallback: resolvePreferredReviewLinearState(issue),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export function resolvePreferredDeployLinearState(issue) {
|
|
53
|
+
return resolvePreferredLinearState(issue, {
|
|
54
|
+
names: ["deploy", "ready to deploy", "ready for deploy", "merge"],
|
|
55
|
+
types: ["unstarted"],
|
|
56
|
+
fallback: resolvePreferredReviewLinearState(issue),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export function resolvePreferredDeployingLinearState(issue) {
|
|
60
|
+
return resolvePreferredLinearState(issue, {
|
|
61
|
+
names: ["deploying", "merging", "shipping"],
|
|
62
|
+
types: ["started"],
|
|
63
|
+
fallback: resolvePreferredDeployLinearState(issue),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export function resolvePreferredHumanNeededLinearState(issue) {
|
|
67
|
+
return resolvePreferredLinearState(issue, {
|
|
68
|
+
names: ["human needed", "needs human", "help needed", "operator needed", "blocked"],
|
|
69
|
+
fallback: undefined,
|
|
19
70
|
});
|
|
20
|
-
return reviewState?.name ?? resolvePreferredStartedLinearState(issue);
|
|
21
71
|
}
|
|
22
72
|
export function resolvePreferredCompletedLinearState(issue) {
|
|
23
73
|
const completed = issue.workflowStates.find((state) => normalizeLinearState(state.type) === "completed");
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
function unwrapShellWrappedCommand(text) {
|
|
2
2
|
return text
|
|
3
3
|
.replace(/`(?:\/bin\/bash|bash|\/bin\/sh|sh)\s+-lc\s+'([^`\n]+)'`/g, "`$1`")
|
|
4
|
-
.replace(/`(?:\/bin\/bash|bash|\/bin\/sh|sh)\s+-lc\s+"([^`\n]+)"`/g, "`$1`")
|
|
4
|
+
.replace(/`(?:\/bin\/bash|bash|\/bin\/sh|sh)\s+-lc\s+"([^`\n]+)"`/g, "`$1`")
|
|
5
|
+
.replace(/^(?:\/bin\/bash|bash|\/bin\/sh|sh)\s+-lc\s+'([^`\n]+)'$/g, "$1")
|
|
6
|
+
.replace(/^(?:\/bin\/bash|bash|\/bin\/sh|sh)\s+-lc\s+"([^`\n]+)"$/g, "$1");
|
|
7
|
+
}
|
|
8
|
+
export function sanitizeOperatorFacingCommand(command) {
|
|
9
|
+
const trimmed = command?.trim();
|
|
10
|
+
if (!trimmed) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
return unwrapShellWrappedCommand(trimmed);
|
|
5
14
|
}
|
|
6
15
|
export function sanitizeOperatorFacingText(text) {
|
|
7
16
|
const trimmed = text?.trim();
|