@aion0/forge 0.10.79 → 0.10.81
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/RELEASE_NOTES.md +4 -5
- package/app/api/tasks/[id]/hook/stop/route.ts +15 -0
- package/app/api/tasks/route.ts +2 -1
- package/cli/mw.mjs +7 -5
- package/cli/mw.ts +8 -6
- package/components/Dashboard.tsx +61 -28
- package/components/InlinePipelineView.tsx +22 -5
- package/components/PipelineHistory.tsx +306 -0
- package/components/TaskDetail.tsx +28 -1
- package/components/TmuxTaskTerminal.tsx +105 -0
- package/components/WebTerminal.tsx +7 -0
- package/docs/design_automation_records/Automation Redesign.dc.html +2019 -0
- package/docs/design_automation_records/README.md +232 -0
- package/lib/chat/agent-loop.ts +6 -0
- package/lib/chat/tool-dispatcher.ts +110 -9
- package/lib/help-docs/05-pipelines.md +31 -0
- package/lib/help-docs/25-chat-tools.md +23 -0
- package/lib/pipeline.ts +27 -3
- package/lib/task-manager.ts +73 -3
- package/lib/task-tmux-backend.ts +625 -0
- package/lib/workspace/skill-installer.ts +18 -8
- package/package.json +1 -1
- package/proxy.ts +5 -4
- package/src/core/db/database.ts +1 -0
- package/src/types/index.ts +3 -0
|
@@ -80,6 +80,7 @@ export function installForgeSkills(
|
|
|
80
80
|
function ensureForgePermissions(projectPath: string): void {
|
|
81
81
|
const settingsFile = join(projectPath, '.claude', 'settings.json');
|
|
82
82
|
const FORGE_CURL_ALLOW = 'Bash(curl*localhost*/smith*)';
|
|
83
|
+
const FORGE_TASK_CURL_ALLOW = 'Bash(curl*localhost*/api/tasks*)';
|
|
83
84
|
|
|
84
85
|
try {
|
|
85
86
|
let settings: any = {};
|
|
@@ -102,14 +103,21 @@ function ensureForgePermissions(projectPath: string): void {
|
|
|
102
103
|
});
|
|
103
104
|
if (settings.permissions.deny.length !== denyBefore) changed = true;
|
|
104
105
|
|
|
105
|
-
// Add forge curl
|
|
106
|
-
const
|
|
106
|
+
// Add forge curl allows if not present
|
|
107
|
+
const hasSmithAllow = settings.permissions.allow.some((rule: string) =>
|
|
107
108
|
rule.includes('localhost') && rule.includes('smith')
|
|
108
109
|
);
|
|
109
|
-
if (!
|
|
110
|
+
if (!hasSmithAllow) {
|
|
110
111
|
settings.permissions.allow.push(FORGE_CURL_ALLOW);
|
|
111
112
|
changed = true;
|
|
112
113
|
}
|
|
114
|
+
const hasTaskAllow = settings.permissions.allow.some((rule: string) =>
|
|
115
|
+
rule.includes('localhost') && rule.includes('/api/tasks')
|
|
116
|
+
);
|
|
117
|
+
if (!hasTaskAllow) {
|
|
118
|
+
settings.permissions.allow.push(FORGE_TASK_CURL_ALLOW);
|
|
119
|
+
changed = true;
|
|
120
|
+
}
|
|
113
121
|
|
|
114
122
|
if (changed) {
|
|
115
123
|
mkdirSync(join(projectPath, '.claude'), { recursive: true });
|
|
@@ -175,6 +183,7 @@ const FORGE_HOOK_MARKER = '# forge-stop-hook';
|
|
|
175
183
|
/**
|
|
176
184
|
* Install a Stop hook in user-level ~/.claude/settings.json.
|
|
177
185
|
* When Claude Code finishes a turn, the hook notifies Forge via HTTP.
|
|
186
|
+
* Handles both workspace agent completion and tmux task completion.
|
|
178
187
|
* Preserves existing user hooks. Creates backup before modifying.
|
|
179
188
|
*/
|
|
180
189
|
// Auto-prune leaves this many timestamped backups behind. `forge-backup-manual`
|
|
@@ -182,17 +191,18 @@ const FORGE_HOOK_MARKER = '# forge-stop-hook';
|
|
|
182
191
|
const FORGE_BACKUP_KEEP = 5;
|
|
183
192
|
const FORGE_BACKUP_RE = /^settings\.json\.forge-backup-\d{8}-\d{4}$/;
|
|
184
193
|
|
|
185
|
-
function installForgeStopHook(forgePort: number): void {
|
|
194
|
+
export function installForgeStopHook(forgePort: number): void {
|
|
186
195
|
const settingsFile = join(homedir(), '.claude', 'settings.json');
|
|
187
196
|
const now = new Date();
|
|
188
197
|
const dateStr = `${now.getFullYear()}${String(now.getMonth()+1).padStart(2,'0')}${String(now.getDate()).padStart(2,'0')}-${String(now.getHours()).padStart(2,'0')}${String(now.getMinutes()).padStart(2,'0')}`;
|
|
189
198
|
const backupFile = join(homedir(), '.claude', `settings.json.forge-backup-${dateStr}`);
|
|
190
199
|
const daemonPort = forgePort + 2; // 8403 → 8405
|
|
191
200
|
|
|
192
|
-
// Hook reads
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
|
|
201
|
+
// Hook reads context from .forge/ in the project dir ($(pwd)).
|
|
202
|
+
// agent-context.json → workspace smith completion (written by ensurePersistentSession)
|
|
203
|
+
// task-context.json → tmux task completion (written by task-tmux-backend)
|
|
204
|
+
// Falls back to env vars for workspace if file doesn't exist.
|
|
205
|
+
const hookCommand = `${FORGE_HOOK_MARKER}\nCTX_FILE="$(pwd)/.forge/agent-context.json"; TASK_FILE="$(pwd)/.forge/task-context.json"; if [ -f "$CTX_FILE" ]; then WS_ID=$(python3 -c "import json;print(json.load(open('$CTX_FILE')).get('workspaceId',''))" 2>/dev/null); AG_ID=$(python3 -c "import json;print(json.load(open('$CTX_FILE')).get('agentId',''))" 2>/dev/null); elif [ -n "$FORGE_WORKSPACE_ID" ]; then WS_ID="$FORGE_WORKSPACE_ID"; AG_ID="$FORGE_AGENT_ID"; fi; if [ -n "$WS_ID" ] && [ -n "$AG_ID" ]; then curl -s -X POST "http://localhost:${daemonPort}/workspace/$WS_ID/agents" -H "Content-Type: application/json" -d "{\\"action\\":\\"agent_done\\",\\"agentId\\":\\"$AG_ID\\"}" > /dev/null 2>&1 & fi; if [ -f "$TASK_FILE" ]; then T_ID=$(python3 -c "import json;print(json.load(open('$TASK_FILE')).get('taskId',''))" 2>/dev/null); T_PORT=$(python3 -c "import json;print(json.load(open('$TASK_FILE')).get('port','8403'))" 2>/dev/null); if [ -n "$T_ID" ]; then curl -s -X POST "http://localhost:$T_PORT/api/tasks/$T_ID/hook/stop" > /dev/null 2>&1 & fi; fi`;
|
|
196
206
|
|
|
197
207
|
try {
|
|
198
208
|
let settings: any = {};
|
package/package.json
CHANGED
package/proxy.ts
CHANGED
|
@@ -38,10 +38,11 @@ export const proxy = auth((req) => {
|
|
|
38
38
|
return NextResponse.next();
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
//
|
|
42
|
-
// callers: pipelines via curl, jobs scheduler, CLI
|
|
43
|
-
// fall through to the normal check.
|
|
44
|
-
|
|
41
|
+
// Loopback-only routes: no auth required when called from localhost.
|
|
42
|
+
// Used by Forge-internal callers: pipelines via curl, jobs scheduler, CLI.
|
|
43
|
+
// Non-loopback hosts fall through to the normal auth check.
|
|
44
|
+
const loopbackOnly = ['/api/connector-tool', '/api/tasks'];
|
|
45
|
+
if (loopbackOnly.some(p => pathname === p || pathname.startsWith(p + '/'))) {
|
|
45
46
|
const host = req.headers.get('host') || '';
|
|
46
47
|
if (host.startsWith('127.0.0.1:') || host.startsWith('localhost:')) {
|
|
47
48
|
return NextResponse.next();
|
package/src/core/db/database.ts
CHANGED
|
@@ -249,6 +249,7 @@ function initSchema(db: Database.Database) {
|
|
|
249
249
|
migrate('ALTER TABLE project_pipelines ADD COLUMN last_run_at TEXT');
|
|
250
250
|
migrate('ALTER TABLE pipeline_runs ADD COLUMN dedup_key TEXT');
|
|
251
251
|
migrate("ALTER TABLE tasks ADD COLUMN agent TEXT DEFAULT 'claude'");
|
|
252
|
+
migrate('ALTER TABLE tasks ADD COLUMN backend TEXT DEFAULT NULL');
|
|
252
253
|
|
|
253
254
|
// Unique index for dedup (needs pipeline_runs to exist; only applies when dedup_key is NOT NULL).
|
|
254
255
|
try { db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_pipeline_runs_dedup ON pipeline_runs(project_path, workflow_name, dedup_key)'); } catch {}
|
package/src/types/index.ts
CHANGED
|
@@ -110,6 +110,9 @@ export interface Task {
|
|
|
110
110
|
completedAt?: string;
|
|
111
111
|
scheduledAt?: string;
|
|
112
112
|
agent?: string;
|
|
113
|
+
/** 'tmux' = run inside a dedicated tmux session (subscription billing).
|
|
114
|
+
* Omitted / undefined = default headless spawn. */
|
|
115
|
+
backend?: 'tmux';
|
|
113
116
|
// Lite-list metadata: present in /api/tasks responses, undefined in detail
|
|
114
117
|
logSize?: number;
|
|
115
118
|
hasGitDiff?: boolean;
|