agentic-orchestrator 0.1.26 → 0.1.28
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/AGENTS.md +2 -2
- package/CLAUDE.md +2 -2
- package/README.md +47 -14
- package/agentic/orchestrator/agents.yaml +13 -0
- package/agentic/orchestrator/policy.yaml +3 -0
- package/agentic/orchestrator/schemas/agents.schema.json +76 -0
- package/agentic/orchestrator/schemas/policy.schema.json +16 -0
- package/agentic/orchestrator/schemas/policy.user.schema.json +16 -0
- package/agentic/orchestrator/schemas/state.schema.json +53 -0
- package/apps/control-plane/src/application/configuration-service.ts +181 -0
- package/apps/control-plane/src/application/kernel-tool-wiring.ts +292 -0
- package/apps/control-plane/src/application/services/checkpoint-service.ts +523 -0
- package/apps/control-plane/src/application/services/feature-send-message-service.ts +132 -0
- package/apps/control-plane/src/application/services/patch-service.ts +29 -5
- package/apps/control-plane/src/application/services/repo-operations-service.ts +276 -0
- package/apps/control-plane/src/application/services/worktree-watchdog-service.ts +156 -0
- package/apps/control-plane/src/cli/cli-argument-parser.ts +12 -0
- package/apps/control-plane/src/cli/help-command-handler.ts +17 -0
- package/apps/control-plane/src/cli/init-command-handler.ts +31 -0
- package/apps/control-plane/src/cli/resume-command-handler.ts +31 -4
- package/apps/control-plane/src/cli/rollback-command-handler.ts +217 -0
- package/apps/control-plane/src/cli/run-command-handler.ts +8 -0
- package/apps/control-plane/src/cli/types.ts +3 -0
- package/apps/control-plane/src/core/kernel-types.ts +55 -0
- package/apps/control-plane/src/core/kernel.ts +61 -878
- package/apps/control-plane/src/core/tool-caller.ts +10 -0
- package/apps/control-plane/src/core/utils/field-readers.ts +38 -0
- package/apps/control-plane/src/core/utils/index-normalizer.ts +119 -0
- package/apps/control-plane/src/core/utils/path-normalizers.ts +22 -0
- package/apps/control-plane/src/interfaces/cli/bootstrap.ts +15 -0
- package/apps/control-plane/src/providers/api-worker-provider.ts +14 -12
- package/apps/control-plane/src/providers/cli-worker-provider.ts +82 -12
- package/apps/control-plane/src/providers/providers.ts +45 -24
- package/apps/control-plane/src/providers/worker-provider-factory.ts +36 -1
- package/apps/control-plane/src/supervisor/run-coordinator.ts +91 -36
- package/apps/control-plane/src/supervisor/runtime.ts +107 -1
- package/apps/control-plane/src/supervisor/types.ts +9 -0
- package/apps/control-plane/src/supervisor/worker-decision-loop.ts +253 -14
- package/apps/control-plane/test/checkpoint-service.spec.ts +537 -0
- package/apps/control-plane/test/cli-helpers.spec.ts +28 -0
- package/apps/control-plane/test/cli.unit.spec.ts +52 -0
- package/apps/control-plane/test/configuration-service.spec.ts +466 -0
- package/apps/control-plane/test/dashboard-api.integration.spec.ts +537 -0
- package/apps/control-plane/test/dashboard-client.spec.ts +233 -0
- package/apps/control-plane/test/feature-send-message-service.spec.ts +314 -0
- package/apps/control-plane/test/init-wizard.spec.ts +35 -0
- package/apps/control-plane/test/path-normalizers.spec.ts +41 -0
- package/apps/control-plane/test/repo-operations-service.spec.ts +339 -0
- package/apps/control-plane/test/resume-command.spec.ts +33 -0
- package/apps/control-plane/test/review-workspace-logic.spec.ts +130 -0
- package/apps/control-plane/test/rollback-command.spec.ts +208 -0
- package/apps/control-plane/test/run-coordinator.spec.ts +119 -0
- package/apps/control-plane/test/worker-decision-loop.spec.ts +209 -0
- package/apps/control-plane/test/worker-provider-adapters.spec.ts +102 -0
- package/apps/control-plane/test/worker-provider-factory.spec.ts +14 -0
- package/apps/control-plane/test/worktree-watchdog-service.spec.ts +147 -0
- package/config/agentic/orchestrator/agents.yaml +13 -0
- package/dist/apps/control-plane/application/configuration-service.d.ts +19 -0
- package/dist/apps/control-plane/application/configuration-service.js +123 -0
- package/dist/apps/control-plane/application/configuration-service.js.map +1 -0
- package/dist/apps/control-plane/application/kernel-tool-wiring.d.ts +39 -0
- package/dist/apps/control-plane/application/kernel-tool-wiring.js +38 -0
- package/dist/apps/control-plane/application/kernel-tool-wiring.js.map +1 -0
- package/dist/apps/control-plane/application/services/checkpoint-service.d.ts +84 -0
- package/dist/apps/control-plane/application/services/checkpoint-service.js +367 -0
- package/dist/apps/control-plane/application/services/checkpoint-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/feature-send-message-service.d.ts +25 -0
- package/dist/apps/control-plane/application/services/feature-send-message-service.js +105 -0
- package/dist/apps/control-plane/application/services/feature-send-message-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/patch-service.d.ts +6 -0
- package/dist/apps/control-plane/application/services/patch-service.js +11 -2
- package/dist/apps/control-plane/application/services/patch-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/repo-operations-service.d.ts +70 -0
- package/dist/apps/control-plane/application/services/repo-operations-service.js +213 -0
- package/dist/apps/control-plane/application/services/repo-operations-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/worktree-watchdog-service.d.ts +23 -0
- package/dist/apps/control-plane/application/services/worktree-watchdog-service.js +119 -0
- package/dist/apps/control-plane/application/services/worktree-watchdog-service.js.map +1 -0
- package/dist/apps/control-plane/cli/cli-argument-parser.js +12 -0
- package/dist/apps/control-plane/cli/cli-argument-parser.js.map +1 -1
- package/dist/apps/control-plane/cli/help-command-handler.js +17 -0
- package/dist/apps/control-plane/cli/help-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/init-command-handler.js +23 -0
- package/dist/apps/control-plane/cli/init-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/resume-command-handler.js +25 -5
- package/dist/apps/control-plane/cli/resume-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/rollback-command-handler.d.ts +6 -0
- package/dist/apps/control-plane/cli/rollback-command-handler.js +177 -0
- package/dist/apps/control-plane/cli/rollback-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/run-command-handler.js +7 -1
- package/dist/apps/control-plane/cli/run-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/types.d.ts +3 -0
- package/dist/apps/control-plane/cli/types.js +1 -0
- package/dist/apps/control-plane/cli/types.js.map +1 -1
- package/dist/apps/control-plane/core/configuration-service.d.ts +25 -0
- package/dist/apps/control-plane/core/configuration-service.js +130 -0
- package/dist/apps/control-plane/core/configuration-service.js.map +1 -0
- package/dist/apps/control-plane/core/kernel-tool-wiring.d.ts +50 -0
- package/dist/apps/control-plane/core/kernel-tool-wiring.js +44 -0
- package/dist/apps/control-plane/core/kernel-tool-wiring.js.map +1 -0
- package/dist/apps/control-plane/core/kernel-types.d.ts +48 -0
- package/dist/apps/control-plane/core/kernel-types.js +2 -0
- package/dist/apps/control-plane/core/kernel-types.js.map +1 -0
- package/dist/apps/control-plane/core/kernel.d.ts +17 -48
- package/dist/apps/control-plane/core/kernel.js +44 -539
- package/dist/apps/control-plane/core/kernel.js.map +1 -1
- package/dist/apps/control-plane/core/tool-caller.d.ts +10 -0
- package/dist/apps/control-plane/core/utils/error-normalizer.d.ts +2 -0
- package/dist/apps/control-plane/core/utils/error-normalizer.js +51 -0
- package/dist/apps/control-plane/core/utils/error-normalizer.js.map +1 -0
- package/dist/apps/control-plane/core/utils/field-readers.d.ts +9 -0
- package/dist/apps/control-plane/core/utils/field-readers.js +30 -0
- package/dist/apps/control-plane/core/utils/field-readers.js.map +1 -0
- package/dist/apps/control-plane/core/utils/index-normalizer.d.ts +7 -0
- package/dist/apps/control-plane/core/utils/index-normalizer.js +92 -0
- package/dist/apps/control-plane/core/utils/index-normalizer.js.map +1 -0
- package/dist/apps/control-plane/core/utils/path-normalizers.d.ts +2 -0
- package/dist/apps/control-plane/core/utils/path-normalizers.js +17 -0
- package/dist/apps/control-plane/core/utils/path-normalizers.js.map +1 -0
- package/dist/apps/control-plane/interfaces/cli/bootstrap.js +13 -1
- package/dist/apps/control-plane/interfaces/cli/bootstrap.js.map +1 -1
- package/dist/apps/control-plane/providers/api-worker-provider.d.ts +4 -13
- package/dist/apps/control-plane/providers/api-worker-provider.js +10 -0
- package/dist/apps/control-plane/providers/api-worker-provider.js.map +1 -1
- package/dist/apps/control-plane/providers/cli-worker-provider.d.ts +11 -13
- package/dist/apps/control-plane/providers/cli-worker-provider.js +64 -0
- package/dist/apps/control-plane/providers/cli-worker-provider.js.map +1 -1
- package/dist/apps/control-plane/providers/providers.d.ts +31 -24
- package/dist/apps/control-plane/providers/providers.js +10 -0
- package/dist/apps/control-plane/providers/providers.js.map +1 -1
- package/dist/apps/control-plane/providers/worker-provider-factory.d.ts +11 -0
- package/dist/apps/control-plane/providers/worker-provider-factory.js +20 -1
- package/dist/apps/control-plane/providers/worker-provider-factory.js.map +1 -1
- package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/run-coordinator.js +81 -33
- package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
- package/dist/apps/control-plane/supervisor/runtime.d.ts +8 -1
- package/dist/apps/control-plane/supervisor/runtime.js +90 -0
- package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
- package/dist/apps/control-plane/supervisor/types.d.ts +11 -0
- package/dist/apps/control-plane/supervisor/types.js.map +1 -1
- package/dist/apps/control-plane/supervisor/worker-decision-loop.d.ts +21 -1
- package/dist/apps/control-plane/supervisor/worker-decision-loop.js +207 -13
- package/dist/apps/control-plane/supervisor/worker-decision-loop.js.map +1 -1
- package/package.json +1 -1
- package/packages/web-dashboard/package.json +2 -0
- package/packages/web-dashboard/src/app/analytics/page.tsx +83 -2
- package/packages/web-dashboard/src/app/api/actions/route.ts +92 -1
- package/packages/web-dashboard/src/app/api/analytics/route.ts +5 -2
- package/packages/web-dashboard/src/app/api/features/[id]/checkpoints/[checkpointId]/diff/route.ts +43 -0
- package/packages/web-dashboard/src/app/api/features/[id]/checkpoints/compare/route.ts +45 -0
- package/packages/web-dashboard/src/app/api/features/[id]/checkpoints/stream/route.ts +170 -0
- package/packages/web-dashboard/src/app/api/features/[id]/file-diff/route.ts +144 -0
- package/packages/web-dashboard/src/app/api/features/[id]/log-stream/route.ts +167 -0
- package/packages/web-dashboard/src/app/api/features/[id]/raw-logs/[filename]/route.ts +65 -0
- package/packages/web-dashboard/src/app/api/features/[id]/raw-logs/route.ts +63 -0
- package/packages/web-dashboard/src/app/api/features/[id]/timeline/route.ts +60 -0
- package/packages/web-dashboard/src/app/feature/[id]/page.tsx +32 -11
- package/packages/web-dashboard/src/app/globals.css +2 -0
- package/packages/web-dashboard/src/components/detail-panel.tsx +483 -0
- package/packages/web-dashboard/src/components/review-workspace.tsx +1162 -0
- package/packages/web-dashboard/src/lib/aop-client.ts +725 -0
- package/packages/web-dashboard/src/lib/review-contracts.ts +182 -0
- package/packages/web-dashboard/src/lib/review-workspace-logic.ts +64 -0
- package/packages/web-dashboard/src/lib/types.ts +131 -0
- package/packages/web-dashboard/src/styles/dashboard.module.css +333 -0
- package/spec-files/completed/agentic_orchestrator_execution_mode_spec.md +1905 -0
- package/spec-files/outstanding/agentic_orchestrator_runtime_inspection_spec.md +940 -0
- package/spec-files/outstanding/execution_mode_critical_review.md +355 -0
- package/spec-files/outstanding/shadow_workspace_implementation_spec.md +1271 -0
- package/spec-files/outstanding/shadow_workspace_spec_summary.md +222 -0
- package/spec-files/progress.md +269 -1
|
@@ -8,12 +8,9 @@ import {
|
|
|
8
8
|
atomicWriteFile,
|
|
9
9
|
withFileLock,
|
|
10
10
|
nowIso,
|
|
11
|
-
stableHash,
|
|
12
11
|
} from './fs.js';
|
|
13
|
-
import { SchemaRegistry
|
|
14
|
-
import { normalizeRepoPath } from './path-rules.js';
|
|
12
|
+
import { SchemaRegistry } from './schemas.js';
|
|
15
13
|
import { parseFrontMatter, buildFrontMatter } from './frontmatter.js';
|
|
16
|
-
import { runGit, runCommand } from './git.js';
|
|
17
14
|
import { ERROR_CODES } from './error-codes.js';
|
|
18
15
|
import { ok, fail, withSuggestedActions, type ToolResponse } from './response.js';
|
|
19
16
|
import {
|
|
@@ -22,17 +19,9 @@ import {
|
|
|
22
19
|
DEFAULT_ROLE_STATUS,
|
|
23
20
|
GATE_RESULT,
|
|
24
21
|
STATUS,
|
|
25
|
-
TOOLS,
|
|
26
22
|
} from './constants.js';
|
|
27
|
-
import { AopPathLayout
|
|
28
|
-
import {
|
|
29
|
-
applyWorktreeSymlinks,
|
|
30
|
-
formatWorkspaceHookWarning,
|
|
31
|
-
runWorktreePostCreate,
|
|
32
|
-
type WorkspaceHookWarning,
|
|
33
|
-
} from './workspace-hooks.js';
|
|
23
|
+
import { AopPathLayout } from './path-layout.js';
|
|
34
24
|
import type { RuntimeSessionsSnapshot } from './runtime-sessions.js';
|
|
35
|
-
import { ToolRegistryLoader } from '../mcp/tool-registry-loader.js';
|
|
36
25
|
import {
|
|
37
26
|
ToolHandlerRegistry,
|
|
38
27
|
ToolRouter,
|
|
@@ -47,7 +36,7 @@ import { ReportingService } from '../application/services/reporting-service.js';
|
|
|
47
36
|
import { FeatureStateService } from '../application/services/feature-state-service.js';
|
|
48
37
|
import { FeatureLifecycleService } from '../application/services/feature-lifecycle-service.js';
|
|
49
38
|
import { PlanService } from '../application/services/plan-service.js';
|
|
50
|
-
import { PatchService } from '../application/services/patch-service.js';
|
|
39
|
+
import { PatchService, type PatchValidationResult } from '../application/services/patch-service.js';
|
|
51
40
|
import { GateService } from '../application/services/gate-service.js';
|
|
52
41
|
import { QaIndexService } from '../application/services/qa-index-service.js';
|
|
53
42
|
import { MergeService } from '../application/services/merge-service.js';
|
|
@@ -62,112 +51,27 @@ import {
|
|
|
62
51
|
type FeatureOutcome,
|
|
63
52
|
} from '../application/services/performance-analytics-service.js';
|
|
64
53
|
import { GateSelectionService } from '../application/services/gate-selection-service.js';
|
|
65
|
-
import { loadComposedPolicy } from '../application/services/policy-loader-service.js';
|
|
66
|
-
import {
|
|
67
|
-
ACTIVITY_DETECTOR_SLOT,
|
|
68
|
-
NOTIFICATION_CHANNEL_SLOT,
|
|
69
|
-
SCM_PROVIDER_SLOT,
|
|
70
|
-
globalAdapterRegistry,
|
|
71
|
-
} from '../application/adapters/adapter-registry.js';
|
|
72
54
|
import type { WorkerProvider } from '../providers/providers.js';
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export interface AgentsRuntimeConfig {
|
|
94
|
-
default_provider?: string;
|
|
95
|
-
default_model?: string;
|
|
96
|
-
provider_config_env?: string;
|
|
97
|
-
default_agent_config?: Record<string, unknown>;
|
|
98
|
-
provider_configs?: Record<string, Record<string, unknown>>;
|
|
99
|
-
worker_provider_mode?: 'live' | 'stub' | string;
|
|
100
|
-
worker_response_timeout_ms?: number;
|
|
101
|
-
max_consecutive_no_progress_iterations?: number;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export interface AgentsConfigSnapshot {
|
|
105
|
-
version?: number;
|
|
106
|
-
roles?: Record<string, AgentsRoleConfig>;
|
|
107
|
-
missing_prompt_behavior?: 'ignore' | 'error' | string;
|
|
108
|
-
runtime?: AgentsRuntimeConfig;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function asArray<T = unknown>(value: unknown): T[] {
|
|
112
|
-
return Array.isArray(value) ? (value as T[]) : [];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function readStringField(record: AnyRecord, key: string): string | null {
|
|
116
|
-
const value = record[key];
|
|
117
|
-
return typeof value === 'string' ? value : null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function readNumberField(record: AnyRecord, key: string): number | null {
|
|
121
|
-
const value = record[key];
|
|
122
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function readPositiveIntegerField(record: AnyRecord, key: string): number | null {
|
|
126
|
-
const value = record[key];
|
|
127
|
-
if (typeof value !== 'number' || !Number.isFinite(value) || value < 1) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
return Math.floor(value);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function readBooleanField(record: AnyRecord, key: string): boolean | null {
|
|
134
|
-
const value = record[key];
|
|
135
|
-
return typeof value === 'boolean' ? value : null;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function readObjectField(record: AnyRecord, key: string): AnyRecord {
|
|
139
|
-
const value = record[key];
|
|
140
|
-
return value && typeof value === 'object' ? (value as AnyRecord) : {};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
interface PolicyConfigSnapshot extends AnyRecord {
|
|
144
|
-
rbac?: Record<string, string[]>;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function normalizeSet(array: string[]): string[] {
|
|
148
|
-
return [...new Set(array)].sort((a, b) => a.localeCompare(b));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function validateAgentRuntimeTimeoutRelationships(agentsConfig: AnyRecord): void {
|
|
152
|
-
const runtime = readObjectField(agentsConfig, 'runtime');
|
|
153
|
-
if (Object.keys(runtime).length === 0) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const responseTimeoutMs = readPositiveIntegerField(runtime, 'worker_response_timeout_ms');
|
|
157
|
-
const spawnTimeoutMs = readPositiveIntegerField(runtime, 'worker_spawn_timeout_ms');
|
|
158
|
-
const idleTimeoutMs = readPositiveIntegerField(runtime, 'worker_idle_timeout_ms');
|
|
159
|
-
|
|
160
|
-
if (responseTimeoutMs != null && spawnTimeoutMs != null && spawnTimeoutMs >= responseTimeoutMs) {
|
|
161
|
-
throw new Error(
|
|
162
|
-
'invalid_agents_yaml:runtime.worker_spawn_timeout_ms must be less than runtime.worker_response_timeout_ms',
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
if (responseTimeoutMs != null && idleTimeoutMs != null && idleTimeoutMs > responseTimeoutMs) {
|
|
166
|
-
throw new Error(
|
|
167
|
-
'invalid_agents_yaml:runtime.worker_idle_timeout_ms must be less than or equal to runtime.worker_response_timeout_ms',
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
55
|
+
import type {
|
|
56
|
+
AnyRecord,
|
|
57
|
+
KernelContext,
|
|
58
|
+
KernelConfigOverrides,
|
|
59
|
+
AgentsConfigSnapshot,
|
|
60
|
+
PolicyConfigSnapshot,
|
|
61
|
+
} from './kernel-types.js';
|
|
62
|
+
export type { AgentsConfigSnapshot } from './kernel-types.js';
|
|
63
|
+
import { readStringField } from './utils/field-readers.js';
|
|
64
|
+
import {
|
|
65
|
+
emptyRuntimeSessions as utilEmptyRuntimeSessions,
|
|
66
|
+
normalizeRuntimeSessions as utilNormalizeRuntimeSessions,
|
|
67
|
+
normalizeIndexShape as utilNormalizeIndexShape,
|
|
68
|
+
isRunLeaseFresh as utilIsRunLeaseFresh,
|
|
69
|
+
} from './utils/index-normalizer.js';
|
|
70
|
+
import { normalizeRepoPathForState } from './utils/path-normalizers.js';
|
|
71
|
+
import { ConfigurationService } from '../application/configuration-service.js';
|
|
72
|
+
import { RepoOperationsService } from '../application/services/repo-operations-service.js';
|
|
73
|
+
import { FeatureSendMessageService } from '../application/services/feature-send-message-service.js';
|
|
74
|
+
import { registerKernelTools } from '../application/kernel-tool-wiring.js';
|
|
171
75
|
|
|
172
76
|
/**
|
|
173
77
|
* Deterministic orchestration kernel for multi-agent feature development.
|
|
@@ -230,6 +134,8 @@ export class AopKernel {
|
|
|
230
134
|
private readonly costTrackingService: CostTrackingService;
|
|
231
135
|
private readonly performanceAnalyticsService: PerformanceAnalyticsService;
|
|
232
136
|
private readonly gateSelectionService: GateSelectionService;
|
|
137
|
+
private readonly repoOperationsService: RepoOperationsService;
|
|
138
|
+
private readonly sendMessageService: FeatureSendMessageService;
|
|
233
139
|
private readonly pathLayout: AopPathLayout;
|
|
234
140
|
readonly instanceId: string;
|
|
235
141
|
private provider: WorkerProvider | null = null;
|
|
@@ -259,7 +165,7 @@ export class AopKernel {
|
|
|
259
165
|
this.adaptersConfig = {};
|
|
260
166
|
this.toolRegistry = null;
|
|
261
167
|
this.toolHandlers = new ToolHandlerRegistry();
|
|
262
|
-
this.
|
|
168
|
+
registerKernelTools(this.toolHandlers, this);
|
|
263
169
|
this.runLeaseService = new RunLeaseService(this);
|
|
264
170
|
this.collisionQueueService = new CollisionQueueService(this);
|
|
265
171
|
this.lockService = new LockService(this);
|
|
@@ -275,6 +181,12 @@ export class AopKernel {
|
|
|
275
181
|
this.costTrackingService = new CostTrackingService(this);
|
|
276
182
|
this.performanceAnalyticsService = new PerformanceAnalyticsService(this);
|
|
277
183
|
this.gateSelectionService = new GateSelectionService(this);
|
|
184
|
+
this.repoOperationsService = new RepoOperationsService(this);
|
|
185
|
+
this.sendMessageService = new FeatureSendMessageService({
|
|
186
|
+
readState: (id: string) => this.readState(id),
|
|
187
|
+
getRuntimeSessions: () => this.getRuntimeSessions(),
|
|
188
|
+
getProvider: () => this.provider,
|
|
189
|
+
});
|
|
278
190
|
this.toolRouter = new ToolRouter(this.toolHandlers, (toolName) =>
|
|
279
191
|
Promise.resolve(
|
|
280
192
|
fail(ERROR_CODES.INVALID_ARGUMENT, `Unknown tool ${toolName}`, {
|
|
@@ -342,229 +254,34 @@ export class AopKernel {
|
|
|
342
254
|
}
|
|
343
255
|
|
|
344
256
|
emptyRuntimeSessions(at = nowIso()): RuntimeSessionsSnapshot {
|
|
345
|
-
return
|
|
346
|
-
run_id: 'none',
|
|
347
|
-
orchestrator_session_id: 'unknown',
|
|
348
|
-
provider: 'unknown',
|
|
349
|
-
model: 'unknown',
|
|
350
|
-
provider_config_ref_hash: stableHash('none'),
|
|
351
|
-
owner_instance_id: 'none',
|
|
352
|
-
lease_id: 'none',
|
|
353
|
-
started_at: at,
|
|
354
|
-
last_heartbeat_at: at,
|
|
355
|
-
lease_expires_at: at,
|
|
356
|
-
orchestrator_epoch: 0,
|
|
357
|
-
feature_sessions: {},
|
|
358
|
-
};
|
|
257
|
+
return utilEmptyRuntimeSessions(at);
|
|
359
258
|
}
|
|
360
259
|
|
|
361
260
|
normalizeRuntimeSessions(value: unknown, at = nowIso()): RuntimeSessionsSnapshot {
|
|
362
|
-
|
|
363
|
-
const source = value && typeof value === 'object' ? (value as Record<string, unknown>) : {};
|
|
364
|
-
const featureSessionsInput =
|
|
365
|
-
source.feature_sessions && typeof source.feature_sessions === 'object'
|
|
366
|
-
? (source.feature_sessions as Record<string, unknown>)
|
|
367
|
-
: {};
|
|
368
|
-
const featureSessions: RuntimeSessionsSnapshot['feature_sessions'] = {};
|
|
369
|
-
|
|
370
|
-
for (const [featureId, raw] of Object.entries(featureSessionsInput)) {
|
|
371
|
-
if (!featureId || typeof raw !== 'object' || !raw) {
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
const typed = raw as Record<string, unknown>;
|
|
375
|
-
featureSessions[featureId] = {
|
|
376
|
-
planner_session_id:
|
|
377
|
-
typeof typed.planner_session_id === 'string' ? typed.planner_session_id : 'unassigned',
|
|
378
|
-
builder_session_id:
|
|
379
|
-
typeof typed.builder_session_id === 'string' ? typed.builder_session_id : 'unassigned',
|
|
380
|
-
qa_session_id: typeof typed.qa_session_id === 'string' ? typed.qa_session_id : 'unassigned',
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const epoch =
|
|
385
|
-
typeof source.orchestrator_epoch === 'number' && Number.isFinite(source.orchestrator_epoch)
|
|
386
|
-
? Math.max(0, Math.floor(source.orchestrator_epoch))
|
|
387
|
-
: 0;
|
|
388
|
-
|
|
389
|
-
return {
|
|
390
|
-
run_id: typeof source.run_id === 'string' && source.run_id ? source.run_id : fallback.run_id,
|
|
391
|
-
orchestrator_session_id:
|
|
392
|
-
typeof source.orchestrator_session_id === 'string' && source.orchestrator_session_id
|
|
393
|
-
? source.orchestrator_session_id
|
|
394
|
-
: fallback.orchestrator_session_id,
|
|
395
|
-
provider:
|
|
396
|
-
typeof source.provider === 'string' && source.provider
|
|
397
|
-
? source.provider
|
|
398
|
-
: fallback.provider,
|
|
399
|
-
model: typeof source.model === 'string' && source.model ? source.model : fallback.model,
|
|
400
|
-
provider_config_ref_hash:
|
|
401
|
-
typeof source.provider_config_ref_hash === 'string' && source.provider_config_ref_hash
|
|
402
|
-
? source.provider_config_ref_hash
|
|
403
|
-
: fallback.provider_config_ref_hash,
|
|
404
|
-
owner_instance_id:
|
|
405
|
-
typeof source.owner_instance_id === 'string' && source.owner_instance_id
|
|
406
|
-
? source.owner_instance_id
|
|
407
|
-
: fallback.owner_instance_id,
|
|
408
|
-
lease_id:
|
|
409
|
-
typeof source.lease_id === 'string' && source.lease_id
|
|
410
|
-
? source.lease_id
|
|
411
|
-
: fallback.lease_id,
|
|
412
|
-
started_at:
|
|
413
|
-
typeof source.started_at === 'string' && source.started_at
|
|
414
|
-
? source.started_at
|
|
415
|
-
: fallback.started_at,
|
|
416
|
-
last_heartbeat_at:
|
|
417
|
-
typeof source.last_heartbeat_at === 'string' && source.last_heartbeat_at
|
|
418
|
-
? source.last_heartbeat_at
|
|
419
|
-
: fallback.last_heartbeat_at,
|
|
420
|
-
lease_expires_at:
|
|
421
|
-
typeof source.lease_expires_at === 'string' && source.lease_expires_at
|
|
422
|
-
? source.lease_expires_at
|
|
423
|
-
: fallback.lease_expires_at,
|
|
424
|
-
orchestrator_epoch: epoch,
|
|
425
|
-
feature_sessions: featureSessions,
|
|
426
|
-
};
|
|
261
|
+
return utilNormalizeRuntimeSessions(value, at);
|
|
427
262
|
}
|
|
428
263
|
|
|
429
264
|
normalizeIndexShape(value: unknown): AnyRecord {
|
|
430
|
-
|
|
431
|
-
const source = value && typeof value === 'object' ? (value as Record<string, unknown>) : {};
|
|
432
|
-
return {
|
|
433
|
-
version:
|
|
434
|
-
typeof source.version === 'number' && Number.isFinite(source.version)
|
|
435
|
-
? Math.max(1, Math.floor(source.version))
|
|
436
|
-
: 1,
|
|
437
|
-
active: normalizeSet(
|
|
438
|
-
asArray<string>(source.active).filter((item) => typeof item === 'string'),
|
|
439
|
-
),
|
|
440
|
-
blocked: normalizeSet(
|
|
441
|
-
asArray<string>(source.blocked).filter((item) => typeof item === 'string'),
|
|
442
|
-
),
|
|
443
|
-
merged: normalizeSet(
|
|
444
|
-
asArray<string>(source.merged).filter((item) => typeof item === 'string'),
|
|
445
|
-
),
|
|
446
|
-
locks: source.locks && typeof source.locks === 'object' ? source.locks : {},
|
|
447
|
-
lock_leases:
|
|
448
|
-
source.lock_leases && typeof source.lock_leases === 'object' ? source.lock_leases : {},
|
|
449
|
-
blocked_queue: asArray(source.blocked_queue).filter(
|
|
450
|
-
(item) => item && typeof item === 'object',
|
|
451
|
-
),
|
|
452
|
-
dep_blocked: asArray(source.dep_blocked).filter((item) => item && typeof item === 'object'),
|
|
453
|
-
updated_at:
|
|
454
|
-
typeof source.updated_at === 'string' && source.updated_at ? source.updated_at : now,
|
|
455
|
-
runtime_sessions: this.normalizeRuntimeSessions(source.runtime_sessions, now),
|
|
456
|
-
};
|
|
265
|
+
return utilNormalizeIndexShape(value);
|
|
457
266
|
}
|
|
458
267
|
|
|
459
268
|
isRunLeaseFresh(runtimeSessions: RuntimeSessionsSnapshot): boolean {
|
|
460
|
-
|
|
461
|
-
if (!Number.isFinite(expiry)) {
|
|
462
|
-
return false;
|
|
463
|
-
}
|
|
464
|
-
return expiry > Date.now();
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
private async resolveDefaultConfigPath(fileName: string): Promise<string> {
|
|
468
|
-
const primary = path.join(this.pathLayout.orchestratorRoot, fileName);
|
|
469
|
-
if (await pathExists(primary)) {
|
|
470
|
-
return primary;
|
|
471
|
-
}
|
|
472
|
-
const legacy = path.join(this.pathLayout.legacyOrchestratorRoot, fileName);
|
|
473
|
-
if (await pathExists(legacy)) {
|
|
474
|
-
return legacy;
|
|
475
|
-
}
|
|
476
|
-
return primary;
|
|
269
|
+
return utilIsRunLeaseFresh(runtimeSessions);
|
|
477
270
|
}
|
|
478
271
|
|
|
479
272
|
async load() {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const gatesPath =
|
|
483
|
-
this.configOverrides.gatesPath ?? (await this.resolveDefaultConfigPath('gates.yaml'));
|
|
484
|
-
const policyPath =
|
|
485
|
-
this.configOverrides.policyPath ?? (await this.resolveDefaultConfigPath('policy.yaml'));
|
|
486
|
-
const agentsPath =
|
|
487
|
-
this.configOverrides.agentsPath ?? (await this.resolveDefaultConfigPath('agents.yaml'));
|
|
488
|
-
const adaptersPath =
|
|
489
|
-
this.configOverrides.adaptersPath ?? (await this.resolveDefaultConfigPath('adapters.yaml'));
|
|
490
|
-
|
|
491
|
-
const gates = await loadAndValidateYaml(this.schemaRegistry, 'gates.schema.json', gatesPath);
|
|
492
|
-
if (!gates.validation.valid) {
|
|
493
|
-
throw new Error(`invalid_gates_yaml:${JSON.stringify(gates.validation.errors)}`);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const { mergedPolicy } = await loadComposedPolicy(
|
|
273
|
+
const configService = new ConfigurationService(
|
|
497
274
|
this.repoRoot,
|
|
498
|
-
|
|
275
|
+
this.pathLayout,
|
|
499
276
|
this.schemaRegistry,
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
if (readStringField(testing, 'framework') !== 'vitest') {
|
|
509
|
-
throw new Error(ERROR_CODES.INVALID_WORKSPACE_IMPLEMENTATION);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const agentsExists = await pathExists(agentsPath);
|
|
513
|
-
let agents = { parsed: { version: 1, roles: {} }, validation: { valid: true, errors: [] } };
|
|
514
|
-
if (agentsExists) {
|
|
515
|
-
agents = await loadAndValidateYaml(this.schemaRegistry, 'agents.schema.json', agentsPath);
|
|
516
|
-
if (!agents.validation.valid) {
|
|
517
|
-
throw new Error(`invalid_agents_yaml:${JSON.stringify(agents.validation.errors)}`);
|
|
518
|
-
}
|
|
519
|
-
validateAgentRuntimeTimeoutRelationships(agents.parsed as AnyRecord);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
const adaptersExists = await pathExists(adaptersPath);
|
|
523
|
-
let adapters = { parsed: {}, validation: { valid: true, errors: [] as unknown[] } };
|
|
524
|
-
if (adaptersExists) {
|
|
525
|
-
adapters = await loadAndValidateYaml(
|
|
526
|
-
this.schemaRegistry,
|
|
527
|
-
'adapters.schema.json',
|
|
528
|
-
adaptersPath,
|
|
529
|
-
);
|
|
530
|
-
if (!adapters.validation.valid) {
|
|
531
|
-
throw new Error(`invalid_adapters_yaml:${JSON.stringify(adapters.validation.errors)}`);
|
|
532
|
-
}
|
|
533
|
-
const parsedAdapters = readObjectField(adapters, 'parsed');
|
|
534
|
-
const notificationChannel = readStringField(parsedAdapters, NOTIFICATION_CHANNEL_SLOT.name);
|
|
535
|
-
if (notificationChannel) {
|
|
536
|
-
try {
|
|
537
|
-
globalAdapterRegistry.resolve(NOTIFICATION_CHANNEL_SLOT, notificationChannel, {});
|
|
538
|
-
} catch {
|
|
539
|
-
throw new Error(
|
|
540
|
-
`adapter_not_found:${NOTIFICATION_CHANNEL_SLOT.name}:${notificationChannel}`,
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
const activityDetector = readStringField(parsedAdapters, ACTIVITY_DETECTOR_SLOT.name);
|
|
545
|
-
if (activityDetector) {
|
|
546
|
-
try {
|
|
547
|
-
globalAdapterRegistry.resolve(ACTIVITY_DETECTOR_SLOT, activityDetector, {});
|
|
548
|
-
} catch {
|
|
549
|
-
throw new Error(`adapter_not_found:${ACTIVITY_DETECTOR_SLOT.name}:${activityDetector}`);
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
const scmProvider = readStringField(parsedAdapters, SCM_PROVIDER_SLOT.name);
|
|
553
|
-
if (scmProvider) {
|
|
554
|
-
try {
|
|
555
|
-
globalAdapterRegistry.resolve(SCM_PROVIDER_SLOT, scmProvider, {});
|
|
556
|
-
} catch {
|
|
557
|
-
throw new Error(`adapter_not_found:${SCM_PROVIDER_SLOT.name}:${scmProvider}`);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
this.gatesConfig = gates.parsed;
|
|
563
|
-
this.policy = parsedPolicy;
|
|
564
|
-
this.agentsConfig = agents.parsed as AgentsConfigSnapshot;
|
|
565
|
-
this.adaptersConfig = readObjectField(adapters, 'parsed');
|
|
566
|
-
const registryLoader = new ToolRegistryLoader(this.repoRoot);
|
|
567
|
-
this.toolRegistry = await registryLoader.load();
|
|
277
|
+
this.configOverrides,
|
|
278
|
+
);
|
|
279
|
+
const config = await configService.loadAll();
|
|
280
|
+
this.gatesConfig = config.gatesConfig;
|
|
281
|
+
this.policy = config.policy;
|
|
282
|
+
this.agentsConfig = config.agentsConfig;
|
|
283
|
+
this.adaptersConfig = config.adaptersConfig;
|
|
284
|
+
this.toolRegistry = config.toolRegistry;
|
|
568
285
|
this.loaded = true;
|
|
569
286
|
}
|
|
570
287
|
|
|
@@ -684,208 +401,6 @@ export class AopKernel {
|
|
|
684
401
|
return await this.toolRouter.route(toolName, args, context);
|
|
685
402
|
}
|
|
686
403
|
|
|
687
|
-
private registerToolHandlers(): void {
|
|
688
|
-
this.toolHandlers.register(
|
|
689
|
-
TOOLS.FEATURE_DISCOVER_SPECS,
|
|
690
|
-
async () => await this.featureDiscoverSpecs(),
|
|
691
|
-
);
|
|
692
|
-
this.toolHandlers.register(
|
|
693
|
-
TOOLS.FEATURE_INIT,
|
|
694
|
-
async (args) => await this.featureInit(readStringField(args, 'feature_id')),
|
|
695
|
-
);
|
|
696
|
-
this.toolHandlers.register(
|
|
697
|
-
TOOLS.FEATURE_GET_CONTEXT,
|
|
698
|
-
async (args) => await this.featureGetContext(readStringField(args, 'feature_id')),
|
|
699
|
-
);
|
|
700
|
-
this.toolHandlers.register(
|
|
701
|
-
TOOLS.FEATURE_STATE_GET,
|
|
702
|
-
async (args) => await this.featureStateGet(readStringField(args, 'feature_id')),
|
|
703
|
-
);
|
|
704
|
-
this.toolHandlers.register(
|
|
705
|
-
TOOLS.FEATURE_STATE_PATCH,
|
|
706
|
-
async (args) =>
|
|
707
|
-
await this.featureStatePatch(
|
|
708
|
-
readStringField(args, 'feature_id'),
|
|
709
|
-
readNumberField(args, 'expected_version'),
|
|
710
|
-
args.patch,
|
|
711
|
-
),
|
|
712
|
-
);
|
|
713
|
-
this.toolHandlers.register(
|
|
714
|
-
TOOLS.FEATURE_LOG_APPEND,
|
|
715
|
-
async (args, context) =>
|
|
716
|
-
await this.featureLogAppend(
|
|
717
|
-
readStringField(args, 'feature_id'),
|
|
718
|
-
readStringField(args, 'note'),
|
|
719
|
-
context,
|
|
720
|
-
),
|
|
721
|
-
);
|
|
722
|
-
this.toolHandlers.register(
|
|
723
|
-
TOOLS.PLAN_SUBMIT,
|
|
724
|
-
async (args) =>
|
|
725
|
-
await this.planSubmit(
|
|
726
|
-
readStringField(args, 'feature_id'),
|
|
727
|
-
args.plan_json,
|
|
728
|
-
readNumberField(args, 'expected_version'),
|
|
729
|
-
),
|
|
730
|
-
);
|
|
731
|
-
this.toolHandlers.register(
|
|
732
|
-
TOOLS.PLAN_GET,
|
|
733
|
-
async (args) => await this.planGet(readStringField(args, 'feature_id')),
|
|
734
|
-
);
|
|
735
|
-
this.toolHandlers.register(
|
|
736
|
-
TOOLS.PLAN_UPDATE,
|
|
737
|
-
async (args) =>
|
|
738
|
-
await this.planUpdate(
|
|
739
|
-
readStringField(args, 'feature_id'),
|
|
740
|
-
readNumberField(args, 'expected_plan_version'),
|
|
741
|
-
args.plan_json,
|
|
742
|
-
),
|
|
743
|
-
);
|
|
744
|
-
this.toolHandlers.register(
|
|
745
|
-
TOOLS.REPO_ENSURE_WORKTREE,
|
|
746
|
-
async (args) => await this.repoEnsureWorktree(readStringField(args, 'feature_id')),
|
|
747
|
-
);
|
|
748
|
-
this.toolHandlers.register(
|
|
749
|
-
TOOLS.REPO_APPLY_PATCH,
|
|
750
|
-
async (args) =>
|
|
751
|
-
await this.repoApplyPatch(
|
|
752
|
-
readStringField(args, 'feature_id'),
|
|
753
|
-
readStringField(args, 'unified_diff'),
|
|
754
|
-
),
|
|
755
|
-
);
|
|
756
|
-
this.toolHandlers.register(
|
|
757
|
-
TOOLS.REPO_STATUS,
|
|
758
|
-
async (args) => await this.repoStatus(readStringField(args, 'feature_id')),
|
|
759
|
-
);
|
|
760
|
-
this.toolHandlers.register(
|
|
761
|
-
TOOLS.REPO_DIFF,
|
|
762
|
-
async (args) =>
|
|
763
|
-
await this.repoDiff(readStringField(args, 'feature_id'), asArray<string>(args.options)),
|
|
764
|
-
);
|
|
765
|
-
this.toolHandlers.register(
|
|
766
|
-
TOOLS.REPO_READ_FILE,
|
|
767
|
-
async (args) =>
|
|
768
|
-
await this.repoReadFile(readStringField(args, 'feature_id'), readStringField(args, 'path')),
|
|
769
|
-
);
|
|
770
|
-
this.toolHandlers.register(
|
|
771
|
-
TOOLS.REPO_SEARCH,
|
|
772
|
-
async (args) =>
|
|
773
|
-
await this.repoSearch(readStringField(args, 'feature_id'), readStringField(args, 'query')),
|
|
774
|
-
);
|
|
775
|
-
this.toolHandlers.register(
|
|
776
|
-
TOOLS.REPO_DIFF_BUNDLE,
|
|
777
|
-
async (args) => await this.repoDiffBundle(readStringField(args, 'feature_id')),
|
|
778
|
-
);
|
|
779
|
-
this.toolHandlers.register(
|
|
780
|
-
TOOLS.FEATURE_READY_TO_MERGE,
|
|
781
|
-
async (args) =>
|
|
782
|
-
await this.featureReadyToMerge(
|
|
783
|
-
readStringField(args, 'feature_id'),
|
|
784
|
-
readStringField(args, 'commit_message'),
|
|
785
|
-
readStringField(args, 'merge_strategy'),
|
|
786
|
-
readStringField(args, 'user_approval_token'),
|
|
787
|
-
),
|
|
788
|
-
);
|
|
789
|
-
this.toolHandlers.register(
|
|
790
|
-
TOOLS.FEATURE_DELETE,
|
|
791
|
-
async (args) =>
|
|
792
|
-
await this.featureDelete(
|
|
793
|
-
readStringField(args, 'feature_id'),
|
|
794
|
-
readBooleanField(args, 'dry_run'),
|
|
795
|
-
readBooleanField(args, 'confirm'),
|
|
796
|
-
readBooleanField(args, 'remove_worktree'),
|
|
797
|
-
readStringField(args, 'remove_branch'),
|
|
798
|
-
),
|
|
799
|
-
);
|
|
800
|
-
this.toolHandlers.register(
|
|
801
|
-
TOOLS.GATES_LIST,
|
|
802
|
-
async (args) => await this.gatesList(readStringField(args, 'profile')),
|
|
803
|
-
);
|
|
804
|
-
this.toolHandlers.register(
|
|
805
|
-
TOOLS.GATES_RUN,
|
|
806
|
-
async (args) =>
|
|
807
|
-
await this.gatesRun(
|
|
808
|
-
readStringField(args, 'feature_id'),
|
|
809
|
-
readStringField(args, 'profile'),
|
|
810
|
-
readStringField(args, 'mode'),
|
|
811
|
-
),
|
|
812
|
-
);
|
|
813
|
-
this.toolHandlers.register(
|
|
814
|
-
TOOLS.EVIDENCE_LATEST,
|
|
815
|
-
async (args) => await this.evidenceLatest(readStringField(args, 'feature_id')),
|
|
816
|
-
);
|
|
817
|
-
this.toolHandlers.register(
|
|
818
|
-
TOOLS.QA_TEST_INDEX_GET,
|
|
819
|
-
async (args) => await this.qaTestIndexGet(readStringField(args, 'feature_id')),
|
|
820
|
-
);
|
|
821
|
-
this.toolHandlers.register(
|
|
822
|
-
TOOLS.QA_TEST_INDEX_UPDATE,
|
|
823
|
-
async (args) =>
|
|
824
|
-
await this.qaTestIndexUpdate(
|
|
825
|
-
readStringField(args, 'feature_id'),
|
|
826
|
-
readNumberField(args, 'expected_version'),
|
|
827
|
-
args.updates,
|
|
828
|
-
asArray(args.evidence_refs),
|
|
829
|
-
),
|
|
830
|
-
);
|
|
831
|
-
this.toolHandlers.register(
|
|
832
|
-
TOOLS.LOCKS_ACQUIRE,
|
|
833
|
-
async (args) =>
|
|
834
|
-
await this.locksAcquire(
|
|
835
|
-
readStringField(args, 'resource'),
|
|
836
|
-
readStringField(args, 'feature_id'),
|
|
837
|
-
readNumberField(args, 'wait_timeout_seconds'),
|
|
838
|
-
),
|
|
839
|
-
);
|
|
840
|
-
this.toolHandlers.register(
|
|
841
|
-
TOOLS.LOCKS_RELEASE,
|
|
842
|
-
async (args) =>
|
|
843
|
-
await this.locksRelease(
|
|
844
|
-
readStringField(args, 'resource'),
|
|
845
|
-
readStringField(args, 'feature_id'),
|
|
846
|
-
),
|
|
847
|
-
);
|
|
848
|
-
this.toolHandlers.register(TOOLS.COLLISIONS_SCAN, async () => await this.collisionsScan());
|
|
849
|
-
this.toolHandlers.register(TOOLS.REPORT_DASHBOARD, async () => await this.reportDashboard());
|
|
850
|
-
this.toolHandlers.register(
|
|
851
|
-
TOOLS.REPORT_FEATURE_SUMMARY,
|
|
852
|
-
async (args) => await this.reportFeatureSummary(readStringField(args, 'feature_id')),
|
|
853
|
-
);
|
|
854
|
-
this.toolHandlers.register(
|
|
855
|
-
TOOLS.FEATURE_SEND_MESSAGE,
|
|
856
|
-
async (args) =>
|
|
857
|
-
await this.featureSendMessage(
|
|
858
|
-
readStringField(args, 'feature_id'),
|
|
859
|
-
readStringField(args, 'message'),
|
|
860
|
-
),
|
|
861
|
-
);
|
|
862
|
-
this.toolHandlers.register(
|
|
863
|
-
TOOLS.COST_RECORD,
|
|
864
|
-
async (args) =>
|
|
865
|
-
await this.costRecord(
|
|
866
|
-
readStringField(args, 'feature_id'),
|
|
867
|
-
typeof args.tokens_used_delta === 'number' ? args.tokens_used_delta : 0,
|
|
868
|
-
typeof args.estimated_cost_usd_delta === 'number' ? args.estimated_cost_usd_delta : 0,
|
|
869
|
-
),
|
|
870
|
-
);
|
|
871
|
-
this.toolHandlers.register(
|
|
872
|
-
TOOLS.COST_GET,
|
|
873
|
-
async (args) => await this.costGet(readStringField(args, 'feature_id')),
|
|
874
|
-
);
|
|
875
|
-
this.toolHandlers.register(
|
|
876
|
-
TOOLS.PERFORMANCE_RECORD_OUTCOME,
|
|
877
|
-
async (args) => await this.performanceRecordOutcome(args),
|
|
878
|
-
);
|
|
879
|
-
this.toolHandlers.register(
|
|
880
|
-
TOOLS.PERFORMANCE_GET_ANALYTICS,
|
|
881
|
-
async (args) =>
|
|
882
|
-
await this.performanceGetAnalytics(
|
|
883
|
-
readStringField(args, 'provider'),
|
|
884
|
-
readStringField(args, 'model'),
|
|
885
|
-
),
|
|
886
|
-
);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
404
|
featurePath(featureId) {
|
|
890
405
|
return this.pathLayout.featureRoot(featureId);
|
|
891
406
|
}
|
|
@@ -946,12 +461,15 @@ export class AopKernel {
|
|
|
946
461
|
}
|
|
947
462
|
|
|
948
463
|
makeDefaultState(featureId, branch, worktreePath) {
|
|
464
|
+
const configuredExecutionMode =
|
|
465
|
+
this.agentsConfig.runtime?.execution_mode === 'interactive' ? 'interactive' : 'deterministic';
|
|
949
466
|
return {
|
|
950
467
|
feature_id: featureId,
|
|
951
468
|
version: 1,
|
|
952
469
|
branch,
|
|
953
470
|
worktree_path: normalizeRepoPathForState(this.repoRoot, worktreePath),
|
|
954
471
|
status: STATUS.PLANNING,
|
|
472
|
+
execution_mode: configuredExecutionMode,
|
|
955
473
|
gate_profile: 'default',
|
|
956
474
|
gates: {
|
|
957
475
|
plan: GATE_RESULT.NA,
|
|
@@ -969,6 +487,7 @@ export class AopKernel {
|
|
|
969
487
|
},
|
|
970
488
|
cluster: { ...DEFAULT_CLUSTER },
|
|
971
489
|
role_status: { ...DEFAULT_ROLE_STATUS },
|
|
490
|
+
checkpoints: [],
|
|
972
491
|
last_updated: nowIso(),
|
|
973
492
|
};
|
|
974
493
|
}
|
|
@@ -1256,106 +775,7 @@ export class AopKernel {
|
|
|
1256
775
|
}
|
|
1257
776
|
|
|
1258
777
|
async repoEnsureWorktree(featureId) {
|
|
1259
|
-
|
|
1260
|
-
const branch = featureId;
|
|
1261
|
-
await ensureDir(path.join(this.repoRoot, '.worktrees'));
|
|
1262
|
-
|
|
1263
|
-
if (await pathExists(worktree)) {
|
|
1264
|
-
return {
|
|
1265
|
-
data: {
|
|
1266
|
-
feature_id: featureId,
|
|
1267
|
-
branch,
|
|
1268
|
-
worktree_path_abs: worktree,
|
|
1269
|
-
existed: true,
|
|
1270
|
-
},
|
|
1271
|
-
};
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
const baseBranch = this.policy.worktree.base_branch;
|
|
1275
|
-
const baseCheck = await runGit(this.repoRoot, ['rev-parse', '--verify', baseBranch]);
|
|
1276
|
-
const baseRef = baseCheck.code === 0 ? baseBranch : 'HEAD';
|
|
1277
|
-
|
|
1278
|
-
const branchCheck = await runGit(this.repoRoot, ['rev-parse', '--verify', branch]);
|
|
1279
|
-
if (branchCheck.code !== 0) {
|
|
1280
|
-
const branchCreate = await runGit(this.repoRoot, ['branch', branch, baseRef]);
|
|
1281
|
-
if (branchCreate.code !== 0) {
|
|
1282
|
-
throw {
|
|
1283
|
-
normalizedResponse: fail(
|
|
1284
|
-
ERROR_CODES.GIT_FAILURE,
|
|
1285
|
-
'Unable to create feature branch',
|
|
1286
|
-
{
|
|
1287
|
-
feature_id: featureId,
|
|
1288
|
-
stderr: branchCreate.stderr,
|
|
1289
|
-
retryable: false,
|
|
1290
|
-
requires_human: true,
|
|
1291
|
-
},
|
|
1292
|
-
{
|
|
1293
|
-
command: ['git', 'branch', branch, baseRef],
|
|
1294
|
-
exit_code: branchCreate.code,
|
|
1295
|
-
},
|
|
1296
|
-
),
|
|
1297
|
-
};
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
const addWorktree = await runGit(this.repoRoot, ['worktree', 'add', worktree, branch]);
|
|
1302
|
-
if (addWorktree.code !== 0) {
|
|
1303
|
-
throw {
|
|
1304
|
-
normalizedResponse: fail(
|
|
1305
|
-
ERROR_CODES.GIT_FAILURE,
|
|
1306
|
-
'Unable to create git worktree',
|
|
1307
|
-
{
|
|
1308
|
-
feature_id: featureId,
|
|
1309
|
-
stderr: addWorktree.stderr,
|
|
1310
|
-
retryable: false,
|
|
1311
|
-
requires_human: true,
|
|
1312
|
-
},
|
|
1313
|
-
{
|
|
1314
|
-
command: ['git', 'worktree', 'add', worktree, branch],
|
|
1315
|
-
exit_code: addWorktree.code,
|
|
1316
|
-
},
|
|
1317
|
-
),
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
const worktreeConfig = this.policy.worktree as
|
|
1322
|
-
| {
|
|
1323
|
-
base_branch: string;
|
|
1324
|
-
symlinks?: string[];
|
|
1325
|
-
post_create?: string[];
|
|
1326
|
-
}
|
|
1327
|
-
| undefined;
|
|
1328
|
-
const hookWarnings: WorkspaceHookWarning[] = [];
|
|
1329
|
-
const collectHookWarning = (warning: WorkspaceHookWarning) => {
|
|
1330
|
-
hookWarnings.push(warning);
|
|
1331
|
-
};
|
|
1332
|
-
|
|
1333
|
-
if (worktreeConfig?.symlinks?.length) {
|
|
1334
|
-
await applyWorktreeSymlinks(
|
|
1335
|
-
this.repoRoot,
|
|
1336
|
-
worktree,
|
|
1337
|
-
worktreeConfig.symlinks,
|
|
1338
|
-
collectHookWarning,
|
|
1339
|
-
);
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
if (worktreeConfig?.post_create?.length) {
|
|
1343
|
-
await runWorktreePostCreate(worktree, worktreeConfig.post_create, collectHookWarning);
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
for (const warning of hookWarnings) {
|
|
1347
|
-
// Preserve non-fatal behavior while making hook failures observable.
|
|
1348
|
-
console.warn(`[aop] workspace hook warning: ${formatWorkspaceHookWarning(warning)}`);
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
return {
|
|
1352
|
-
data: {
|
|
1353
|
-
feature_id: featureId,
|
|
1354
|
-
branch,
|
|
1355
|
-
worktree_path_abs: worktree,
|
|
1356
|
-
existed: false,
|
|
1357
|
-
},
|
|
1358
|
-
};
|
|
778
|
+
return this.repoOperationsService.repoEnsureWorktree(featureId);
|
|
1359
779
|
}
|
|
1360
780
|
|
|
1361
781
|
async loadAcceptedPlan(featureId) {
|
|
@@ -1370,145 +790,28 @@ export class AopKernel {
|
|
|
1370
790
|
return await this.patchService.repoApplyPatch(featureId, unifiedDiff);
|
|
1371
791
|
}
|
|
1372
792
|
|
|
1373
|
-
async
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
const branch = await runGit(this.repoRoot, ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
1377
|
-
cwd: worktree,
|
|
1378
|
-
});
|
|
793
|
+
async validatePatchDiff(featureId: string, parsedDiff: unknown): Promise<PatchValidationResult> {
|
|
794
|
+
return await this.patchService.validateDiff(featureId, parsedDiff);
|
|
795
|
+
}
|
|
1379
796
|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
feature_id: featureId,
|
|
1383
|
-
branch: branch.stdout.trim(),
|
|
1384
|
-
status_porcelain: status.stdout.trim().split('\n').filter(Boolean),
|
|
1385
|
-
},
|
|
1386
|
-
};
|
|
797
|
+
async repoStatus(featureId) {
|
|
798
|
+
return this.repoOperationsService.repoStatus(featureId);
|
|
1387
799
|
}
|
|
1388
800
|
|
|
1389
801
|
async repoDiff(featureId, options = []) {
|
|
1390
|
-
|
|
1391
|
-
(option) => typeof option === 'string' && option.startsWith('--'),
|
|
1392
|
-
);
|
|
1393
|
-
const diff = await runGit(this.repoRoot, ['diff', ...safeOptions], {
|
|
1394
|
-
cwd: this.worktreePath(featureId),
|
|
1395
|
-
});
|
|
1396
|
-
return {
|
|
1397
|
-
data: {
|
|
1398
|
-
feature_id: featureId,
|
|
1399
|
-
diff: diff.stdout,
|
|
1400
|
-
},
|
|
1401
|
-
};
|
|
802
|
+
return this.repoOperationsService.repoDiff(featureId, options);
|
|
1402
803
|
}
|
|
1403
804
|
|
|
1404
805
|
async repoReadFile(featureId, filePath) {
|
|
1405
|
-
|
|
1406
|
-
this.repoRoot,
|
|
1407
|
-
path.join(this.worktreePath(featureId), filePath),
|
|
1408
|
-
this.policy.path_rules.allow_symlink_traversal,
|
|
1409
|
-
).then((relative) =>
|
|
1410
|
-
normalizeFromWorktree(this.worktreePath(featureId), this.repoRoot, relative),
|
|
1411
|
-
);
|
|
1412
|
-
const absolute = path.join(this.repoRoot, normalized);
|
|
1413
|
-
const exists = await pathExists(absolute);
|
|
1414
|
-
if (!exists) {
|
|
1415
|
-
throw {
|
|
1416
|
-
normalizedResponse: fail(ERROR_CODES.FILE_NOT_FOUND, 'File not found', {
|
|
1417
|
-
path: normalized,
|
|
1418
|
-
retryable: false,
|
|
1419
|
-
requires_human: false,
|
|
1420
|
-
}),
|
|
1421
|
-
};
|
|
1422
|
-
}
|
|
1423
|
-
const content = await fs.readFile(absolute, 'utf8');
|
|
1424
|
-
return {
|
|
1425
|
-
data: {
|
|
1426
|
-
feature_id: featureId,
|
|
1427
|
-
path: normalized,
|
|
1428
|
-
content,
|
|
1429
|
-
},
|
|
1430
|
-
};
|
|
806
|
+
return this.repoOperationsService.repoReadFile(featureId, filePath);
|
|
1431
807
|
}
|
|
1432
808
|
|
|
1433
809
|
async repoSearch(featureId, query) {
|
|
1434
|
-
|
|
1435
|
-
const rgResult = await runCommand('rg', ['-n', '--no-heading', query, '.'], {
|
|
1436
|
-
cwd: worktree,
|
|
1437
|
-
timeoutMs: 30_000,
|
|
1438
|
-
});
|
|
1439
|
-
|
|
1440
|
-
if (rgResult.code === 127) {
|
|
1441
|
-
throw {
|
|
1442
|
-
normalizedResponse: fail(
|
|
1443
|
-
ERROR_CODES.GIT_FAILURE,
|
|
1444
|
-
'ripgrep (rg) not found - required for search functionality',
|
|
1445
|
-
{
|
|
1446
|
-
stderr: rgResult.stderr,
|
|
1447
|
-
retryable: false,
|
|
1448
|
-
requires_human: true,
|
|
1449
|
-
},
|
|
1450
|
-
),
|
|
1451
|
-
};
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
if (rgResult.code !== 0 && rgResult.code !== 1) {
|
|
1455
|
-
throw {
|
|
1456
|
-
normalizedResponse: fail(ERROR_CODES.GIT_FAILURE, 'Search failed', {
|
|
1457
|
-
stderr: rgResult.stderr,
|
|
1458
|
-
retryable: true,
|
|
1459
|
-
requires_human: false,
|
|
1460
|
-
}),
|
|
1461
|
-
};
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
const matches = rgResult.stdout
|
|
1465
|
-
.trim()
|
|
1466
|
-
.split('\n')
|
|
1467
|
-
.filter(Boolean)
|
|
1468
|
-
.map((line) => {
|
|
1469
|
-
const firstColon = line.indexOf(':');
|
|
1470
|
-
const secondColon = line.indexOf(':', firstColon + 1);
|
|
1471
|
-
if (firstColon === -1 || secondColon === -1) {
|
|
1472
|
-
return { raw: line };
|
|
1473
|
-
}
|
|
1474
|
-
return {
|
|
1475
|
-
path: line.slice(0, firstColon),
|
|
1476
|
-
line: Number(line.slice(firstColon + 1, secondColon)),
|
|
1477
|
-
snippet: line.slice(secondColon + 1),
|
|
1478
|
-
};
|
|
1479
|
-
});
|
|
1480
|
-
|
|
1481
|
-
return {
|
|
1482
|
-
data: {
|
|
1483
|
-
feature_id: featureId,
|
|
1484
|
-
query,
|
|
1485
|
-
matches,
|
|
1486
|
-
},
|
|
1487
|
-
};
|
|
810
|
+
return this.repoOperationsService.repoSearch(featureId, query);
|
|
1488
811
|
}
|
|
1489
812
|
|
|
1490
813
|
async repoDiffBundle(featureId) {
|
|
1491
|
-
|
|
1492
|
-
cwd: this.worktreePath(featureId),
|
|
1493
|
-
});
|
|
1494
|
-
const full = await runGit(this.repoRoot, ['diff'], { cwd: this.worktreePath(featureId) });
|
|
1495
|
-
const names = await runGit(this.repoRoot, ['diff', '--name-only'], {
|
|
1496
|
-
cwd: this.worktreePath(featureId),
|
|
1497
|
-
});
|
|
1498
|
-
const latest = await this.evidenceLatest(featureId);
|
|
1499
|
-
|
|
1500
|
-
return {
|
|
1501
|
-
data: {
|
|
1502
|
-
feature_id: featureId,
|
|
1503
|
-
diff_stat: stat.stdout,
|
|
1504
|
-
diff: full.stdout,
|
|
1505
|
-
touched_files: names.stdout
|
|
1506
|
-
.split('\n')
|
|
1507
|
-
.map((x) => x.trim())
|
|
1508
|
-
.filter(Boolean),
|
|
1509
|
-
last_gate_summary: latest.data?.latest ?? null,
|
|
1510
|
-
},
|
|
1511
|
-
};
|
|
814
|
+
return this.repoOperationsService.repoDiffBundle(featureId);
|
|
1512
815
|
}
|
|
1513
816
|
|
|
1514
817
|
async gatesList(profileName = null) {
|
|
@@ -1592,107 +895,8 @@ export class AopKernel {
|
|
|
1592
895
|
return await this.lockService.recoverFromState();
|
|
1593
896
|
}
|
|
1594
897
|
|
|
1595
|
-
private async waitForSessionToBecomeActive(sessionId: string): Promise<void> {
|
|
1596
|
-
if (!this.provider?.getSessionInfo) {
|
|
1597
|
-
return;
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
const timeoutMs = 5000;
|
|
1601
|
-
const pollIntervalMs = 250;
|
|
1602
|
-
const deadline = Date.now() + timeoutMs;
|
|
1603
|
-
|
|
1604
|
-
while (Date.now() <= deadline) {
|
|
1605
|
-
const sessionInfo = await this.provider.getSessionInfo(sessionId).catch(() => null);
|
|
1606
|
-
if (sessionInfo?.active) {
|
|
1607
|
-
return;
|
|
1608
|
-
}
|
|
1609
|
-
await new Promise<void>((resolve) => {
|
|
1610
|
-
setTimeout(resolve, pollIntervalMs);
|
|
1611
|
-
});
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
898
|
async featureSendMessage(featureId: string | null, message: string | null): Promise<unknown> {
|
|
1616
|
-
|
|
1617
|
-
throw {
|
|
1618
|
-
normalizedResponse: fail(ERROR_CODES.INVALID_ARGUMENT, 'feature_id is required', {
|
|
1619
|
-
retryable: false,
|
|
1620
|
-
requires_human: false,
|
|
1621
|
-
}),
|
|
1622
|
-
};
|
|
1623
|
-
}
|
|
1624
|
-
if (!message) {
|
|
1625
|
-
throw {
|
|
1626
|
-
normalizedResponse: fail(
|
|
1627
|
-
ERROR_CODES.INVALID_ARGUMENT,
|
|
1628
|
-
'message is required and must not be empty',
|
|
1629
|
-
{ retryable: false, requires_human: false },
|
|
1630
|
-
),
|
|
1631
|
-
};
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
const runtimeSessions = await this.getRuntimeSessions();
|
|
1635
|
-
const featureSession = runtimeSessions.feature_sessions?.[featureId];
|
|
1636
|
-
if (!featureSession) {
|
|
1637
|
-
throw {
|
|
1638
|
-
normalizedResponse: {
|
|
1639
|
-
ok: false,
|
|
1640
|
-
error: { code: 'session_not_found', message: 'No active session cluster for feature' },
|
|
1641
|
-
},
|
|
1642
|
-
};
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
const state = await this.readState(featureId);
|
|
1646
|
-
const status = typeof state.frontMatter.status === 'string' ? state.frontMatter.status : '';
|
|
1647
|
-
const gates = readObjectField(state.frontMatter, 'gates');
|
|
1648
|
-
|
|
1649
|
-
let targetRole = 'orchestrator';
|
|
1650
|
-
let targetSessionId = runtimeSessions.orchestrator_session_id;
|
|
1651
|
-
|
|
1652
|
-
if (status === STATUS.PLANNING) {
|
|
1653
|
-
targetRole = 'planner';
|
|
1654
|
-
targetSessionId = featureSession.planner_session_id;
|
|
1655
|
-
} else if (status === STATUS.BUILDING) {
|
|
1656
|
-
targetRole = 'builder';
|
|
1657
|
-
targetSessionId = featureSession.builder_session_id;
|
|
1658
|
-
} else if (status === STATUS.QA || status === STATUS.READY_TO_MERGE) {
|
|
1659
|
-
targetRole = 'qa';
|
|
1660
|
-
targetSessionId = featureSession.qa_session_id;
|
|
1661
|
-
} else if (status === STATUS.BLOCKED) {
|
|
1662
|
-
const fastGate = readStringField(gates, 'fast');
|
|
1663
|
-
const fullGate = readStringField(gates, 'full');
|
|
1664
|
-
if (fastGate === GATE_RESULT.FAIL && fullGate !== GATE_RESULT.FAIL) {
|
|
1665
|
-
targetRole = 'builder';
|
|
1666
|
-
targetSessionId = featureSession.builder_session_id;
|
|
1667
|
-
} else {
|
|
1668
|
-
targetRole = 'qa';
|
|
1669
|
-
targetSessionId = featureSession.qa_session_id;
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
if (!targetSessionId || targetSessionId === 'unassigned' || targetSessionId === 'unknown') {
|
|
1674
|
-
targetRole = 'orchestrator';
|
|
1675
|
-
targetSessionId = runtimeSessions.orchestrator_session_id;
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
if (!this.provider?.sendMessage) {
|
|
1679
|
-
throw {
|
|
1680
|
-
normalizedResponse: {
|
|
1681
|
-
ok: false,
|
|
1682
|
-
error: { code: 'provider_unsupported', message: 'Provider does not support sendMessage' },
|
|
1683
|
-
},
|
|
1684
|
-
};
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
await this.waitForSessionToBecomeActive(targetSessionId);
|
|
1688
|
-
await this.provider.sendMessage(targetSessionId, message);
|
|
1689
|
-
return {
|
|
1690
|
-
feature_id: featureId,
|
|
1691
|
-
session_id: targetSessionId,
|
|
1692
|
-
target_role: targetRole,
|
|
1693
|
-
status,
|
|
1694
|
-
delivered: true,
|
|
1695
|
-
};
|
|
899
|
+
return this.sendMessageService.featureSendMessage(featureId, message);
|
|
1696
900
|
}
|
|
1697
901
|
|
|
1698
902
|
async costRecord(featureId: string, tokensDelta: number, costUsdDelta: number) {
|
|
@@ -1745,24 +949,3 @@ export class AopKernel {
|
|
|
1745
949
|
return { ok: true as const, data: snapshot };
|
|
1746
950
|
}
|
|
1747
951
|
}
|
|
1748
|
-
|
|
1749
|
-
function normalizeRepoPathForState(repoRoot: string, absolutePath: string) {
|
|
1750
|
-
const relative = path.relative(repoRoot, absolutePath).replaceAll('\\\\', '/');
|
|
1751
|
-
if (!relative || relative === '.') {
|
|
1752
|
-
return '.';
|
|
1753
|
-
}
|
|
1754
|
-
return relative;
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
function normalizeFromWorktree(
|
|
1758
|
-
worktreePath: string,
|
|
1759
|
-
repoRoot: string,
|
|
1760
|
-
repoRelativeFromWorktree: string,
|
|
1761
|
-
) {
|
|
1762
|
-
const absolute = path.resolve(repoRoot, repoRelativeFromWorktree);
|
|
1763
|
-
const maybeRelativeToWorktree = path.relative(worktreePath, absolute).replaceAll('\\\\', '/');
|
|
1764
|
-
if (!maybeRelativeToWorktree.startsWith('../')) {
|
|
1765
|
-
return maybeRelativeToWorktree;
|
|
1766
|
-
}
|
|
1767
|
-
return path.relative(repoRoot, absolute).replaceAll('\\\\', '/');
|
|
1768
|
-
}
|