@moretea-labs/forge 1.5.0 → 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.
Files changed (138) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/DESIGN.md +7 -2
  3. package/README.md +7 -7
  4. package/README.zh-CN.md +5 -3
  5. package/THIRD_PARTY_NOTICES.md +1 -1
  6. package/assets/hooks/pre-edit-guard.sh +8 -39
  7. package/assets/hooks/prompt-guard.sh +2 -2
  8. package/assets/plugin-registry.v1.json +54 -3
  9. package/assets/reference-configs/hook-operations.md +1 -1
  10. package/assets/reference-configs/sprint-contracts.md +2 -2
  11. package/assets/skill-version.json +2 -2
  12. package/assets/templates/helpers/ensure-task-workflow.sh +2 -2
  13. package/assets/templates/helpers/sprint-backlog.sh +6 -6
  14. package/docs/architecture/current/automation-and-schedule-engine.md +9 -3
  15. package/docs/architecture/current/codegraph-context-provider.md +15 -3
  16. package/docs/architecture/current/dispatch-and-agent-strategy.md +32 -13
  17. package/docs/architecture/current/entity-model.md +20 -0
  18. package/docs/architecture/current/external-plugin-governance.md +3 -3
  19. package/docs/architecture/current/system-overview.md +26 -1
  20. package/docs/architecture/current/thin-harness-v1.md +10 -0
  21. package/docs/forge-chatgpt-mcp-setup.md +27 -3
  22. package/docs/operations/chatgpt-personal-assistant-runtime.md +12 -2
  23. package/docs/operations/controller-browser-plugin.md +12 -16
  24. package/docs/operations/features.md +1 -1
  25. package/docs/operations/homebrew.md +2 -2
  26. package/docs/public-usage-guide.md +1 -1
  27. package/docs/public-usage-guide.zh-CN.md +1 -1
  28. package/docs/tutorials/01-install-and-start.md +6 -4
  29. package/docs/tutorials/01-install-and-start.zh-CN.md +3 -1
  30. package/docs/tutorials/02-connect-chatgpt.md +8 -2
  31. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +8 -2
  32. package/docs/wiki/Installation.md +1 -1
  33. package/docs/wiki/Quick-Start.md +1 -1
  34. package/package.json +1 -1
  35. package/scripts/check-public-docs.sh +14 -1
  36. package/scripts/migrate-chatgpt-automation-schedules.ts +64 -0
  37. package/scripts/sprint-backlog.sh +1 -1
  38. package/src/cli/commands/chatgpt.ts +31 -2
  39. package/src/cli/commands/init-hook.ts +3 -1
  40. package/src/cli/commands/mcp.ts +2 -0
  41. package/src/cli/commands/plugin.ts +36 -9
  42. package/src/cli/commands/runtime.ts +1 -0
  43. package/src/cli/commands/setup-profile.ts +49 -18
  44. package/src/cli/controller/context-pack.ts +158 -6
  45. package/src/cli/hook/prompt-guard-decision.ts +17 -18
  46. package/src/cli/hook/prompt-intents.ts +1 -1
  47. package/src/cli/local-bridge/console-assets.ts +17 -0
  48. package/src/cli/local-bridge/console-automations.ts +205 -23
  49. package/src/cli/local-bridge/console-view-models.ts +2 -0
  50. package/src/cli/local-bridge/facade-api.ts +2 -0
  51. package/src/cli/local-bridge/frontend/src/App.tsx +1 -1
  52. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +143 -3
  53. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +1 -1
  54. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +181 -3
  55. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
  56. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
  57. package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -6
  58. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
  59. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -1
  60. package/src/cli/local-bridge/frontend/src/styles.css +27 -11
  61. package/src/cli/local-bridge/frontend/src/types.ts +6 -2
  62. package/src/cli/local-bridge/server.ts +31 -20
  63. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  64. package/src/cli/local-bridge/ui-dist/app.js +5003 -4815
  65. package/src/cli/mcp/auth.ts +2 -0
  66. package/src/cli/mcp/paths.ts +8 -2
  67. package/src/cli/mcp/repository-tools.ts +124 -9
  68. package/src/cli/mcp/server.ts +164 -19
  69. package/src/cli/mcp/setup.ts +9 -1
  70. package/src/cli/mcp/transports/http.ts +52 -2
  71. package/src/cli/mcp/transports/session-registry.ts +24 -0
  72. package/src/cli/repositories/command-classifier.ts +113 -0
  73. package/src/cli/repositories/safe-patch.ts +6 -4
  74. package/src/cli/repositories/structured-git.ts +49 -7
  75. package/src/cli/repository/inspector.ts +16 -3
  76. package/src/runtime/assistant/intent.ts +3 -3
  77. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +157 -2
  78. package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
  79. package/src/runtime/control-plane/execution/validation.ts +15 -2
  80. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +32 -0
  81. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +6 -7
  82. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +2 -1
  83. package/src/runtime/control-plane/facade/capability-registry.ts +1 -1
  84. package/src/runtime/control-plane/facade/controller-session-store.ts +12 -13
  85. package/src/runtime/control-plane/facade/goal-workloop-access.ts +7 -2
  86. package/src/runtime/control-plane/facade/goal-workloop.ts +231 -8
  87. package/src/runtime/control-plane/facade/plan-contract-store.ts +32 -12
  88. package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
  89. package/src/runtime/control-plane/facade/types.ts +33 -6
  90. package/src/runtime/control-plane/facade/work-contract-store.ts +31 -2
  91. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +375 -23
  92. package/src/runtime/control-plane/persistence/requirement-store.ts +15 -11
  93. package/src/runtime/diagnostics/mcp-timing.ts +6 -0
  94. package/src/runtime/execution/process-runtime/check-facade.ts +1 -10
  95. package/src/runtime/execution/process-runtime/check-result.ts +10 -2
  96. package/src/runtime/execution/process-runtime/command-facade.ts +1 -0
  97. package/src/runtime/execution/process-runtime/gc.ts +64 -10
  98. package/src/runtime/execution/process-runtime/persisted-check.ts +10 -2
  99. package/src/runtime/execution/process-runtime/resource-claims.ts +87 -2
  100. package/src/runtime/execution/process-runtime/runtime.ts +16 -1
  101. package/src/runtime/execution/process-runtime/store.ts +6 -0
  102. package/src/runtime/gateway/mcp/execution-tools.ts +27 -12
  103. package/src/runtime/gateway/mcp/router.ts +1 -1
  104. package/src/runtime/gateway/mcp/runtime-tools.ts +482 -178
  105. package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
  106. package/src/runtime/plugins/browser-adapter.ts +19 -72
  107. package/src/runtime/plugins/browser-handoff-host.ts +6 -31
  108. package/src/runtime/plugins/browser-handoff.ts +0 -3
  109. package/src/runtime/plugins/browser-macos-bridge.ts +8 -0
  110. package/src/runtime/plugins/desktop-operator-registration.ts +8 -11
  111. package/src/runtime/plugins/external-registration.ts +84 -5
  112. package/src/runtime/plugins/first-party-registry.ts +2 -2
  113. package/src/runtime/plugins/gmail-adapter.ts +28 -2
  114. package/src/runtime/plugins/local-system-adapter.ts +2 -2
  115. package/src/runtime/plugins/plugin-management-adapter.ts +140 -0
  116. package/src/runtime/plugins/store.ts +107 -5
  117. package/src/runtime/plugins/types.ts +4 -0
  118. package/src/runtime/recovery/actions.ts +0 -10
  119. package/src/runtime/recovery/classifier.ts +0 -9
  120. package/src/runtime/recovery/maintenance-executor.ts +43 -11
  121. package/src/runtime/recovery/probe.ts +0 -1
  122. package/src/runtime/recovery/types.ts +0 -1
  123. package/src/runtime/root/package-connector-service.ts +136 -0
  124. package/src/runtime/root/package-runtime-service.ts +25 -12
  125. package/src/runtime/root/release-materialize.ts +161 -18
  126. package/src/runtime/root/service.ts +6 -4
  127. package/src/runtime/safe-tooling/index.ts +0 -1
  128. package/src/runtime/safe-tooling/types.ts +0 -30
  129. package/src/runtime/standalone-recovery/core.ts +2 -2
  130. package/src/runtime/workflow/schedules/browser-probe.ts +180 -0
  131. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
  132. package/src/runtime/workflow/schedules/engine.ts +357 -64
  133. package/src/runtime/workflow/schedules/types.ts +5 -0
  134. package/src/runtime/workflow/schedules/work-continuation.ts +100 -27
  135. package/bin/forge-desktop-helper.mjs +0 -204
  136. package/docs/operations/controller-desktop-plugin.md +0 -112
  137. package/src/runtime/plugins/desktop-adapter.ts +0 -374
  138. package/src/runtime/safe-tooling/browser-safe.ts +0 -133
