@moretea-labs/forge 1.5.1 → 1.6.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 +20 -0
- package/DESIGN.md +7 -2
- package/README.md +4 -4
- package/README.zh-CN.md +2 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/assets/hooks/pre-edit-guard.sh +8 -39
- package/assets/hooks/prompt-guard.sh +2 -2
- package/assets/plugin-registry.v1.json +54 -3
- package/assets/reference-configs/hook-operations.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +2 -2
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/ensure-task-workflow.sh +2 -2
- package/assets/templates/helpers/sprint-backlog.sh +6 -6
- package/docs/architecture/current/automation-and-schedule-engine.md +9 -3
- package/docs/architecture/current/codegraph-context-provider.md +15 -3
- package/docs/architecture/current/dispatch-and-agent-strategy.md +32 -13
- package/docs/architecture/current/entity-model.md +20 -0
- package/docs/architecture/current/external-plugin-governance.md +3 -3
- package/docs/architecture/current/system-overview.md +26 -1
- package/docs/architecture/current/thin-harness-v1.md +10 -0
- package/docs/operations/chatgpt-personal-assistant-runtime.md +12 -2
- package/docs/operations/controller-browser-plugin.md +12 -16
- package/docs/operations/features.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/tutorials/01-install-and-start.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/wiki/Installation.md +1 -1
- package/docs/wiki/Quick-Start.md +1 -1
- package/package.json +1 -1
- package/scripts/check-public-docs.sh +14 -1
- package/scripts/migrate-chatgpt-automation-schedules.ts +64 -0
- package/scripts/sprint-backlog.sh +1 -1
- package/src/cli/commands/chatgpt.ts +31 -2
- package/src/cli/commands/plugin.ts +36 -9
- package/src/cli/controller/context-pack.ts +158 -6
- package/src/cli/hook/prompt-guard-decision.ts +17 -18
- package/src/cli/hook/prompt-intents.ts +1 -1
- package/src/cli/local-bridge/console-automations.ts +205 -23
- package/src/cli/local-bridge/console-view-models.ts +2 -0
- package/src/cli/local-bridge/facade-api.ts +2 -0
- package/src/cli/local-bridge/frontend/src/App.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +143 -3
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +181 -3
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -6
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -1
- package/src/cli/local-bridge/frontend/src/styles.css +27 -11
- package/src/cli/local-bridge/frontend/src/types.ts +6 -2
- package/src/cli/local-bridge/server.ts +29 -19
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +5003 -4815
- package/src/cli/mcp/paths.ts +8 -2
- package/src/cli/mcp/repository-tools.ts +124 -9
- package/src/cli/mcp/server.ts +164 -19
- package/src/cli/mcp/transports/http.ts +25 -2
- package/src/cli/mcp/transports/session-registry.ts +24 -0
- package/src/cli/repositories/command-classifier.ts +113 -0
- package/src/cli/repositories/safe-patch.ts +6 -4
- package/src/cli/repositories/structured-git.ts +49 -7
- package/src/cli/repository/inspector.ts +16 -3
- package/src/runtime/assistant/intent.ts +3 -3
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +157 -2
- package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
- package/src/runtime/control-plane/execution/validation.ts +15 -2
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +32 -0
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +6 -7
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +2 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +1 -1
- package/src/runtime/control-plane/facade/controller-session-store.ts +12 -13
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +7 -2
- package/src/runtime/control-plane/facade/goal-workloop.ts +231 -8
- package/src/runtime/control-plane/facade/plan-contract-store.ts +32 -12
- package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
- package/src/runtime/control-plane/facade/types.ts +33 -6
- package/src/runtime/control-plane/facade/work-contract-store.ts +31 -2
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +375 -23
- package/src/runtime/control-plane/persistence/requirement-store.ts +15 -11
- package/src/runtime/diagnostics/mcp-timing.ts +6 -0
- package/src/runtime/execution/process-runtime/check-facade.ts +1 -10
- package/src/runtime/execution/process-runtime/check-result.ts +10 -2
- package/src/runtime/execution/process-runtime/command-facade.ts +1 -0
- package/src/runtime/execution/process-runtime/gc.ts +64 -10
- package/src/runtime/execution/process-runtime/persisted-check.ts +10 -2
- package/src/runtime/execution/process-runtime/resource-claims.ts +87 -2
- package/src/runtime/execution/process-runtime/runtime.ts +16 -1
- package/src/runtime/execution/process-runtime/store.ts +6 -0
- package/src/runtime/gateway/mcp/execution-tools.ts +27 -12
- package/src/runtime/gateway/mcp/router.ts +1 -1
- package/src/runtime/gateway/mcp/runtime-tools.ts +482 -178
- package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
- package/src/runtime/plugins/browser-adapter.ts +19 -72
- package/src/runtime/plugins/browser-handoff-host.ts +6 -31
- package/src/runtime/plugins/browser-handoff.ts +0 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +8 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +8 -11
- package/src/runtime/plugins/external-registration.ts +84 -5
- package/src/runtime/plugins/first-party-registry.ts +2 -2
- package/src/runtime/plugins/gmail-adapter.ts +28 -2
- package/src/runtime/plugins/local-system-adapter.ts +2 -2
- package/src/runtime/plugins/plugin-management-adapter.ts +140 -0
- package/src/runtime/plugins/store.ts +107 -5
- package/src/runtime/plugins/types.ts +4 -0
- package/src/runtime/recovery/actions.ts +0 -10
- package/src/runtime/recovery/classifier.ts +0 -9
- package/src/runtime/recovery/maintenance-executor.ts +43 -11
- package/src/runtime/recovery/probe.ts +0 -1
- package/src/runtime/recovery/types.ts +0 -1
- package/src/runtime/root/release-materialize.ts +143 -19
- package/src/runtime/root/service.ts +6 -4
- package/src/runtime/safe-tooling/index.ts +0 -1
- package/src/runtime/safe-tooling/types.ts +0 -30
- package/src/runtime/standalone-recovery/core.ts +2 -2
- package/src/runtime/workflow/schedules/browser-probe.ts +180 -0
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
- package/src/runtime/workflow/schedules/engine.ts +357 -64
- package/src/runtime/workflow/schedules/types.ts +5 -0
- package/src/runtime/workflow/schedules/work-continuation.ts +100 -27
- package/bin/forge-desktop-helper.mjs +0 -204
- package/docs/operations/controller-desktop-plugin.md +0 -112
- package/src/runtime/plugins/desktop-adapter.ts +0 -374
- package/src/runtime/safe-tooling/browser-safe.ts +0 -133
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomUUID } from 'crypto';
|
|
1
|
+
import { createHash, randomUUID } from 'crypto';
|
|
2
2
|
import { executeBrowserPluginAction, readBrowserPluginConfiguration } from '../../plugins/browser-adapter';
|
|
3
3
|
import { controllerPluginRepository } from '../../plugins/store';
|
|
4
4
|
import { getWorkContract } from '../facade/work-contract-store';
|
|
@@ -8,10 +8,19 @@ import {
|
|
|
8
8
|
type ChatgptWorkConversationBinding,
|
|
9
9
|
} from './chatgpt-work-binding-store';
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
11
|
+
const LEGACY_CONTROLLER_CHATGPT_SESSION_ID = 'forge-chatgpt-supercontroller';
|
|
12
|
+
export const DEFAULT_CHATGPT_AUTOMATION_MODEL = 'gpt-5.6';
|
|
13
|
+
export const DEFAULT_CHATGPT_AUTOMATION_REASONING = 'high';
|
|
14
|
+
export const DEFAULT_CHATGPT_AUTOMATION_TAB_POLICY = 'auto';
|
|
15
|
+
export const DEFAULT_CHATGPT_AUTOMATION_PLUGIN_MENTION = '@forge';
|
|
16
|
+
const CHATGPT_PROMPT_SELECTOR = 'div#prompt-textarea[contenteditable="true"]';
|
|
17
|
+
const CHATGPT_SEND_SELECTOR = '[data-testid="send-button"]';
|
|
18
|
+
const CHATGPT_INTELLIGENCE_CONTROL_SELECTOR = 'main button';
|
|
19
|
+
const CHATGPT_CAPABILITY_SLIDER_SELECTOR = '[role="slider"]';
|
|
20
|
+
const CHATGPT_CAPABILITY_MENUITEM_SELECTOR = '[role="menuitem"][aria-keyshortcuts~="ArrowLeft"][aria-keyshortcuts~="ArrowRight"]';
|
|
21
|
+
|
|
22
|
+
export type ChatgptAutomationReasoning = 'medium' | 'high' | 'xhigh';
|
|
23
|
+
export type ChatgptAutomationTabPolicy = 'auto' | 'reuse' | 'new';
|
|
15
24
|
|
|
16
25
|
export interface WorkChatgptContinuationInput {
|
|
17
26
|
controllerHome: string;
|
|
@@ -22,9 +31,39 @@ export interface WorkChatgptContinuationInput {
|
|
|
22
31
|
title?: string;
|
|
23
32
|
browserSessionId?: string;
|
|
24
33
|
conversationUrl?: string;
|
|
34
|
+
model?: string;
|
|
35
|
+
reasoning?: ChatgptAutomationReasoning;
|
|
36
|
+
tabPolicy?: ChatgptAutomationTabPolicy;
|
|
25
37
|
timeoutMs?: number;
|
|
26
38
|
}
|
|
27
39
|
|
|
40
|
+
export interface ScheduledChatgptPromptInput {
|
|
41
|
+
controllerHome: string;
|
|
42
|
+
repoId: string;
|
|
43
|
+
automationId: string;
|
|
44
|
+
prompt: string;
|
|
45
|
+
title?: string;
|
|
46
|
+
browserSessionId?: string;
|
|
47
|
+
conversationUrl?: string;
|
|
48
|
+
model?: string;
|
|
49
|
+
reasoning?: ChatgptAutomationReasoning;
|
|
50
|
+
tabPolicy?: ChatgptAutomationTabPolicy;
|
|
51
|
+
timeoutMs?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ScheduledChatgptPromptResult {
|
|
55
|
+
status: 'dispatched' | 'failed';
|
|
56
|
+
provider: 'controller-browser';
|
|
57
|
+
browserSessionId: string;
|
|
58
|
+
conversationUrl?: string;
|
|
59
|
+
model: string;
|
|
60
|
+
reasoning: ChatgptAutomationReasoning;
|
|
61
|
+
tabPolicy: ChatgptAutomationTabPolicy;
|
|
62
|
+
executionPreferenceVerified: boolean;
|
|
63
|
+
reusedSession: boolean;
|
|
64
|
+
error?: { code: string; message: string };
|
|
65
|
+
}
|
|
66
|
+
|
|
28
67
|
export interface WorkChatgptContinuationResult {
|
|
29
68
|
status: 'dispatched' | 'failed';
|
|
30
69
|
provider: 'controller-browser';
|
|
@@ -33,9 +72,15 @@ export interface WorkChatgptContinuationResult {
|
|
|
33
72
|
conversationId?: string;
|
|
34
73
|
localAlias?: string;
|
|
35
74
|
resumedFromBinding: boolean;
|
|
75
|
+
model: string;
|
|
76
|
+
reasoning: ChatgptAutomationReasoning;
|
|
77
|
+
tabPolicy: ChatgptAutomationTabPolicy;
|
|
78
|
+
executionPreferenceVerified: boolean;
|
|
36
79
|
error?: { code: string; message: string };
|
|
37
80
|
}
|
|
38
81
|
|
|
82
|
+
const workflowToolAttributionInstruction = (workId: string): string => `Forge Workflow execution contract: first claim exact Work ${workId}. For every repository_command_execute and repository_safe_patch_apply call in this turn, pass work_id=${workId} explicitly. MCP transport sessions may change between tool calls; never omit this Work id.`;
|
|
83
|
+
|
|
39
84
|
function requestId(workId: string, actionId: string): string {
|
|
40
85
|
return `chatgpt-work:${workId}:${actionId}:${randomUUID()}`;
|
|
41
86
|
}
|
|
@@ -68,7 +113,6 @@ async function ensureControllerChatgptBrowser(controllerHome: string, workId: st
|
|
|
68
113
|
const repoRoot = repository.canonicalRoot ?? repository.localRoot;
|
|
69
114
|
if (!repoRoot) throw new Error('CHATGPT_CONTROLLER_BROWSER_ROOT_UNAVAILABLE');
|
|
70
115
|
const existing = readBrowserPluginConfiguration(repoRoot);
|
|
71
|
-
const allowedDomains = [...new Set([...existing.allowedDomains, ...CHATGPT_ALLOWED_DOMAINS])];
|
|
72
116
|
await controllerBrowserAction(controllerHome, workId, 'configure', {
|
|
73
117
|
enabled: true,
|
|
74
118
|
...(!existing.enabled ? {
|
|
@@ -79,7 +123,6 @@ async function ensureControllerChatgptBrowser(controllerHome: string, workId: st
|
|
|
79
123
|
native_attach_mode: 'auto',
|
|
80
124
|
native_browser_candidates: ['chrome'],
|
|
81
125
|
} : {}),
|
|
82
|
-
allowed_domains: allowedDomains,
|
|
83
126
|
});
|
|
84
127
|
}
|
|
85
128
|
|
|
@@ -87,11 +130,309 @@ function stringField(value: unknown): string | undefined {
|
|
|
87
130
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
88
131
|
}
|
|
89
132
|
|
|
133
|
+
function normalizeModel(value?: string): string {
|
|
134
|
+
const model = value?.trim().toLowerCase() || DEFAULT_CHATGPT_AUTOMATION_MODEL;
|
|
135
|
+
if (model === 'gpt-5.6' || model === 'gpt-5.6-sol' || model === '5.6' || model === '5.6s') return DEFAULT_CHATGPT_AUTOMATION_MODEL;
|
|
136
|
+
throw new Error(`CHATGPT_AUTOMATION_MODEL_UNSUPPORTED:${value}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function normalizeReasoning(value?: ChatgptAutomationReasoning): ChatgptAutomationReasoning {
|
|
140
|
+
return value ?? DEFAULT_CHATGPT_AUTOMATION_REASONING;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizeTabPolicy(value?: ChatgptAutomationTabPolicy): ChatgptAutomationTabPolicy {
|
|
144
|
+
return value ?? DEFAULT_CHATGPT_AUTOMATION_TAB_POLICY;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function stableChatgptWorkBrowserSessionId(repoId: string, workId: string): string {
|
|
148
|
+
const digest = createHash('sha256').update(`${repoId}\n${workId}`).digest('hex').slice(0, 20);
|
|
149
|
+
return `forge-chatgpt-work-${digest}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function stableChatgptAutomationBrowserSessionId(repoId: string, automationId: string): string {
|
|
153
|
+
const digest = createHash('sha256').update(`${repoId}\nautomation\n${automationId}`).digest('hex').slice(0, 20);
|
|
154
|
+
return `forge-chatgpt-automation-${digest}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function resolveChatgptWorkBrowserSessionId(input: {
|
|
158
|
+
repoId: string;
|
|
159
|
+
workId: string;
|
|
160
|
+
tabPolicy?: ChatgptAutomationTabPolicy;
|
|
161
|
+
explicitSessionId?: string;
|
|
162
|
+
boundSessionId?: string;
|
|
163
|
+
}): string {
|
|
164
|
+
const policy = normalizeTabPolicy(input.tabPolicy);
|
|
165
|
+
const stable = stableChatgptWorkBrowserSessionId(input.repoId, input.workId);
|
|
166
|
+
if (policy === 'new') return `${stable}-${randomUUID().slice(0, 8)}`;
|
|
167
|
+
const explicit = input.explicitSessionId?.trim();
|
|
168
|
+
if (explicit && explicit !== LEGACY_CONTROLLER_CHATGPT_SESSION_ID) return explicit;
|
|
169
|
+
const bound = input.boundSessionId?.trim();
|
|
170
|
+
if (bound && bound !== LEGACY_CONTROLLER_CHATGPT_SESSION_ID) return bound;
|
|
171
|
+
return stable;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface BrowserQueryMatch {
|
|
175
|
+
text?: string;
|
|
176
|
+
selectorHint?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function queryMatches(result: Record<string, unknown> | undefined): BrowserQueryMatch[] {
|
|
180
|
+
if (!result || !Array.isArray(result.matches)) return [];
|
|
181
|
+
return result.matches.filter((value): value is BrowserQueryMatch => Boolean(value) && typeof value === 'object');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function matchText(value: BrowserQueryMatch): string {
|
|
185
|
+
return typeof value.text === 'string' ? value.text.trim() : '';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function matchSelector(value: BrowserQueryMatch | undefined): string | undefined {
|
|
189
|
+
return typeof value?.selectorHint === 'string' && value.selectorHint.trim() ? value.selectorHint.trim() : undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function modelLabelMatches(label: string | undefined, model: string): boolean {
|
|
193
|
+
if (model !== DEFAULT_CHATGPT_AUTOMATION_MODEL || !label) return false;
|
|
194
|
+
const normalized = label.toLowerCase().replace(/\s+/g, '');
|
|
195
|
+
return normalized.includes('5.6sol') || normalized.includes('gpt-5.6sol');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function reasoningSliderValue(reasoning: ChatgptAutomationReasoning): number {
|
|
199
|
+
if (reasoning === 'medium') return 2;
|
|
200
|
+
if (reasoning === 'high') return 3;
|
|
201
|
+
return 4;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function findChatgptIntelligenceControl(
|
|
205
|
+
controllerHome: string,
|
|
206
|
+
workId: string,
|
|
207
|
+
browserSessionId: string,
|
|
208
|
+
timeoutMs?: number,
|
|
209
|
+
): Promise<BrowserQueryMatch | undefined> {
|
|
210
|
+
const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
|
|
211
|
+
session_id: browserSessionId,
|
|
212
|
+
selector: CHATGPT_INTELLIGENCE_CONTROL_SELECTOR,
|
|
213
|
+
limit: 50,
|
|
214
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
215
|
+
}, timeoutMs);
|
|
216
|
+
return queryMatches(result).find((match) => /(?:gpt-)?5\.6\s*sol/i.test(matchText(match)));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function waitForChatgptIntelligenceControl(
|
|
220
|
+
controllerHome: string,
|
|
221
|
+
workId: string,
|
|
222
|
+
browserSessionId: string,
|
|
223
|
+
timeoutMs?: number,
|
|
224
|
+
): Promise<BrowserQueryMatch | undefined> {
|
|
225
|
+
const waitBudgetMs = Math.min(Math.max(timeoutMs ?? 12_000, 1_000), 12_000);
|
|
226
|
+
const deadline = Date.now() + waitBudgetMs;
|
|
227
|
+
do {
|
|
228
|
+
const control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, Math.min(waitBudgetMs, 5_000));
|
|
229
|
+
if (control) return control;
|
|
230
|
+
if (Date.now() >= deadline) break;
|
|
231
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 500));
|
|
232
|
+
} while (Date.now() < deadline);
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function ensureChatgptExecutionPreference(
|
|
237
|
+
controllerHome: string,
|
|
238
|
+
workId: string,
|
|
239
|
+
browserSessionId: string,
|
|
240
|
+
model: string,
|
|
241
|
+
reasoning: ChatgptAutomationReasoning,
|
|
242
|
+
timeoutMs?: number,
|
|
243
|
+
): Promise<boolean> {
|
|
244
|
+
if (model !== DEFAULT_CHATGPT_AUTOMATION_MODEL) throw new Error(`CHATGPT_AUTOMATION_MODEL_UNSUPPORTED:${model}`);
|
|
245
|
+
let control = await waitForChatgptIntelligenceControl(controllerHome, workId, browserSessionId, timeoutMs);
|
|
246
|
+
if (!control) throw new Error('CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE');
|
|
247
|
+
if (!modelLabelMatches(matchText(control), model)) throw new Error(`CHATGPT_AUTOMATION_MODEL_NOT_VERIFIED:${model}`);
|
|
248
|
+
let controlSelector = matchSelector(control);
|
|
249
|
+
if (!controlSelector) throw new Error('CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE');
|
|
250
|
+
|
|
251
|
+
const expanded = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
|
|
252
|
+
session_id: browserSessionId,
|
|
253
|
+
selector: controlSelector,
|
|
254
|
+
attribute: 'aria-expanded',
|
|
255
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
256
|
+
}, timeoutMs).catch(() => undefined);
|
|
257
|
+
if (stringField(expanded?.value) !== 'true') {
|
|
258
|
+
await controllerBrowserAction(controllerHome, workId, 'press', {
|
|
259
|
+
session_id: browserSessionId,
|
|
260
|
+
selector: controlSelector,
|
|
261
|
+
key: 'ArrowDown',
|
|
262
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
263
|
+
post_action_wait_ms: 150,
|
|
264
|
+
}, timeoutMs);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const currentValueResult = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
|
|
268
|
+
session_id: browserSessionId,
|
|
269
|
+
selector: CHATGPT_CAPABILITY_SLIDER_SELECTOR,
|
|
270
|
+
attribute: 'aria-valuenow',
|
|
271
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
272
|
+
}, timeoutMs).catch(() => undefined);
|
|
273
|
+
const currentValue = Number(stringField(currentValueResult?.value));
|
|
274
|
+
const targetValue = reasoningSliderValue(reasoning);
|
|
275
|
+
if (!Number.isInteger(currentValue) || currentValue < 0 || currentValue > 4) {
|
|
276
|
+
throw new Error(`CHATGPT_AUTOMATION_REASONING_STATE_UNAVAILABLE:${reasoning}`);
|
|
277
|
+
}
|
|
278
|
+
const direction = targetValue > currentValue ? 'ArrowRight' : 'ArrowLeft';
|
|
279
|
+
for (let index = 0; index < Math.abs(targetValue - currentValue); index += 1) {
|
|
280
|
+
await controllerBrowserAction(controllerHome, workId, 'press', {
|
|
281
|
+
session_id: browserSessionId,
|
|
282
|
+
selector: CHATGPT_CAPABILITY_MENUITEM_SELECTOR,
|
|
283
|
+
key: direction,
|
|
284
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
285
|
+
post_action_wait_ms: 120,
|
|
286
|
+
}, timeoutMs);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const verifiedValueResult = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
|
|
290
|
+
session_id: browserSessionId,
|
|
291
|
+
selector: CHATGPT_CAPABILITY_SLIDER_SELECTOR,
|
|
292
|
+
attribute: 'aria-valuenow',
|
|
293
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
294
|
+
}, timeoutMs).catch(() => undefined);
|
|
295
|
+
if (Number(stringField(verifiedValueResult?.value)) !== targetValue) {
|
|
296
|
+
throw new Error(`CHATGPT_AUTOMATION_REASONING_NOT_VERIFIED:${reasoning}`);
|
|
297
|
+
}
|
|
298
|
+
control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, timeoutMs) ?? control;
|
|
299
|
+
if (!modelLabelMatches(matchText(control), model)) throw new Error(`CHATGPT_AUTOMATION_MODEL_NOT_VERIFIED:${model}`);
|
|
300
|
+
controlSelector = matchSelector(control) ?? controlSelector;
|
|
301
|
+
await controllerBrowserAction(controllerHome, workId, 'press', {
|
|
302
|
+
session_id: browserSessionId,
|
|
303
|
+
selector: controlSelector,
|
|
304
|
+
key: 'Escape',
|
|
305
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
306
|
+
post_action_wait_ms: 100,
|
|
307
|
+
}, timeoutMs).catch(() => undefined);
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
|
|
90
311
|
function resultUrl(result: Record<string, unknown>): string | undefined {
|
|
91
312
|
return stringField(result.url)
|
|
92
313
|
?? stringField((result.session as Record<string, unknown> | undefined)?.url);
|
|
93
314
|
}
|
|
94
315
|
|
|
316
|
+
async function submitChatgptPrompt(
|
|
317
|
+
controllerHome: string,
|
|
318
|
+
workId: string,
|
|
319
|
+
browserSessionId: string,
|
|
320
|
+
prompt: string,
|
|
321
|
+
targetUrl: string,
|
|
322
|
+
timeoutMs?: number,
|
|
323
|
+
): Promise<string> {
|
|
324
|
+
await controllerBrowserAction(controllerHome, workId, 'fill', {
|
|
325
|
+
session_id: browserSessionId,
|
|
326
|
+
selector: CHATGPT_PROMPT_SELECTOR,
|
|
327
|
+
text: withForgePluginMention(prompt),
|
|
328
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
329
|
+
post_action_wait_ms: 100,
|
|
330
|
+
}, timeoutMs);
|
|
331
|
+
const sent = await controllerBrowserAction(controllerHome, workId, 'click', {
|
|
332
|
+
session_id: browserSessionId,
|
|
333
|
+
selector: CHATGPT_SEND_SELECTOR,
|
|
334
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
335
|
+
post_action_wait_ms: 1_000,
|
|
336
|
+
}, timeoutMs);
|
|
337
|
+
let observedUrl = resultUrl(sent) ?? targetUrl;
|
|
338
|
+
if (/\/c\/[^/?#]+/.test(observedUrl)) return observedUrl;
|
|
339
|
+
const deadline = Date.now() + Math.min(Math.max(timeoutMs ?? 8_000, 2_000), 8_000);
|
|
340
|
+
do {
|
|
341
|
+
const observed = await controllerBrowserAction(controllerHome, workId, 'query_all', {
|
|
342
|
+
session_id: browserSessionId,
|
|
343
|
+
selector: 'main',
|
|
344
|
+
limit: 1,
|
|
345
|
+
timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
|
|
346
|
+
}, timeoutMs).catch(() => undefined);
|
|
347
|
+
observedUrl = observed ? resultUrl(observed) ?? observedUrl : observedUrl;
|
|
348
|
+
if (/\/c\/[^/?#]+/.test(observedUrl)) return observedUrl;
|
|
349
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 250));
|
|
350
|
+
} while (Date.now() < deadline);
|
|
351
|
+
throw new Error(`CHATGPT_AUTOMATION_SUBMISSION_NOT_CONFIRMED:${observedUrl}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function withForgePluginMention(prompt: string): string {
|
|
355
|
+
const value = prompt.trim();
|
|
356
|
+
if (!value) throw new Error('CHATGPT_AUTOMATION_PROMPT_REQUIRED');
|
|
357
|
+
if (/^@forge(?:\s|$)/i.test(value)) return value;
|
|
358
|
+
return `${DEFAULT_CHATGPT_AUTOMATION_PLUGIN_MENTION} ${value}`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export async function runScheduledChatgptPrompt(input: ScheduledChatgptPromptInput): Promise<ScheduledChatgptPromptResult> {
|
|
362
|
+
const model = normalizeModel(input.model);
|
|
363
|
+
const reasoning = normalizeReasoning(input.reasoning);
|
|
364
|
+
const tabPolicy = normalizeTabPolicy(input.tabPolicy);
|
|
365
|
+
const stableSessionId = stableChatgptAutomationBrowserSessionId(input.repoId, input.automationId);
|
|
366
|
+
const explicitSessionId = input.browserSessionId?.trim();
|
|
367
|
+
const sessionId = tabPolicy === 'new'
|
|
368
|
+
? `${stableSessionId}-${randomUUID().slice(0, 8)}`
|
|
369
|
+
: explicitSessionId || stableSessionId;
|
|
370
|
+
const targetUrl = tabPolicy === 'new'
|
|
371
|
+
? 'https://chatgpt.com/'
|
|
372
|
+
: input.conversationUrl?.trim() || 'https://chatgpt.com/';
|
|
373
|
+
let reusedSession = false;
|
|
374
|
+
try {
|
|
375
|
+
await ensureControllerChatgptBrowser(input.controllerHome, input.automationId);
|
|
376
|
+
if (tabPolicy !== 'new') {
|
|
377
|
+
try {
|
|
378
|
+
await controllerBrowserAction(input.controllerHome, input.automationId, 'get_text', {
|
|
379
|
+
session_id: sessionId,
|
|
380
|
+
selector: 'body',
|
|
381
|
+
max_chars: 1,
|
|
382
|
+
timeout_ms: Math.min(input.timeoutMs ?? 60_000, 3_000),
|
|
383
|
+
}, input.timeoutMs);
|
|
384
|
+
reusedSession = true;
|
|
385
|
+
} catch {
|
|
386
|
+
reusedSession = false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (!reusedSession) {
|
|
390
|
+
await controllerBrowserAction(input.controllerHome, input.automationId, 'open_page', {
|
|
391
|
+
session_id: sessionId,
|
|
392
|
+
url: targetUrl,
|
|
393
|
+
wait_until: 'domcontentloaded',
|
|
394
|
+
timeout_ms: input.timeoutMs ?? 60_000,
|
|
395
|
+
retries: 1,
|
|
396
|
+
}, input.timeoutMs);
|
|
397
|
+
}
|
|
398
|
+
const executionPreferenceVerified = await ensureChatgptExecutionPreference(
|
|
399
|
+
input.controllerHome,
|
|
400
|
+
input.automationId,
|
|
401
|
+
sessionId,
|
|
402
|
+
model,
|
|
403
|
+
reasoning,
|
|
404
|
+
input.timeoutMs,
|
|
405
|
+
);
|
|
406
|
+
const observedUrl = await submitChatgptPrompt(input.controllerHome, input.automationId, sessionId, input.prompt, targetUrl, input.timeoutMs);
|
|
407
|
+
return {
|
|
408
|
+
status: 'dispatched',
|
|
409
|
+
provider: 'controller-browser',
|
|
410
|
+
browserSessionId: sessionId,
|
|
411
|
+
conversationUrl: observedUrl,
|
|
412
|
+
model,
|
|
413
|
+
reasoning,
|
|
414
|
+
tabPolicy,
|
|
415
|
+
executionPreferenceVerified,
|
|
416
|
+
reusedSession,
|
|
417
|
+
};
|
|
418
|
+
} catch (error) {
|
|
419
|
+
return {
|
|
420
|
+
status: 'failed',
|
|
421
|
+
provider: 'controller-browser',
|
|
422
|
+
browserSessionId: sessionId,
|
|
423
|
+
model,
|
|
424
|
+
reasoning,
|
|
425
|
+
tabPolicy,
|
|
426
|
+
executionPreferenceVerified: false,
|
|
427
|
+
reusedSession,
|
|
428
|
+
error: {
|
|
429
|
+
code: error instanceof Error && error.message.includes(':') ? error.message.split(':', 1)[0] : 'CHATGPT_SCHEDULED_PROMPT_FAILED',
|
|
430
|
+
message: error instanceof Error ? error.message : String(error),
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
95
436
|
/**
|
|
96
437
|
* Dispatches a bounded resume prompt to one controller-owned ChatGPT Web tab.
|
|
97
438
|
* Chat history is transport context only. Forge Work/Plan/evidence remain authoritative.
|
|
@@ -100,7 +441,16 @@ export async function runWorkChatgptContinuation(input: WorkChatgptContinuationI
|
|
|
100
441
|
const store = { controllerHome: input.controllerHome, repoId: input.repoId };
|
|
101
442
|
const existing = getChatgptWorkConversationBinding(store, input.workId);
|
|
102
443
|
const seedUrl = input.conversationUrl?.trim() || existing?.conversationUrl;
|
|
103
|
-
const
|
|
444
|
+
const model = normalizeModel(input.model);
|
|
445
|
+
const reasoning = normalizeReasoning(input.reasoning);
|
|
446
|
+
const tabPolicy = normalizeTabPolicy(input.tabPolicy);
|
|
447
|
+
const sessionId = resolveChatgptWorkBrowserSessionId({
|
|
448
|
+
repoId: input.repoId,
|
|
449
|
+
workId: input.workId,
|
|
450
|
+
tabPolicy,
|
|
451
|
+
explicitSessionId: input.browserSessionId,
|
|
452
|
+
boundSessionId: existing?.latestBrowserSessionId,
|
|
453
|
+
});
|
|
104
454
|
let binding: ChatgptWorkConversationBinding | undefined = existing;
|
|
105
455
|
|
|
106
456
|
try {
|
|
@@ -125,21 +475,15 @@ export async function runWorkChatgptContinuation(input: WorkChatgptContinuationI
|
|
|
125
475
|
timeout_ms: input.timeoutMs ?? 60_000,
|
|
126
476
|
retries: 1,
|
|
127
477
|
}, input.timeoutMs);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
selector: CHATGPT_PROMPT_SELECTOR,
|
|
138
|
-
key: CHATGPT_SEND_KEY,
|
|
139
|
-
timeout_ms: input.timeoutMs ?? 60_000,
|
|
140
|
-
post_action_wait_ms: 1_500,
|
|
141
|
-
}, input.timeoutMs);
|
|
142
|
-
const observedUrl = resultUrl(sent) ?? targetUrl;
|
|
478
|
+
const executionPreferenceVerified = await ensureChatgptExecutionPreference(
|
|
479
|
+
input.controllerHome,
|
|
480
|
+
input.workId,
|
|
481
|
+
sessionId,
|
|
482
|
+
model,
|
|
483
|
+
reasoning,
|
|
484
|
+
input.timeoutMs,
|
|
485
|
+
);
|
|
486
|
+
const observedUrl = await submitChatgptPrompt(input.controllerHome, input.workId, sessionId, `${workflowToolAttributionInstruction(input.workId)}\n\n${input.prompt}`, targetUrl, input.timeoutMs);
|
|
143
487
|
if (/\/c\/[^/?#]+/.test(observedUrl)) {
|
|
144
488
|
binding = bindChatgptWorkConversation(store, {
|
|
145
489
|
workId: input.workId,
|
|
@@ -156,6 +500,10 @@ export async function runWorkChatgptContinuation(input: WorkChatgptContinuationI
|
|
|
156
500
|
conversationId: binding?.conversationId,
|
|
157
501
|
localAlias: binding?.localAlias,
|
|
158
502
|
resumedFromBinding: Boolean(existing),
|
|
503
|
+
model,
|
|
504
|
+
reasoning,
|
|
505
|
+
tabPolicy,
|
|
506
|
+
executionPreferenceVerified,
|
|
159
507
|
};
|
|
160
508
|
} catch (error) {
|
|
161
509
|
return {
|
|
@@ -166,6 +514,10 @@ export async function runWorkChatgptContinuation(input: WorkChatgptContinuationI
|
|
|
166
514
|
conversationId: binding?.conversationId,
|
|
167
515
|
localAlias: binding?.localAlias,
|
|
168
516
|
resumedFromBinding: Boolean(existing),
|
|
517
|
+
model,
|
|
518
|
+
reasoning,
|
|
519
|
+
tabPolicy,
|
|
520
|
+
executionPreferenceVerified: false,
|
|
169
521
|
error: {
|
|
170
522
|
code: error instanceof Error && error.message.includes(':') ? error.message.split(':', 1)[0] : 'CHATGPT_CONTROLLER_BROWSER_FAILED',
|
|
171
523
|
message: error instanceof Error ? error.message : String(error),
|
|
@@ -20,6 +20,7 @@ export interface Requirement {
|
|
|
20
20
|
outcomeStatement: string;
|
|
21
21
|
acceptanceCriteria: string[];
|
|
22
22
|
requiredDeliveryReferences: string[];
|
|
23
|
+
/** @deprecated Migration compatibility pointer only. Current Plan.requirementId is the relationship authority. */
|
|
23
24
|
activePlanId?: string;
|
|
24
25
|
state: RequirementState;
|
|
25
26
|
needsAttention: boolean;
|
|
@@ -124,7 +125,7 @@ export function createRequirement(
|
|
|
124
125
|
const ALLOWED_TRANSITIONS: Readonly<Record<RequirementState, readonly RequirementState[]>> = {
|
|
125
126
|
planned: ['planned', 'active', 'waiting_for_user', 'cancelled'],
|
|
126
127
|
active: ['active', 'waiting_for_user', 'done', 'cancelled'],
|
|
127
|
-
waiting_for_user: ['waiting_for_user', 'planned', 'active', 'cancelled'],
|
|
128
|
+
waiting_for_user: ['waiting_for_user', 'planned', 'active', 'done', 'cancelled'],
|
|
128
129
|
done: ['done', 'cancelled'],
|
|
129
130
|
cancelled: ['cancelled'],
|
|
130
131
|
};
|
|
@@ -164,6 +165,7 @@ export function updateRequirement(
|
|
|
164
165
|
});
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
/** @deprecated Migration/legacy compatibility only. New Plan creation must bind Plan.requirementId and derive active Plan slices by query. */
|
|
167
169
|
export function setRequirementPlan(
|
|
168
170
|
options: RequirementStoreOptions,
|
|
169
171
|
input: { requirementId: string; planId: string; action?: string },
|
|
@@ -189,12 +191,13 @@ export interface RequirementCompletionInput {
|
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
/**
|
|
192
|
-
*
|
|
194
|
+
* Project machine-complete Work delivery into a Requirement without asserting
|
|
195
|
+
* semantic acceptance. The Requirement waits for an explicit ChatGPT/user
|
|
196
|
+
* decision; a previously audited `done` Requirement is never reopened by
|
|
197
|
+
* historical execution evidence.
|
|
193
198
|
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* legacy Task receipt, or stale Work projection therefore cannot reopen or
|
|
197
|
-
* rewrite a completed user outcome.
|
|
199
|
+
* The legacy function name is retained for compatibility with completion
|
|
200
|
+
* callers. Its authority is evidence projection only, not semantic completion.
|
|
198
201
|
*/
|
|
199
202
|
export function completeRequirementFromWork(
|
|
200
203
|
options: RequirementStoreOptions,
|
|
@@ -221,19 +224,20 @@ export function completeRequirementFromWork(
|
|
|
221
224
|
if (receipt.delivery.kind === 'superseded' || receipt.delivery.status !== 'integrated' || !receipt.delivery.reachable) throw new Error('REQUIREMENT_WORK_DELIVERY_NOT_PROVEN');
|
|
222
225
|
if (!['complete', 'maintenance_warning'].includes(receipt.cleanup.status) || receipt.cleanup.blockers.length > 0) throw new Error('REQUIREMENT_WORK_CLEANUP_NOT_PROVEN');
|
|
223
226
|
|
|
227
|
+
if (current.value.state === 'waiting_for_user' && current.value.auditRefs.includes(receipt.receiptId)) return current.value;
|
|
224
228
|
return updateRequirement(options, {
|
|
225
229
|
requirementId,
|
|
226
|
-
action: '
|
|
230
|
+
action: 'requirement_delivery_evidence_recorded',
|
|
227
231
|
mutate: (latest) => {
|
|
228
|
-
// Preserve a concurrent
|
|
229
|
-
// never allowed to move a terminal Requirement backwards.
|
|
232
|
+
// Preserve a concurrent explicit semantic completion; stale execution
|
|
233
|
+
// evidence is never allowed to move a terminal Requirement backwards.
|
|
230
234
|
if (latest.state === 'done') return latest;
|
|
231
235
|
if (latest.state === 'cancelled') throw new Error('REQUIREMENT_CANCELLED');
|
|
232
236
|
return {
|
|
233
237
|
...latest,
|
|
234
|
-
state: '
|
|
238
|
+
state: 'waiting_for_user',
|
|
235
239
|
needsAttention: false,
|
|
236
|
-
attentionSummary:
|
|
240
|
+
attentionSummary: `Work ${work.workId} delivered machine-valid evidence (${receipt.receiptId}); ChatGPT/user semantic acceptance is still required.`,
|
|
237
241
|
auditRefs: Array.from(new Set([...latest.auditRefs, receipt.receiptId])).slice(-50),
|
|
238
242
|
};
|
|
239
243
|
},
|
|
@@ -7,6 +7,10 @@ export interface McpTimingTrace {
|
|
|
7
7
|
traceId?: string;
|
|
8
8
|
requestId?: string;
|
|
9
9
|
rpcId?: string | number;
|
|
10
|
+
/** Public Connector ingress versus the authoritative loopback Runtime span. */
|
|
11
|
+
layer?: 'public_gateway' | 'canonical_runtime';
|
|
12
|
+
/** Wall-clock start for joining nested spans and later client-side timing when available. */
|
|
13
|
+
startedAt?: string;
|
|
10
14
|
outcome?: 'ok' | 'error' | 'exception';
|
|
11
15
|
errorCode?: string;
|
|
12
16
|
sessionResolutionMs?: number;
|
|
@@ -21,6 +25,8 @@ export interface McpTimingTrace {
|
|
|
21
25
|
sessionId?: string;
|
|
22
26
|
repoId?: string;
|
|
23
27
|
workId?: string;
|
|
28
|
+
processId?: string;
|
|
29
|
+
route?: string;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
export interface McpIncident {
|
|
@@ -64,16 +64,7 @@ export function processCheckSemanticScopeKey(
|
|
|
64
64
|
input: Pick<RunCheckFacadeInput, 'workId' | 'verificationBinding' | 'checkoutId'>,
|
|
65
65
|
reuseScope: 'repository' | 'checkout',
|
|
66
66
|
): string {
|
|
67
|
-
|
|
68
|
-
const binding = input.verificationBinding;
|
|
69
|
-
const owner = [
|
|
70
|
-
input.workId ? `work:${input.workId}` : undefined,
|
|
71
|
-
binding?.executionSessionId ? `execution:${binding.executionSessionId}` : undefined,
|
|
72
|
-
binding?.editSessionId ? `edit:${binding.editSessionId}:r${binding.editRevision ?? 'unknown'}` : undefined,
|
|
73
|
-
binding?.issueId ? `issue:${binding.issueId}` : undefined,
|
|
74
|
-
binding?.taskId ? `task:${binding.taskId}` : undefined,
|
|
75
|
-
].filter(Boolean).join('|') || 'unbound';
|
|
76
|
-
return `${base}|owner:${owner}`;
|
|
67
|
+
return reuseScope === 'repository' ? 'repository' : `checkout:${input.checkoutId ?? 'unknown'}`;
|
|
77
68
|
}
|
|
78
69
|
|
|
79
70
|
export interface RunCheckFacadeResult {
|
|
@@ -18,10 +18,18 @@ export interface PersistedCheckResultReceipt {
|
|
|
18
18
|
cacheHit?: boolean;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export function allocatePersistedCheckResultReceiptPath(
|
|
21
|
+
export function allocatePersistedCheckResultReceiptPath(
|
|
22
|
+
controllerHome: string,
|
|
23
|
+
repoId: string,
|
|
24
|
+
requestId?: string,
|
|
25
|
+
): string {
|
|
22
26
|
const root = join(processLogDir(controllerHome, repoId), 'check-results');
|
|
23
27
|
mkdirSync(root, { recursive: true });
|
|
24
|
-
|
|
28
|
+
const stableRequestId = requestId?.trim();
|
|
29
|
+
const receiptId = stableRequestId
|
|
30
|
+
? `request-${createHash('sha256').update(stableRequestId).digest('hex').slice(0, 32)}`
|
|
31
|
+
: randomUUID();
|
|
32
|
+
return join(root, `${receiptId}.json`);
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
export function writePersistedCheckResultReceipt(path: string, input: Omit<PersistedCheckResultReceipt, 'schemaVersion' | 'receiptId'>): PersistedCheckResultReceipt {
|
|
@@ -273,6 +273,7 @@ export async function executeRepositoryCommandViaProcessRuntime(
|
|
|
273
273
|
executionIdentity.repositoryId,
|
|
274
274
|
executionIdentity.checkoutId,
|
|
275
275
|
input.repository.defaultBranch,
|
|
276
|
+
input.repository.canonicalRoot,
|
|
276
277
|
),
|
|
277
278
|
executionIdentity.repositoryId,
|
|
278
279
|
executionIdentity.checkoutId,
|