fix(FN-4884): finalize docs and keep workspace gates green
Fusion-Task-Id: FN-4884 Fusion-Task-Lineage: b906ade4-a9f1-4f2e-b684-4ebf49c3e7e6
This commit is contained in:
committed by
gsxdsm
parent
030a27b40d
commit
96a19309ae
5
.changeset/fn-4884-reanchor-already-bound.md
Normal file
5
.changeset/fn-4884-reanchor-already-bound.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix bootstrap-misbinding recovery when a Fusion worktree is already bound to its task branch at the target base SHA (no more `fatal: '<branch>' is already used by worktree` errors during re-anchor).
|
||||||
@@ -33,7 +33,7 @@ Mission → Milestone → Slice → Feature → Task
|
|||||||
- **Agent tools** — `fn_agent_stop`, `fn_agent_start`, `fn_agent_create`, `fn_agent_delete`, `fn_list_agents`, `fn_delegate_task`, `fn_agent_show`, `fn_agent_org_chart`
|
- **Agent tools** — `fn_agent_stop`, `fn_agent_start`, `fn_agent_create`, `fn_agent_delete`, `fn_list_agents`, `fn_delegate_task`, `fn_agent_show`, `fn_agent_org_chart`
|
||||||
- **Skills tools** — `fn_skills_search`, `fn_skills_install`
|
- **Skills tools** — `fn_skills_search`, `fn_skills_install`
|
||||||
- **Insight tools** — `fn_insight_list`, `fn_insight_show`, `fn_insight_run_list`, `fn_insight_run_show`
|
- **Insight tools** — `fn_insight_list`, `fn_insight_show`, `fn_insight_run_list`, `fn_insight_run_show`
|
||||||
- **Other tools** — `fn_web_fetch`, `fn_research_run`, `fn_research_list`, `fn_research_get`, `fn_research_cancel`, `fn_research_retry`, `fn_experiment_finalize`
|
- **Other tools** — `fn_web_fetch`, `fn_secret_get`, `fn_research_run`, `fn_research_list`, `fn_research_get`, `fn_research_cancel`, `fn_research_retry`, `fn_experiment_finalize`
|
||||||
<!-- END: tool-categories -->
|
<!-- END: tool-categories -->
|
||||||
- **Dashboard** — Use `/fn` command to start/stop the dashboard
|
- **Dashboard** — Use `/fn` command to start/stop the dashboard
|
||||||
|
|
||||||
|
|||||||
@@ -427,6 +427,15 @@ Lightweight URL fetch (no JS rendering). Use agent-browser skill for JS-heavy pa
|
|||||||
| `timeoutMs` | number | — | Timeout in milliseconds (default: 30000) |
|
| `timeoutMs` | number | — | Timeout in milliseconds (default: 30000) |
|
||||||
| `maxBytes` | number | — | Max bytes to return (default: 512000) |
|
| `maxBytes` | number | — | Max bytes to return (default: 512000) |
|
||||||
|
|
||||||
|
### fn_secret_get
|
||||||
|
|
||||||
|
Read a secret by key using per-secret access policy.
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| `key` | string | ✓ | Secret key |
|
||||||
|
| `scope` | union | — | Optional scope |
|
||||||
|
|
||||||
### fn_research_run
|
### fn_research_run
|
||||||
|
|
||||||
Cited-research pipeline: create a bounded search/fetch/synthesis run (not an autonomous experiment loop) and optionally wait for completion.
|
Cited-research pipeline: create a bounded search/fetch/synthesis run (not an autonomous experiment loop) and optionally wait for completion.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ All skill/extension tool invocations in this catalog use the public `fn_*` names
|
|||||||
| `fn_task_browse_github_issues` | List open GitHub issues from a repository to browse before importing. Returns issue numbers, titles, and URLs for selection. Use with fn_task_import_github_issue to import specific issues by number. |
|
| `fn_task_browse_github_issues` | List open GitHub issues from a repository to browse before importing. Returns issue numbers, titles, and URLs for selection. Use with fn_task_import_github_issue to import specific issues by number. |
|
||||||
| `fn_task_plan` | Create a task via AI-guided planning mode — interactive conversation to refine your idea into a well-specified task. |
|
| `fn_task_plan` | Create a task via AI-guided planning mode — interactive conversation to refine your idea into a well-specified task. |
|
||||||
| `fn_web_fetch` | Lightweight URL fetch (no JS rendering). Use agent-browser skill for JS-heavy pages. URL to fetch (http/https) Optional extraction hint for downstream summarization Timeout in milliseconds (default: 30000) Max bytes to return (default: 512000) |
|
| `fn_web_fetch` | Lightweight URL fetch (no JS rendering). Use agent-browser skill for JS-heavy pages. URL to fetch (http/https) Optional extraction hint for downstream summarization Timeout in milliseconds (default: 30000) Max bytes to return (default: 512000) |
|
||||||
|
| `fn_secret_get` | Read a secret by key using per-secret access policy. |
|
||||||
| `fn_research_run` | Cited-research pipeline: create a bounded search/fetch/synthesis run (not an autonomous experiment loop) and optionally wait for completion. |
|
| `fn_research_run` | Cited-research pipeline: create a bounded search/fetch/synthesis run (not an autonomous experiment loop) and optionally wait for completion. |
|
||||||
| `fn_research_list` | Cited-research pipeline: list recent search/fetch/synthesis runs (not experiment-loop sessions). |
|
| `fn_research_list` | Cited-research pipeline: list recent search/fetch/synthesis runs (not experiment-loop sessions). |
|
||||||
| `fn_research_get` | Cited-research pipeline: get one run with structured findings and citations (not experiment-loop state). |
|
| `fn_research_get` | Cited-research pipeline: get one run with structured findings and citations (not experiment-loop state). |
|
||||||
|
|||||||
@@ -1,9 +1,32 @@
|
|||||||
import { createCipheriv, createDecipheriv, randomBytes, scrypt as scryptCallback } from "node:crypto";
|
import { createCipheriv, createDecipheriv, randomBytes, scrypt as scryptCallback } from "node:crypto";
|
||||||
import { promisify } from "node:util";
|
|
||||||
import type { SecretAccessPolicy } from "./types.js";
|
import type { SecretAccessPolicy } from "./types.js";
|
||||||
import type { SecretScope } from "./secrets-store.js";
|
import type { SecretScope } from "./secrets-store.js";
|
||||||
|
|
||||||
const scrypt = promisify(scryptCallback);
|
function deriveKey(
|
||||||
|
passphrase: string,
|
||||||
|
salt: Buffer,
|
||||||
|
keyLen: number,
|
||||||
|
params: { N: number; r: number; p: number },
|
||||||
|
): Promise<Buffer> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
scryptCallback(
|
||||||
|
passphrase,
|
||||||
|
salt,
|
||||||
|
keyLen,
|
||||||
|
{
|
||||||
|
...params,
|
||||||
|
maxmem: 64 * 1024 * 1024,
|
||||||
|
},
|
||||||
|
(error, derivedKey) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(derivedKey as Buffer);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export interface WrappedSecretsBundle {
|
export interface WrappedSecretsBundle {
|
||||||
ciphertext: string;
|
ciphertext: string;
|
||||||
@@ -37,12 +60,7 @@ const DEFAULT_KDF_PARAMS = { N: 32768, r: 8, p: 1, keyLen: 32 } as const;
|
|||||||
export async function wrapSecretsBundle(records: SecretsSyncRecord[], passphrase: string): Promise<WrappedSecretsBundle> {
|
export async function wrapSecretsBundle(records: SecretsSyncRecord[], passphrase: string): Promise<WrappedSecretsBundle> {
|
||||||
const salt = randomBytes(16);
|
const salt = randomBytes(16);
|
||||||
const nonce = randomBytes(12);
|
const nonce = randomBytes(12);
|
||||||
const key = await scrypt(passphrase, salt, DEFAULT_KDF_PARAMS.keyLen, {
|
const key = await deriveKey(passphrase, salt, DEFAULT_KDF_PARAMS.keyLen, DEFAULT_KDF_PARAMS);
|
||||||
N: DEFAULT_KDF_PARAMS.N,
|
|
||||||
r: DEFAULT_KDF_PARAMS.r,
|
|
||||||
p: DEFAULT_KDF_PARAMS.p,
|
|
||||||
maxmem: 64 * 1024 * 1024,
|
|
||||||
}) as Buffer;
|
|
||||||
|
|
||||||
const cipher = createCipheriv("aes-256-gcm", key, nonce);
|
const cipher = createCipheriv("aes-256-gcm", key, nonce);
|
||||||
const payload = Buffer.from(JSON.stringify(records), "utf8");
|
const payload = Buffer.from(JSON.stringify(records), "utf8");
|
||||||
@@ -72,12 +90,7 @@ export async function unwrapSecretsBundle(envelope: WrappedSecretsBundle, passph
|
|||||||
const authTag = packed.subarray(packed.length - 16);
|
const authTag = packed.subarray(packed.length - 16);
|
||||||
const encrypted = packed.subarray(0, packed.length - 16);
|
const encrypted = packed.subarray(0, packed.length - 16);
|
||||||
|
|
||||||
const key = await scrypt(passphrase, salt, envelope.kdfParams.keyLen, {
|
const key = await deriveKey(passphrase, salt, envelope.kdfParams.keyLen, envelope.kdfParams);
|
||||||
N: envelope.kdfParams.N,
|
|
||||||
r: envelope.kdfParams.r,
|
|
||||||
p: envelope.kdfParams.p,
|
|
||||||
maxmem: 64 * 1024 * 1024,
|
|
||||||
}) as Buffer;
|
|
||||||
|
|
||||||
const decipher = createDecipheriv("aes-256-gcm", key, nonce);
|
const decipher = createDecipheriv("aes-256-gcm", key, nonce);
|
||||||
decipher.setAuthTag(authTag);
|
decipher.setAuthTag(authTag);
|
||||||
|
|||||||
@@ -535,6 +535,13 @@ export interface ReanchorBranchToBaseResult {
|
|||||||
newTipSha: string;
|
newTipSha: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-anchor a task branch to base while handling already-at-base worktrees.
|
||||||
|
*
|
||||||
|
* Fast-path: when both worktree HEAD and branch tip already equal baseSha,
|
||||||
|
* avoid detach/rebranch churn (`checkout -B` can fail with worktree-binding
|
||||||
|
* conflicts) and only attempt lightweight branch re-association.
|
||||||
|
*/
|
||||||
export async function reanchorBranchToBase(
|
export async function reanchorBranchToBase(
|
||||||
input: ReanchorBranchToBaseInput,
|
input: ReanchorBranchToBaseInput,
|
||||||
): Promise<ReanchorBranchToBaseResult> {
|
): Promise<ReanchorBranchToBaseResult> {
|
||||||
|
|||||||
@@ -2020,10 +2020,11 @@ export class SelfHealingManager {
|
|||||||
});
|
});
|
||||||
result.worktreeRemoved = true;
|
result.worktreeRemoved = true;
|
||||||
if (task) {
|
if (task) {
|
||||||
const patch: Partial<Task> = { worktree: null };
|
const patch = {
|
||||||
if (task.branch === branchName) patch.branch = null;
|
worktree: null as string | null,
|
||||||
await this.store.updateTask(task.id, patch);
|
...(task.branch === branchName ? { branch: null as string | null } : {}),
|
||||||
task = { ...task, ...patch } as Task;
|
};
|
||||||
|
await this.store.updateTask(task.id, patch as Partial<Task>);
|
||||||
}
|
}
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||||
|
|||||||
Reference in New Issue
Block a user