@@ -0,0 +1,180 @@
1
+ import { createHash } from 'crypto';
2
+ import type { RepositoryRecord } from '../../../cli/repositories/types';
3
+ import { executeBrowserPluginAction } from '../../plugins/browser-adapter';
4
+
5
+ export type ScheduledBrowserProbeStatus = 'observed' | 'keepalive' | 'auth_required';
6
+
7
+ export interface ScheduledBrowserProbeResult {
8
+ status: ScheduledBrowserProbeStatus;
9
+ fingerprint?: string;
10
+ url?: string;
11
+ sessionId: string;
12
+ projectedLineCount: number;
13
+ observedChars: number;
14
+ truncated: boolean;
15
+ authReason?: string;
16
+ }
17
+
18
+ function stringValue(value: unknown): string | undefined {
19
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
20
+ }
21
+
22
+ function recordValue(value: unknown): Record<string, unknown> {
23
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
24
+ }
25
+
26
+ function stringList(value: unknown): string[] {
27
+ return Array.isArray(value) ? value.map((entry) => String(entry).trim()).filter(Boolean) : [];
28
+ }
29
+
30
+ function boundedNumber(value: unknown, fallback: number, min: number, max: number): number {
31
+ return typeof value === 'number' && Number.isFinite(value)
32
+ ? Math.max(min, Math.min(Math.trunc(value), max))
33
+ : fallback;
34
+ }
35
+
36
+ export function buildScheduledBrowserFingerprint(text: string, includeTerms: string[] = [], ignorePatterns: string[] = []): { fingerprint: string; lineCount: number } {
37
+ const terms = includeTerms.map((term) => term.toLocaleLowerCase());
38
+ let lines = text
39
+ .split(/\r?\n/)
40
+ .map((line) => line.replace(/\s+/g, ' ').trim())
41
+ .filter(Boolean);
42
+ if (terms.length > 0) {
43
+ lines = lines.filter((line) => {
44
+ const normalized = line.toLocaleLowerCase();
45
+ return terms.some((term) => normalized.includes(term));
46
+ });
47
+ }
48
+ for (const pattern of ignorePatterns) {
49
+ try {
50
+ const expression = new RegExp(pattern, 'giu');
51
+ lines = lines.map((line) => line.replace(expression, ' ').replace(/\s+/g, ' ').trim()).filter(Boolean);
52
+ } catch {
53
+ throw new Error(`SCHEDULE_BROWSER_PROBE_IGNORE_PATTERN_INVALID: ${pattern}`);
54
+ }
55
+ }
56
+ lines = [...new Set(lines)].sort((left, right) => left.localeCompare(right));
57
+ return { fingerprint: createHash('sha256').update(lines.join('\n')).digest('hex'), lineCount: lines.length };
58
+ }
59
+
60
+ export function classifyScheduledBrowserObservation(
61
+ previousFingerprint: string | undefined,
62
+ currentFingerprint: string | undefined,
63
+ wakeOnFirstObservation = false,
64
+ ): { status: 'baseline' | 'unchanged' | 'changed'; shouldWake: boolean } {
65
+ const firstObservation = !previousFingerprint;
66
+ const changed = Boolean(previousFingerprint && currentFingerprint && previousFingerprint !== currentFingerprint);
67
+ return {
68
+ status: firstObservation ? 'baseline' : changed ? 'changed' : 'unchanged',
69
+ shouldWake: changed || (firstObservation && wakeOnFirstObservation),
70
+ };
71
+ }
72
+
73
+ function containsAny(value: string | undefined, terms: string[]): string | undefined {
74
+ if (!value) return undefined;
75
+ const normalized = value.toLocaleLowerCase();
76
+ return terms.find((term) => normalized.includes(term.toLocaleLowerCase()));
77
+ }
78
+
79
+ async function browserAction(
80
+ controllerHome: string,
81
+ repository: RepositoryRecord,
82
+ occurrenceId: string,
83
+ actionId: string,
84
+ args: Record<string, unknown>,
85
+ ): Promise<Record<string, unknown>> {
86
+ return executeBrowserPluginAction({
87
+ controllerHome,
88
+ repoId: repository.repoId,
89
+ repoRoot: repository.canonicalRoot,
90
+ pluginId: 'browser',
91
+ actionId,
92
+ requestId: `schedule-browser-probe:${occurrenceId}:${actionId}`,
93
+ args,
94
+ origin: { surface: 'schedule', actor: 'browser-probe', correlationId: occurrenceId },
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Bounded read-only browser observation for Schedule. It never interprets page
100
+ * meaning: it only refreshes/navigates, extracts bounded text, projects matching
101
+ * lines, detects configured login markers, and returns a stable fingerprint.
102
+ */
103
+ export async function executeScheduledBrowserProbe(input: {
104
+ controllerHome: string;
105
+ repository: RepositoryRecord;
106
+ occurrenceId: string;
107
+ args: Record<string, unknown>;
108
+ }): Promise<ScheduledBrowserProbeResult> {
109
+ const requestedSessionId = stringValue(input.args.probe_session_id) ?? stringValue(input.args.session_id);
110
+ const url = stringValue(input.args.probe_url) ?? stringValue(input.args.url);
111
+ if (!requestedSessionId && !url) throw new Error('SCHEDULE_BROWSER_PROBE_TARGET_REQUIRED');
112
+
113
+ const waitUntil = stringValue(input.args.wait_until) ?? 'domcontentloaded';
114
+ const timeoutMs = boundedNumber(input.args.timeout_ms, 60_000, 1_000, 120_000);
115
+ let navigation: Record<string, unknown>;
116
+ if (url) {
117
+ navigation = await browserAction(input.controllerHome, input.repository, input.occurrenceId, 'navigate', {
118
+ ...(requestedSessionId ? { session_id: requestedSessionId } : {}),
119
+ url,
120
+ wait_until: waitUntil,
121
+ timeout_ms: timeoutMs,
122
+ retries: 1,
123
+ });
124
+ } else {
125
+ navigation = await browserAction(input.controllerHome, input.repository, input.occurrenceId, 'reload', {
126
+ session_id: requestedSessionId!,
127
+ wait_until: waitUntil,
128
+ timeout_ms: timeoutMs,
129
+ });
130
+ }
131
+
132
+ const session = recordValue(navigation.session);
133
+ const sessionId = stringValue(navigation.sessionId) ?? stringValue(session.sessionId) ?? stringValue(session.id) ?? requestedSessionId;
134
+ if (!sessionId) throw new Error('SCHEDULE_BROWSER_PROBE_SESSION_UNAVAILABLE');
135
+ const observedUrl = stringValue(navigation.url) ?? stringValue(session.url) ?? url;
136
+ const loginUrlTerms = stringList(input.args.login_url_terms);
137
+ const urlLoginMarker = containsAny(observedUrl, loginUrlTerms);
138
+
139
+ const keepaliveOnly = input.args.keepalive_only === true;
140
+ const loginTextTerms = stringList(input.args.login_text_terms);
141
+ const mustExtractText = !keepaliveOnly || loginTextTerms.length > 0;
142
+ let rawText = '';
143
+ let truncated = false;
144
+ if (mustExtractText) {
145
+ const textResult = await browserAction(input.controllerHome, input.repository, input.occurrenceId, 'get_text', {
146
+ session_id: sessionId,
147
+ ...(stringValue(input.args.selector) ? { selector: stringValue(input.args.selector) } : {}),
148
+ max_chars: boundedNumber(input.args.max_chars, 20_000, 256, 100_000),
149
+ });
150
+ rawText = stringValue(textResult.text) ?? '';
151
+ truncated = textResult.truncated === true;
152
+ }
153
+ const textLoginMarker = containsAny(rawText, loginTextTerms);
154
+ const authMarker = urlLoginMarker ?? textLoginMarker;
155
+ if (authMarker) {
156
+ return {
157
+ status: 'auth_required',
158
+ url: observedUrl,
159
+ sessionId,
160
+ projectedLineCount: 0,
161
+ observedChars: rawText.length,
162
+ truncated,
163
+ authReason: `matched configured login marker: ${authMarker}`,
164
+ };
165
+ }
166
+ if (keepaliveOnly) {
167
+ return { status: 'keepalive', url: observedUrl, sessionId, projectedLineCount: 0, observedChars: rawText.length, truncated };
168
+ }
169
+
170
+ const projected = buildScheduledBrowserFingerprint(rawText, stringList(input.args.include_terms), stringList(input.args.ignore_patterns));
171
+ return {
172
+ status: 'observed',
173
+ fingerprint: projected.fingerprint,
174
+ url: observedUrl,
175
+ sessionId,
176
+ projectedLineCount: projected.lineCount,
177
+ observedChars: rawText.length,
178
+ truncated,
179
+ };
180
+ }
@@ -0,0 +1,40 @@
1
+ import type { RepositorySchedule } from './types';
2
+
3
+ export const CHATGPT_AUTOMATION_EXECUTION_PROFILE = 'chatgpt_browser_v1';
4
+ export const CHATGPT_AUTOMATION_DEFAULT_MODEL = 'gpt-5.6';
5
+ export const CHATGPT_AUTOMATION_DEFAULT_REASONING = 'high';
6
+ export const CHATGPT_AUTOMATION_DEFAULT_TAB_POLICY = 'auto';
7
+
8
+ export interface ChatgptAutomationScheduleMigrationResult {
9
+ changed: boolean;
10
+ schedule: RepositorySchedule;
11
+ }
12
+
13
+ function nonEmptyString(value: unknown): string | undefined {
14
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
15
+ }
16
+
17
+ export function migrateChatgptAutomationSchedule(schedule: RepositorySchedule): ChatgptAutomationScheduleMigrationResult {
18
+ if (schedule.action.operation !== 'external_controller_wake') return { changed: false, schedule };
19
+ const current = schedule.action.arguments ?? {};
20
+ const controllerType = nonEmptyString(current.controller_type) ?? 'chatgpt';
21
+ if (controllerType !== 'chatgpt') return { changed: false, schedule };
22
+
23
+ const argumentsNext: Record<string, unknown> = {
24
+ ...current,
25
+ controller_type: controllerType,
26
+ model: nonEmptyString(current.model) ?? CHATGPT_AUTOMATION_DEFAULT_MODEL,
27
+ reasoning: nonEmptyString(current.reasoning) ?? CHATGPT_AUTOMATION_DEFAULT_REASONING,
28
+ tab_policy: nonEmptyString(current.tab_policy) ?? CHATGPT_AUTOMATION_DEFAULT_TAB_POLICY,
29
+ execution_profile: nonEmptyString(current.execution_profile) ?? CHATGPT_AUTOMATION_EXECUTION_PROFILE,
30
+ };
31
+ const changed = Object.entries(argumentsNext).some(([key, value]) => current[key] !== value);
32
+ if (!changed) return { changed: false, schedule };
33
+ return {
34
+ changed: true,
35
+ schedule: {
36
+ ...schedule,
37
+ action: { ...schedule.action, arguments: argumentsNext },
38
+ },
39
+ };
40
+ }