pi-subagents 0.62.0 → 0.64.0
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/CHANGELOG.md +52 -1
- package/docs/agents.md +5 -4
- package/docs/configuration.md +28 -2
- package/docs/models.md +5 -5
- package/docs/observability.md +4 -1
- package/docs/tool-reference.md +2 -2
- package/docs/watchdog.md +92 -114
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +4 -3
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +3 -3
- package/src/agents/agent-management.ts +22 -4
- package/src/agents/agents.ts +107 -125
- package/src/api/shared-types.ts +3 -0
- package/src/extension/config.ts +20 -0
- package/src/inspectors/herdr/inspector-runner.ts +19 -13
- package/src/runs/background/active-async-capacity.ts +0 -1
- package/src/runs/background/async-execution.ts +56 -7
- package/src/runs/background/async-resume.ts +3 -0
- package/src/runs/background/async-status.ts +18 -2
- package/src/runs/background/notify.ts +54 -3
- package/src/runs/background/run-status.ts +22 -2
- package/src/runs/background/subagent-runner.ts +53 -3
- package/src/runs/background/wait-completions.ts +13 -0
- package/src/runs/foreground/execution.ts +7 -1
- package/src/runs/foreground/subagent-executor.ts +56 -4
- package/src/runs/shared/acceptance.ts +25 -9
- package/src/runs/shared/async-status-projection.ts +11 -43
- package/src/runs/shared/lane-metadata.ts +24 -3
- package/src/runs/shared/parallel-handoff.ts +4 -0
- package/src/runs/shared/pi-args.ts +9 -6
- package/src/runs/shared/subagent-control.ts +4 -2
- package/src/runs/shared/task-intent.ts +5 -2
- package/src/runs/shared/worktree.ts +467 -63
- package/src/shared/types.ts +38 -2
- package/src/shared/utils.ts +18 -7
- package/src/slash/subagents-admin.ts +24 -12
- package/src/tui/fleet-status.ts +61 -2
- package/src/tui/fleet.ts +12 -7
- package/src/tui/render.ts +227 -14
- package/src/watchdog/child-status.ts +54 -33
- package/src/watchdog/diff-tool.ts +77 -0
- package/src/watchdog/emission-guard.ts +5 -3
- package/src/watchdog/guidance.ts +20 -0
- package/src/watchdog/register-child.ts +16 -13
- package/src/watchdog/register-main.ts +10 -9
- package/src/watchdog/render.ts +4 -5
- package/src/watchdog/review.ts +15 -4
- package/src/watchdog/rules.ts +70 -0
- package/src/watchdog/runtime.ts +75 -92
- package/src/watchdog/scope.ts +0 -11
- package/src/watchdog/settings.ts +48 -104
- package/src/watchdog/types.ts +18 -32
- package/src/watchdog/warning-format.ts +0 -1
- package/src/workflows/chat-progress.ts +3 -2
- package/src/workflows/workflow-checklist.ts +439 -0
- package/src/workflows/workflow-preflight.ts +28 -1
|
@@ -2,6 +2,7 @@ import { sanitizeDisplayText, truncateDisplayText } from "../../shared/display-t
|
|
|
2
2
|
import { formatModelThinking } from "../../shared/formatters.ts";
|
|
3
3
|
import type { AsyncJobState, AsyncJobStep, HostStepFreshnessV1, HostStepMonitorKind, HostStepNodeV1, HostStepState, HostStepVerdict, NestedRunSummary, NestedStepSummary, SubagentRunMode, WorkflowGraphSnapshot, WorkflowPreflightLaneV1, WorkflowPreflightV1 } from "../../shared/types.ts";
|
|
4
4
|
import { HOST_STEP_MAX_COUNT, HOST_STEP_MAX_DETAIL_CHARS, HOST_STEP_MAX_LABEL_CHARS, HOST_STEP_MAX_PROVIDER_CHARS, HOST_STEP_MAX_REASON_CHARS, HOST_STEP_MAX_REF_CHARS, HOST_STEP_MAX_ROLE_CHARS, HOST_STEP_MAX_TARGET_CHARS, hostStepReportName, parseHostStepNode, validHostStepNodes } from "./host-step-status.ts";
|
|
5
|
+
import { workflowPreflightLaneForRuntimeKey } from "../../workflows/workflow-preflight.ts";
|
|
5
6
|
import { workflowGraphStageNodes } from "./workflow-graph.ts";
|
|
6
7
|
|
|
7
8
|
export const ASYNC_STATUS_SNAPSHOT_KIND = "pi-subagents.async-status-snapshot";
|
|
@@ -472,7 +473,7 @@ function projectWorkflowGraphRow(node: WorkflowGraphSnapshot["nodes"][number], p
|
|
|
472
473
|
};
|
|
473
474
|
}
|
|
474
475
|
|
|
475
|
-
/** Project
|
|
476
|
+
/** Project authoritative workflow facts into compact rows, annotated by preflight hints. */
|
|
476
477
|
export function projectAsyncWorkflowRows(
|
|
477
478
|
steps: readonly AsyncJobStep[] | undefined,
|
|
478
479
|
hostStepsOrPreflight?: readonly HostStepNodeV1[] | WorkflowGraphSnapshot | WorkflowPreflightV1,
|
|
@@ -482,7 +483,8 @@ export function projectAsyncWorkflowRows(
|
|
|
482
483
|
const graph = isWorkflowGraph(hostStepsOrPreflight) ? hostStepsOrPreflight : undefined;
|
|
483
484
|
const hostSteps = isWorkflowPreflight(hostStepsOrPreflight) || graph ? undefined : hostStepsOrPreflight;
|
|
484
485
|
const loaded = steps ?? [];
|
|
485
|
-
const
|
|
486
|
+
const preflightForKey = (key: string, groupKeys: readonly (string | undefined)[] = []): WorkflowPreflightLaneV1 | undefined =>
|
|
487
|
+
workflowPreflightLaneForRuntimeKey(preflight, key, groupKeys);
|
|
486
488
|
if (graph) {
|
|
487
489
|
const loadedIndexesByKey = new Map<string, number[]>();
|
|
488
490
|
for (const [index, step] of loaded.entries()) {
|
|
@@ -493,7 +495,6 @@ export function projectAsyncWorkflowRows(
|
|
|
493
495
|
}
|
|
494
496
|
const consumed = new Set<number>();
|
|
495
497
|
const childRows: AsyncStatusWorkflowRow[] = [];
|
|
496
|
-
for (const lane of preflight?.lanes ?? []) declared.set(lane.key, lane);
|
|
497
498
|
const graphStages = workflowGraphStageNodes(graph);
|
|
498
499
|
const graphKeys = new Set(graphStages.map((node) => node.id));
|
|
499
500
|
const graphPhaseByNodeId = new Map<string, string>();
|
|
@@ -502,60 +503,27 @@ export function projectAsyncWorkflowRows(
|
|
|
502
503
|
if (graphKeys.has(nodeId) && !graphPhaseByNodeId.has(nodeId)) graphPhaseByNodeId.set(nodeId, phase.title);
|
|
503
504
|
}
|
|
504
505
|
}
|
|
505
|
-
const graphLaneKeys = new Set(graphPhaseByNodeId.values());
|
|
506
|
-
const preflightForNode = (node: WorkflowGraphSnapshot["nodes"][number]): WorkflowPreflightLaneV1 | undefined => {
|
|
507
|
-
const phaseTitle = graphPhaseByNodeId.get(node.id);
|
|
508
|
-
return declared.get(node.id) ?? (node.phase ? declared.get(node.phase) : undefined) ?? (phaseTitle ? declared.get(phaseTitle) : undefined);
|
|
509
|
-
};
|
|
510
506
|
for (const node of graphStages) {
|
|
507
|
+
const lane = preflightForKey(node.id, [node.phase, graphPhaseByNodeId.get(node.id)]);
|
|
511
508
|
const indexes = (loadedIndexesByKey.get(node.id) ?? []).filter((index) => !consumed.has(index));
|
|
512
|
-
if (indexes.length > 0) {
|
|
513
|
-
for (const index of indexes) {
|
|
514
|
-
consumed.add(index);
|
|
515
|
-
childRows.push(projectLoadedWorkflowRow(loaded[index]!, index, preflightForNode(node)));
|
|
516
|
-
}
|
|
517
|
-
} else {
|
|
518
|
-
childRows.push(projectWorkflowGraphRow(node, preflightForNode(node)));
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
for (const lane of preflight?.lanes ?? []) {
|
|
522
|
-
if (graphKeys.has(lane.key) || graphLaneKeys.has(lane.key)) continue;
|
|
523
|
-
const indexes = (loadedIndexesByKey.get(lane.key) ?? []).filter((index) => !consumed.has(index));
|
|
524
509
|
if (indexes.length > 0) {
|
|
525
510
|
for (const index of indexes) {
|
|
526
511
|
consumed.add(index);
|
|
527
512
|
childRows.push(projectLoadedWorkflowRow(loaded[index]!, index, lane));
|
|
528
513
|
}
|
|
529
514
|
} else {
|
|
530
|
-
childRows.push(
|
|
515
|
+
childRows.push(projectWorkflowGraphRow(node, lane));
|
|
531
516
|
}
|
|
532
517
|
}
|
|
533
518
|
for (const [index, step] of loaded.entries()) {
|
|
534
|
-
if (!consumed.has(index)) childRows.push(projectLoadedWorkflowRow(step, index, step.workflowKey ?
|
|
519
|
+
if (!consumed.has(index)) childRows.push(projectLoadedWorkflowRow(step, index, step.workflowKey ? preflightForKey(step.workflowKey, [step.phase]) : undefined));
|
|
535
520
|
}
|
|
536
521
|
return [...childRows, ...validHostStepList(graph).map(hostStepRow)];
|
|
537
522
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const consumed = new Set<number>();
|
|
543
|
-
const childRows: AsyncStatusWorkflowRow[] = [];
|
|
544
|
-
for (const lane of preflight?.lanes ?? []) {
|
|
545
|
-
declared.set(lane.key, lane);
|
|
546
|
-
const index = loadedIndexByKey.get(lane.key);
|
|
547
|
-
if (index === undefined) {
|
|
548
|
-
childRows.push({ name: lane.key, state: "planned", preflight: lane });
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
consumed.add(index);
|
|
552
|
-
childRows.push(projectLoadedWorkflowRow(loaded[index]!, index, lane));
|
|
553
|
-
}
|
|
554
|
-
for (const [index, step] of loaded.entries()) {
|
|
555
|
-
if (consumed.has(index)) continue;
|
|
556
|
-
childRows.push(projectLoadedWorkflowRow(step, index, step.workflowKey ? declared.get(step.workflowKey) : undefined));
|
|
557
|
-
}
|
|
558
|
-
return [...childRows, ...validHostStepList(hostSteps).map(hostStepRow)];
|
|
523
|
+
return [
|
|
524
|
+
...loaded.map((step, index) => projectLoadedWorkflowRow(step, index, step.workflowKey ? preflightForKey(step.workflowKey, [step.phase]) : undefined)),
|
|
525
|
+
...validHostStepList(hostSteps).map(hostStepRow),
|
|
526
|
+
];
|
|
559
527
|
}
|
|
560
528
|
|
|
561
529
|
function projectLoadedWorkflowRow(step: AsyncJobStep, index: number, preflight?: WorkflowPreflightLaneV1): AsyncStatusWorkflowRow {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AsyncStatus, WorkflowLaneMetadata, WorkflowLaneMode } from "../../shared/types.ts";
|
|
1
|
+
import type { AsyncStatus, ManagedWorktreeProvider, WorktreeNaming, WorkflowLaneMetadata, WorkflowLaneMode } from "../../shared/types.ts";
|
|
2
2
|
|
|
3
3
|
export const WORKFLOW_LANE_KEY_MAX_BYTES = 128;
|
|
4
4
|
export const WORKFLOW_LANE_SOURCE_REF_MAX_BYTES = 128;
|
|
@@ -8,6 +8,7 @@ export const WORKFLOW_LANE_OUTPUT_PATH_MAX_BYTES = 256;
|
|
|
8
8
|
export const WORKFLOW_LANE_OUTPUT_PATHS_MAX = 10;
|
|
9
9
|
export const WORKTREE_STATUS_PATH_MAX_BYTES = 4096;
|
|
10
10
|
export const WORKTREE_STATUS_BRANCH_MAX_BYTES = 256;
|
|
11
|
+
export const WORKTREE_STATUS_NAMING_LABEL_MAX_BYTES = 256;
|
|
11
12
|
|
|
12
13
|
const WORKFLOW_LANE_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
13
14
|
const WORKFLOW_LANE_MODES = new Set<WorkflowLaneMode>(["mutation", "review", "scout", "gate"]);
|
|
@@ -75,16 +76,36 @@ export function assertWorkflowLaneKey(lane: WorkflowLaneMetadata | undefined, wo
|
|
|
75
76
|
export interface WorktreeStatusReference {
|
|
76
77
|
worktreePath: string;
|
|
77
78
|
branch: string;
|
|
79
|
+
provider?: ManagedWorktreeProvider;
|
|
80
|
+
naming?: WorktreeNaming;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeWorktreeNaming(value: unknown, label: string): WorktreeNaming {
|
|
84
|
+
assertPlainObject(value, label);
|
|
85
|
+
assertKnownFields(value, ["requestedBranch", "branchPrefix", "label", "sanitizedPathComponent", "collision", "collisionSuffix"], label);
|
|
86
|
+
const collision = value.collision;
|
|
87
|
+
if (collision !== undefined && collision !== "branch" && collision !== "path" && collision !== "both") throw new Error(`${label}.collision is invalid.`);
|
|
88
|
+
return {
|
|
89
|
+
requestedBranch: boundedNonEmptyString(value.requestedBranch, `${label}.requestedBranch`, WORKTREE_STATUS_BRANCH_MAX_BYTES),
|
|
90
|
+
branchPrefix: boundedNonEmptyString(value.branchPrefix, `${label}.branchPrefix`, WORKTREE_STATUS_BRANCH_MAX_BYTES),
|
|
91
|
+
label: boundedNonEmptyString(value.label, `${label}.label`, WORKTREE_STATUS_NAMING_LABEL_MAX_BYTES),
|
|
92
|
+
sanitizedPathComponent: boundedNonEmptyString(value.sanitizedPathComponent, `${label}.sanitizedPathComponent`, WORKTREE_STATUS_NAMING_LABEL_MAX_BYTES),
|
|
93
|
+
...(collision !== undefined ? { collision } : {}),
|
|
94
|
+
...(value.collisionSuffix !== undefined ? { collisionSuffix: boundedNonEmptyString(value.collisionSuffix, `${label}.collisionSuffix`, WORKTREE_STATUS_NAMING_LABEL_MAX_BYTES) } : {}),
|
|
95
|
+
};
|
|
78
96
|
}
|
|
79
97
|
|
|
80
98
|
/** Validate the display-only worktree fields copied into status.json. */
|
|
81
99
|
export function normalizeWorktreeStatusReference(value: unknown, label = "worktree status reference"): WorktreeStatusReference | undefined {
|
|
82
100
|
if (value === undefined) return undefined;
|
|
83
101
|
assertPlainObject(value, label);
|
|
84
|
-
assertKnownFields(value, ["worktreePath", "branch"], label);
|
|
102
|
+
assertKnownFields(value, ["worktreePath", "branch", "provider", "naming"], label);
|
|
103
|
+
if (value.provider !== undefined && value.provider !== "native" && value.provider !== "worktrunk") throw new Error(`${label}.provider is invalid.`);
|
|
85
104
|
return {
|
|
86
105
|
worktreePath: boundedNonEmptyString(value.worktreePath, `${label}.worktreePath`, WORKTREE_STATUS_PATH_MAX_BYTES),
|
|
87
106
|
branch: boundedNonEmptyString(value.branch, `${label}.branch`, WORKTREE_STATUS_BRANCH_MAX_BYTES),
|
|
107
|
+
...(value.provider !== undefined ? { provider: value.provider as ManagedWorktreeProvider } : {}),
|
|
108
|
+
...(value.naming !== undefined ? { naming: normalizeWorktreeNaming(value.naming, `${label}.naming`) } : {}),
|
|
88
109
|
};
|
|
89
110
|
}
|
|
90
111
|
|
|
@@ -100,6 +121,6 @@ export function validateAsyncStatusLaneMetadata(status: Pick<AsyncStatus, "runId
|
|
|
100
121
|
const hasPath = step.worktreePath !== undefined;
|
|
101
122
|
const hasBranch = step.branch !== undefined;
|
|
102
123
|
if (hasPath !== hasBranch) throw new Error(`${label}.steps[${index}] must include both worktreePath and branch.`);
|
|
103
|
-
if (hasPath) normalizeWorktreeStatusReference({ worktreePath: step.worktreePath, branch: step.branch }, `${label}.steps[${index}]`);
|
|
124
|
+
if (hasPath) normalizeWorktreeStatusReference({ worktreePath: step.worktreePath, branch: step.branch, ...(step.provider ? { provider: step.provider } : {}), ...(step.naming ? { naming: step.naming } : {}) }, `${label}.steps[${index}]`);
|
|
104
125
|
}
|
|
105
126
|
}
|
|
@@ -574,6 +574,8 @@ export function writeParallelHandoffGroup(input: {
|
|
|
574
574
|
index: worktree.index,
|
|
575
575
|
path: worktree.path,
|
|
576
576
|
branch: worktree.branch,
|
|
577
|
+
...(worktree.provider ? { provider: worktree.provider } : {}),
|
|
578
|
+
...(worktree.naming ? { naming: worktree.naming } : {}),
|
|
577
579
|
worktreeRemoved: false,
|
|
578
580
|
branchRemoved: false,
|
|
579
581
|
preserved: true,
|
|
@@ -653,6 +655,8 @@ export function discardPreservedWorktrees(
|
|
|
653
655
|
path: task.path,
|
|
654
656
|
agentCwd: task.path,
|
|
655
657
|
branch: task.branch,
|
|
658
|
+
...(task.provider ? { provider: task.provider } : {}),
|
|
659
|
+
...(task.naming ? { naming: task.naming } : {}),
|
|
656
660
|
index: task.index,
|
|
657
661
|
nodeModulesLinked: false,
|
|
658
662
|
syntheticPaths: [],
|
|
@@ -47,7 +47,6 @@ import {
|
|
|
47
47
|
} from "./tool-availability.ts";
|
|
48
48
|
import {
|
|
49
49
|
CHILD_WATCHDOG_CONFIG_ENV,
|
|
50
|
-
encodeChildWatchdogConfig,
|
|
51
50
|
type ChildWatchdogConfig,
|
|
52
51
|
} from "../../watchdog/child-status.ts";
|
|
53
52
|
import { WAIT_TOOL_DEFAULT_TIMEOUT_MS_ENV, WAIT_TOOL_ENABLED_ENV } from "../background/wait-config.ts";
|
|
@@ -93,11 +92,12 @@ export function resolveSubagentTaskDelivery(
|
|
|
93
92
|
: "auto";
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
function shouldDeliverTaskViaFile(
|
|
95
|
+
export function shouldDeliverTaskViaFile(
|
|
97
96
|
task: string,
|
|
98
97
|
delivery: SubagentTaskDelivery,
|
|
98
|
+
platform: NodeJS.Platform = process.platform,
|
|
99
99
|
): boolean {
|
|
100
|
-
return delivery === "file" || task.length > TASK_ARG_LIMIT;
|
|
100
|
+
return delivery === "file" || platform === "darwin" || task.length > TASK_ARG_LIMIT;
|
|
101
101
|
}
|
|
102
102
|
const MAX_LAUNCH_RESOLVED_EXTENSION_IDS = 32;
|
|
103
103
|
const PROMPT_RUNTIME_EXTENSION_PATH = path.join(
|
|
@@ -845,6 +845,11 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
845
845
|
);
|
|
846
846
|
env[RUNTIME_EXTENSION_ACK_PATH_ENV] = runtimeAcknowledgedExtensionsPath;
|
|
847
847
|
let toolDiagnosticPath: string | undefined;
|
|
848
|
+
// Child launch environments are merged over process.env. Explicitly clear
|
|
849
|
+
// parent-scoped diagnostics so a nested zero-tool child cannot validate
|
|
850
|
+
// against, or overwrite, its parent's required-tool report.
|
|
851
|
+
env[REQUIRED_CHILD_TOOLS_ENV] = undefined;
|
|
852
|
+
env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = undefined;
|
|
848
853
|
if (toolPlan.requiredChildTools.length > 0) {
|
|
849
854
|
if (!tempDir)
|
|
850
855
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
@@ -1025,9 +1030,7 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
1025
1030
|
const encodedToolBudget = encodeToolBudgetEnv(input.toolBudget);
|
|
1026
1031
|
if (encodedToolBudget) env[TOOL_BUDGET_ENV] = encodedToolBudget;
|
|
1027
1032
|
env[TOOL_BUDGET_ZERO_AUTH_ENV] = input.allowZeroToolBudget ? "1" : undefined;
|
|
1028
|
-
|
|
1029
|
-
if (encodedChildWatchdog)
|
|
1030
|
-
env[CHILD_WATCHDOG_CONFIG_ENV] = encodedChildWatchdog;
|
|
1033
|
+
if (input.childWatchdog) env[CHILD_WATCHDOG_CONFIG_ENV] = JSON.stringify(input.childWatchdog);
|
|
1031
1034
|
|
|
1032
1035
|
env[SUBAGENT_PARENT_SESSION_ENV] =
|
|
1033
1036
|
input.parentSessionId ?? process.env[SUBAGENT_PARENT_SESSION_ENV] ?? "";
|
|
@@ -45,11 +45,12 @@ export function resolveControlConfig(
|
|
|
45
45
|
const enabled = override?.enabled ?? globalConfig?.enabled ?? DEFAULT_CONTROL_CONFIG.enabled;
|
|
46
46
|
const overrideNeedsAttentionAfterMs = parsePositiveInt(override?.needsAttentionAfterMs);
|
|
47
47
|
const globalNeedsAttentionAfterMs = parsePositiveInt(globalConfig?.needsAttentionAfterMs);
|
|
48
|
+
const globalNeedsAttentionAfterMsIsExplicit = (globalConfig as Partial<ResolvedControlConfig> | undefined)?.needsAttentionAfterMsIsExplicit ?? globalNeedsAttentionAfterMs !== undefined;
|
|
48
49
|
const needsAttentionAfterMs = overrideNeedsAttentionAfterMs
|
|
49
50
|
?? globalNeedsAttentionAfterMs
|
|
50
51
|
?? DEFAULT_CONTROL_CONFIG.needsAttentionAfterMs;
|
|
51
52
|
const needsAttentionAfterMsIsExplicit = overrideNeedsAttentionAfterMs !== undefined
|
|
52
|
-
||
|
|
53
|
+
|| globalNeedsAttentionAfterMsIsExplicit;
|
|
53
54
|
const activeNoticeAfterMs = parsePositiveInt(override?.activeNoticeAfterMs)
|
|
54
55
|
?? parsePositiveInt(globalConfig?.activeNoticeAfterMs)
|
|
55
56
|
?? DEFAULT_CONTROL_CONFIG.activeNoticeAfterMs;
|
|
@@ -98,11 +99,12 @@ export function deriveActivityState(input: {
|
|
|
98
99
|
config: ResolvedControlConfig;
|
|
99
100
|
startedAt: number;
|
|
100
101
|
lastActivityAt?: number;
|
|
102
|
+
turnCount?: number;
|
|
101
103
|
currentTool?: string;
|
|
102
104
|
thinking?: string | false;
|
|
103
105
|
now?: number;
|
|
104
106
|
}): ActivityState | undefined {
|
|
105
|
-
if (!input.config.enabled || input.currentTool) return undefined;
|
|
107
|
+
if (!input.config.enabled || input.currentTool || (input.turnCount ?? 0) === 0) return undefined;
|
|
106
108
|
const now = input.now ?? Date.now();
|
|
107
109
|
const lastActivity = input.lastActivityAt ?? input.startedAt;
|
|
108
110
|
const ageMs = Math.max(0, now - lastActivity);
|
|
@@ -41,6 +41,7 @@ const REVIEWER_REQUIRED_EDIT_PATTERNS = [
|
|
|
41
41
|
// Accept serialized line separators too: workflow prompts can carry literal
|
|
42
42
|
// `\\n`/`\\r\\n` between clauses instead of decoded newlines.
|
|
43
43
|
const NO_EDIT_PROHIBITION_PATTERN = /(?:\b|\\(?:r\\n|n))(?:do not|don't|must not)\s+(?:edit|modify|write(?:\s+to)?|touch|change)\b((?:(?!\b(?:but|and|then)\b|\\(?:r\\n|n))[^.;,:!?\n–—-])*)/gi;
|
|
44
|
+
const COORDINATED_NO_EDIT_PROHIBITION_PATTERN = /(?:\b|\\(?:r\\n|n))(?:do not|don't|must not)\s+((?=(?:(?!\\(?:r\\n|n))[^.;:!?\n–—-])*\b(?:and|or)\s+(?:edit|modify|write(?:\s+to)?|touch|change)\b)(?:(?!\\(?:r\\n|n))[^.;:!?\n–—-])*?\b(?:and|or)\s+(?:edit|modify|write(?:\s+to)?|touch|change)\b(?:(?!\b(?:but|and|then)\b|\\(?:r\\n|n))[^.;,:!?\n–—-])*)/gi;
|
|
44
45
|
|
|
45
46
|
/** Objects of a no-edit prohibition that mean "the codebase in general" rather than a named scope. */
|
|
46
47
|
const GENERIC_PROHIBITION_OBJECT = /^\s*(?:(?:any|all|the|these|those|your|our|existing|project|product|source|sources|config|configs|repo|repository)[\s/,-]*)*(?:files?|code|codebase|sources?|anything|repo(?:sitory)?)?\s*$/i;
|
|
@@ -144,11 +145,13 @@ function analyzeNoEditProhibitions(taskText: string): NoEditProhibitionAnalysis
|
|
|
144
145
|
|| NO_TOOL_INTENT_PATTERNS.some((pattern) => pattern.test(taskText));
|
|
145
146
|
let blanket = present;
|
|
146
147
|
let strippedText = stripPatterns(taskText, [...REVIEW_ONLY_PATTERNS, ...NO_TOOL_INTENT_PATTERNS]);
|
|
147
|
-
|
|
148
|
+
const stripNoEditProhibition = (match: string, object: string, offset: number, source: string): string => {
|
|
148
149
|
present = true;
|
|
149
150
|
if (GENERIC_PROHIBITION_OBJECT.test(object) && !hasScopedProhibitionContinuation(source.slice(offset + match.length))) blanket = true;
|
|
150
151
|
return " ";
|
|
151
|
-
}
|
|
152
|
+
};
|
|
153
|
+
strippedText = strippedText.replace(new RegExp(COORDINATED_NO_EDIT_PROHIBITION_PATTERN.source, COORDINATED_NO_EDIT_PROHIBITION_PATTERN.flags), stripNoEditProhibition);
|
|
154
|
+
strippedText = strippedText.replace(new RegExp(NO_EDIT_PROHIBITION_PATTERN.source, NO_EDIT_PROHIBITION_PATTERN.flags), stripNoEditProhibition);
|
|
152
155
|
// Restore boundaries after stripping a prohibition from serialized prompts.
|
|
153
156
|
strippedText = strippedText.replace(/\\(?:r\\n|n)/g, "\n");
|
|
154
157
|
return { present, blanket, strippedText };
|