@aion0/forge 0.10.51 → 0.10.55
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 +3 -3
- package/app/api/cache/route.ts +125 -41
- package/app/api/chat/sessions/[id]/abort/route.ts +14 -0
- package/app/api/chat/sessions/[id]/note/route.ts +16 -0
- package/app/api/files/[...path]/route.ts +94 -0
- package/app/api/scratch/[...path]/route.ts +5 -0
- package/app/chat/page.tsx +237 -36
- package/app/files/[...path]/page.tsx +22 -0
- package/app/scratch/[...path]/page.tsx +24 -0
- package/components/Dashboard.tsx +76 -24
- package/components/ScratchViewer.tsx +152 -0
- package/lib/chat/agent-loop.ts +72 -2
- package/lib/chat/link-patterns.ts +29 -6
- package/lib/chat/tool-dispatcher.ts +270 -17
- package/lib/chat/turn-control.ts +81 -0
- package/lib/chat-standalone.ts +51 -0
- package/lib/help-docs/10-troubleshooting.md +16 -0
- package/lib/help-docs/17-connectors.md +19 -0
- package/lib/help-docs/25-chat-tools.md +125 -0
- package/lib/help-docs/CLAUDE.md +2 -0
- package/lib/scratch-cleanup.ts +25 -16
- package/package.json +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Chat tools — what the chat agent can do directly
|
|
2
|
+
|
|
3
|
+
The chat agent (Forge's `/chat` page + the IDE plugin + Telegram bot) talks
|
|
4
|
+
to LLMs that can call **builtin tools** without round-tripping through a
|
|
5
|
+
background task. These cover the everyday "Forge knows / Forge owns" cases
|
|
6
|
+
so the agent doesn't have to dispatch a Claude CLI task just to read a
|
|
7
|
+
file or fire a pipeline.
|
|
8
|
+
|
|
9
|
+
This doc lists the tools the agent has by default and the workflows they
|
|
10
|
+
unlock. (Connector tools — `mantis.search_bugs`, `gitlab.list_my_todos`,
|
|
11
|
+
etc. — are documented in `17-connectors.md`.)
|
|
12
|
+
|
|
13
|
+
## File access — `<dataDir>/`
|
|
14
|
+
|
|
15
|
+
Forge's data dir (`~/.forge/data/` by default, override with `FORGE_DATA_DIR`)
|
|
16
|
+
holds everything Forge owns: pipelines, schedules, prompts, connectors,
|
|
17
|
+
the sqlite DB, the encryption key, and two cache pools — `cloned-projects/`
|
|
18
|
+
and `tmp/`. The chat agent can read most of it and write to `tmp/`.
|
|
19
|
+
|
|
20
|
+
### `save_tmp_file` — write to `<dataDir>/tmp/`
|
|
21
|
+
Use case: the user says *"save this report to a file"*, *"give me a
|
|
22
|
+
downloadable copy"*, *"export the results"*. The agent already has the
|
|
23
|
+
content in context — no CLI task needed.
|
|
24
|
+
|
|
25
|
+
The file lands in `<dataDir>/tmp/<filename>`. The response includes:
|
|
26
|
+
- `path`: `tmp/<filename>` — chat auto-links this string to the in-browser
|
|
27
|
+
viewer at `/files/tmp/<filename>` (renders markdown, download button)
|
|
28
|
+
- `file_url`: `file://<abs-path>` — opens directly in Chrome on localhost,
|
|
29
|
+
or the user can right-click → Show in Finder/Explorer
|
|
30
|
+
- `local_path`: absolute filesystem path
|
|
31
|
+
|
|
32
|
+
`tmp/` is **cache-managed** — counts in the user-menu Cache panel and gets
|
|
33
|
+
swept by the janitor after `scratchRetentionDays` (default 7 days). Tell
|
|
34
|
+
users their saved file is ephemeral; if they want it permanent, ask them
|
|
35
|
+
to copy it out.
|
|
36
|
+
|
|
37
|
+
### `read_forge_file` — read anywhere under `<dataDir>/`
|
|
38
|
+
Path is dataDir-relative: `tmp/foo.md`, `scratch/report.md`, `flows/x.yaml`,
|
|
39
|
+
`prompts/y.yaml`, `connectors/mantis/manifest.yaml`, etc. Use this instead
|
|
40
|
+
of dispatching a `cat` task when the user wants to inspect a Forge-owned
|
|
41
|
+
file.
|
|
42
|
+
|
|
43
|
+
Sensitive items at the dataDir root are refused: `.encrypt-key`,
|
|
44
|
+
`.enterprise-keys.json`, any `*.db*`, `forge.log*`, `*-tokens.json`. The
|
|
45
|
+
chat agent can't accidentally leak these into chat context.
|
|
46
|
+
|
|
47
|
+
Pass `as_base64: true` for binary files (pdf, images, zip).
|
|
48
|
+
|
|
49
|
+
### `list_forge_files` — list files anywhere under `<dataDir>/`
|
|
50
|
+
Pass `dir` as a dataDir-relative subdir (`tmp`, `scratch`, `flows`,
|
|
51
|
+
`connectors/mantis`). Each entry returns `path`, `kind` (file/dir),
|
|
52
|
+
`size`, `mtime`, and a `file_url` (file:// link).
|
|
53
|
+
|
|
54
|
+
Use this for *"what files are in tmp?"* / *"list my flows"* — never
|
|
55
|
+
dispatch a `ls` task for these.
|
|
56
|
+
|
|
57
|
+
### `scratch://<path>` — connector-arg ref protocol
|
|
58
|
+
Use case: the agent wants to **upload** a Forge-owned file to a connector
|
|
59
|
+
(e.g. `onedrive.upload_file`, `teams.send_message` with an attachment).
|
|
60
|
+
Instead of reading the file into context and base64-encoding it via the
|
|
61
|
+
model (unreliable for non-ASCII, expensive for large files), pass
|
|
62
|
+
`scratch://tmp/report.md` as the connector arg's value. Forge resolves
|
|
63
|
+
the path under `<dataDir>/`, base64-encodes server-side, then dispatches.
|
|
64
|
+
|
|
65
|
+
`scratch://` is the historical name for the **ref protocol**, not the
|
|
66
|
+
`scratch/` subdir — the path after it is dataDir-relative. Bare forms
|
|
67
|
+
(`scratch/<file>`, `/api/scratch/<file>`) are also accepted for
|
|
68
|
+
back-compat. Same sensitive-file blacklist as `read_forge_file`.
|
|
69
|
+
|
|
70
|
+
## Background tasks
|
|
71
|
+
|
|
72
|
+
### `dispatch_task` — fire-and-forget a Claude CLI task
|
|
73
|
+
For longer-running asks the user wants in the background ("analyze this
|
|
74
|
+
codebase and write findings", "run the test suite and summarize"). Returns
|
|
75
|
+
a `task_id`. Pair with `get_task_status` (or `start_watch`) to follow up.
|
|
76
|
+
|
|
77
|
+
### `cancel_task` — stop a running task by id
|
|
78
|
+
Use when the user says *"停止 / cancel / kill that task"*. Safe to call
|
|
79
|
+
on already-terminal tasks (returns ok + a note). Much cleaner than
|
|
80
|
+
telling the user to open the Tasks UI.
|
|
81
|
+
|
|
82
|
+
### `get_task_status` — check a dispatched task
|
|
83
|
+
Returns `status`, `terminal`, `result_summary` (truncated to 1KB),
|
|
84
|
+
`error`, `completed_at`. For long-running polls, prefer `start_watch`
|
|
85
|
+
over manual polling — see `24-watch.md`.
|
|
86
|
+
|
|
87
|
+
## Pipelines + schedules
|
|
88
|
+
|
|
89
|
+
The chat agent owns the full schedule CRUD surface and pipeline triggers:
|
|
90
|
+
`create_schedule`, `list_schedules`, `update_schedule`, `delete_schedule`,
|
|
91
|
+
`run_schedule_now`, `trigger_pipeline`, `get_pipeline_status`. See
|
|
92
|
+
`05-pipelines.md` and `13-schedules.md` for details on what the
|
|
93
|
+
underlying objects look like.
|
|
94
|
+
|
|
95
|
+
## Stop / mid-task interjection
|
|
96
|
+
|
|
97
|
+
The user can interrupt a runaway tool-call loop at any time:
|
|
98
|
+
- **Stop button** — aborts the loop at the next iteration boundary, OR
|
|
99
|
+
between parallel tool calls (so a 5-way parallel batch can be stopped
|
|
100
|
+
mid-way). Persists a "⏹ Stopped by user." message in the thread.
|
|
101
|
+
- **Send during streaming** — typed text is queued as a **note** for the
|
|
102
|
+
running turn; the loop splices it in as a user message at the start of
|
|
103
|
+
the next iteration. The first such note carries a `[mid-task
|
|
104
|
+
interjection — …]` prefix so the model treats it as an authoritative
|
|
105
|
+
redirect, not ambient chat.
|
|
106
|
+
|
|
107
|
+
Both work on the web `/chat` page and the extension. If the user opens the
|
|
108
|
+
same chat session from two clients (e.g. extension + webchat) and posts a
|
|
109
|
+
follow-up while a turn is running, the server merges it into the running
|
|
110
|
+
loop as a note — no parallel turns on one session.
|
|
111
|
+
|
|
112
|
+
See `10-troubleshooting.md` § "Chat keeps looping" for recovery when a
|
|
113
|
+
runaway loop pre-dates Stop being available.
|
|
114
|
+
|
|
115
|
+
## Help + introspection
|
|
116
|
+
|
|
117
|
+
`list_help_docs` + `read_help_doc` give the agent access to these same
|
|
118
|
+
docs so it can answer Forge questions directly. `list_forge_context`
|
|
119
|
+
returns the current instance's projects / agent profiles / installed
|
|
120
|
+
skills — call it before passing project / agent / skill names to
|
|
121
|
+
`dispatch_task` or `trigger_pipeline` to validate them.
|
|
122
|
+
|
|
123
|
+
`add_enterprise_key` registers a pasted enterprise marketplace key
|
|
124
|
+
(`fortinet:github_pat_…`) and triggers an immediate sync — see
|
|
125
|
+
`01-settings.md` § Marketplace Providers.
|
package/lib/help-docs/CLAUDE.md
CHANGED
|
@@ -51,6 +51,7 @@ The token is valid for 24 hours. Store it in a variable and reuse for all API ca
|
|
|
51
51
|
| `18-chrome-mcp.md` | Connect Forge Claude Code sessions to a real Chrome via chrome-devtools-mcp — dev-time browser access for connector authoring |
|
|
52
52
|
| `23-automation-states.md` | Fortinet pipeline automation: GitLab MR stage labels, Mantis status flow, Teams notify policy |
|
|
53
53
|
| `24-watch.md` | Background watches — async polling of long jobs (device upgrade, Jenkins build, test run) that report back in chat. Two triggers: connectors' declarative `async` block, and the `start_watch` builtin the assistant calls on the fly. Where to see/cancel them. |
|
|
54
|
+
| `25-chat-tools.md` | Builtin tools the chat agent has by default — file access (`save_tmp_file` / `read_forge_file` / `list_forge_files`), `scratch://` connector-ref protocol, `dispatch_task` / `cancel_task`, schedules CRUD, pipelines, Stop + mid-task notes. |
|
|
54
55
|
|
|
55
56
|
## Matching questions to docs
|
|
56
57
|
|
|
@@ -86,3 +87,4 @@ The token is valid for 24 hours. Store it in a variable and reuse for all API ca
|
|
|
86
87
|
- Recipe / "From recipe" form / parameterized job → tell user: **recipes deprecated** along with Jobs; fire pipelines manually or via Schedules.
|
|
87
88
|
- Mantis bug fix / fortinet-mantis-bug-fix / open MR for Mantis bug / fortinet-mr-review / pre-review / GitLab stage labels → `23-automation-states.md` (kept Fortinet pipelines)
|
|
88
89
|
- Background watch / "watch this build" / "tell me when the upgrade is done" / progress chip / start_watch / async long job / why is the assistant polling → `24-watch.md`
|
|
90
|
+
- save_tmp_file / read_forge_file / list_forge_files / cancel_task / scratch:// ref / `<dataDir>/tmp/` / Stop / mid-task note / "what tools does the chat agent have" → `25-chat-tools.md`
|
package/lib/scratch-cleanup.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Cache-dir janitor — deletes stale files under:
|
|
3
|
+
* <dataDir>/tmp/ — chat-saved files (save_tmp_file)
|
|
4
|
+
* <dataDir>/scratch/ — legacy chat-saved location (kept for back-compat)
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Retention is settings.scratchRetentionDays (default 7).
|
|
6
|
+
* Without periodic sweeping the dirs grow forever. Retention is
|
|
7
|
+
* settings.scratchRetentionDays (default 7).
|
|
7
8
|
*
|
|
8
|
-
* Conservative scope: only
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
11
|
-
* - Skips subdirectories — could be user-created project trees we don't own
|
|
9
|
+
* Conservative scope: only top-level files. Skips dotfiles, the
|
|
10
|
+
* scratch-project marker CLAUDE.md, and subdirs (could be user-created
|
|
11
|
+
* project trees we don't own).
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { readdirSync, statSync, unlinkSync } from 'node:fs';
|
|
@@ -29,16 +29,13 @@ function retentionMs(): number {
|
|
|
29
29
|
return days * ONE_DAY_MS;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
const root = join(getDataDir(), 'scratch');
|
|
32
|
+
function sweepDir(root: string, cutoff: number, skipNames: Set<string>): { scanned: number; deleted: number; freedBytes: number } {
|
|
34
33
|
let scanned = 0, deleted = 0, freedBytes = 0;
|
|
35
|
-
const maxAge = retentionMs();
|
|
36
|
-
const cutoff = Date.now() - maxAge;
|
|
37
34
|
let entries: string[] = [];
|
|
38
35
|
try { entries = readdirSync(root); } catch { return { scanned, deleted, freedBytes }; }
|
|
39
36
|
for (const name of entries) {
|
|
40
37
|
if (name.startsWith('.')) continue;
|
|
41
|
-
if (name
|
|
38
|
+
if (skipNames.has(name)) continue;
|
|
42
39
|
const p = join(root, name);
|
|
43
40
|
let st;
|
|
44
41
|
try { st = statSync(p); } catch { continue }
|
|
@@ -48,12 +45,24 @@ export function cleanupScratch(): { scanned: number; deleted: number; freedBytes
|
|
|
48
45
|
try { unlinkSync(p); deleted++; freedBytes += st.size; }
|
|
49
46
|
catch (e) { console.warn('[scratch-cleanup] unlink failed:', p, (e as Error).message); }
|
|
50
47
|
}
|
|
51
|
-
if (deleted > 0) {
|
|
52
|
-
console.log(`[scratch-cleanup] deleted ${deleted}/${scanned} files, freed ${freedBytes} bytes`);
|
|
53
|
-
}
|
|
54
48
|
return { scanned, deleted, freedBytes };
|
|
55
49
|
}
|
|
56
50
|
|
|
51
|
+
export function cleanupScratch(): { scanned: number; deleted: number; freedBytes: number } {
|
|
52
|
+
const cutoff = Date.now() - retentionMs();
|
|
53
|
+
const tmp = sweepDir(join(getDataDir(), 'tmp'), cutoff, new Set());
|
|
54
|
+
const scratch = sweepDir(join(getDataDir(), 'scratch'), cutoff, new Set(['CLAUDE.md']));
|
|
55
|
+
const total = {
|
|
56
|
+
scanned: tmp.scanned + scratch.scanned,
|
|
57
|
+
deleted: tmp.deleted + scratch.deleted,
|
|
58
|
+
freedBytes: tmp.freedBytes + scratch.freedBytes,
|
|
59
|
+
};
|
|
60
|
+
if (total.deleted > 0) {
|
|
61
|
+
console.log(`[scratch-cleanup] deleted ${total.deleted}/${total.scanned} files (tmp=${tmp.deleted}, scratch=${scratch.deleted}), freed ${total.freedBytes} bytes`);
|
|
62
|
+
}
|
|
63
|
+
return total;
|
|
64
|
+
}
|
|
65
|
+
|
|
57
66
|
let started = false;
|
|
58
67
|
export function startScratchCleanup(): void {
|
|
59
68
|
if (started) return;
|
package/package.json
CHANGED