@aion0/forge 0.10.32 → 0.10.34

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.
@@ -16,11 +16,21 @@ Both register as `/slash-command` in Claude Code.
16
16
 
17
17
  ## Install
18
18
 
19
- 1. Go to **Skills** tab in Forge
19
+ 1. Go to **Marketplace** tab in Forge
20
20
  2. Click **Sync** to fetch latest registry
21
21
  3. Click **Install** on any skill → choose Global or specific project
22
22
  4. Use in Claude Code with `/<skill-name>`
23
23
 
24
+ ## Navigating the Marketplace
25
+
26
+ The toolbar has three group-scoped controls, ordered by usage frequency. Opening the Marketplace tab lands on **Pipelines** by default — the highest-traffic category.
27
+
28
+ - **Pipelines** (button) — Templates synced from `forge-workflow`. Default landing view.
29
+ - **Extensions ▾** — Connectors / Plugins / Crafts
30
+ - **Catalog ▾** — All / Skills / Commands / Local / Rules
31
+
32
+ The control whose value is currently active highlights in the accent color; the other two show their group label as a placeholder. Click any option to switch — no need to traverse a single long dropdown.
33
+
24
34
  ## Update
25
35
 
26
36
  Skills with newer versions show a yellow "update" indicator. Click to update (checks for local modifications first).
@@ -4,7 +4,7 @@ Forge tracks Claude API token usage and estimated costs across all your projects
4
4
 
5
5
  ## Access
6
6
 
7
- Click **Usage** in the Dashboard top navigation.
7
+ Open the **user menu** (top-right `▾`) and click **Usage**. It sits next to Monitor and Login Status — the three periodic-check screens are grouped there so the top toolbar only carries at-a-glance signals.
8
8
 
9
9
  ## Data Source
10
10
 
@@ -47,25 +47,50 @@ Both use the same background machinery; you experience them identically.
47
47
  ## `start_watch` (for the assistant)
48
48
 
49
49
  When you've just started a long job and have a tool that reports its
50
- status, register a watch instead of polling in the conversation:
50
+ status, register a watch instead of polling in the conversation. Two
51
+ poll forms are accepted:
52
+
53
+ **1. Connector tool** — `"<connector>.<tool>"`, e.g. `jenkins.get_build`:
51
54
 
52
55
  ```
53
56
  start_watch({
54
- poll: "jenkins.get_build", // <connector>.<status tool>
57
+ poll: "jenkins.get_build",
55
58
  poll_args: { job_path: "job/foo", build_number: 18 },
56
- done_match: { path: "result", equals: "SUCCESS" }, // or done_path (truthy)
59
+ done_match: { path: "result", equals: "SUCCESS" },
57
60
  interval_sec: 60, timeout_sec: 1800,
58
- message: "Build 18 finished: {poll.result}" // {poll.<path>} = latest result
61
+ message: "Build 18 finished: {poll.result}"
62
+ })
63
+ ```
64
+
65
+ **2. Bare builtin name** — Forge's own status readers:
66
+
67
+ | Builtin | Pair with | done condition |
68
+ |---|---|---|
69
+ | `get_pipeline_status` | `trigger_pipeline` → pipeline_id | `done_match={path:"status",equals:"done"}` (or `done_path:"terminal"`) |
70
+ | `get_task_status` | `dispatch_task` → task_id | `done_path:"terminal"` |
71
+
72
+ ```
73
+ start_watch({
74
+ poll: "get_pipeline_status", // bare builtin, no prefix
75
+ poll_args: { pipeline_id: "a8e049a3" },
76
+ done_path: "terminal",
77
+ message: "Pipeline {poll.workflowName} finished: {poll.status}"
59
78
  })
60
79
  ```
61
80
 
62
- Then **stop** — do not keep calling get_build in the conversation. A
63
- completion message arrives in chat; the user can cancel it from the watch
64
- list. Pick `done_match`/`done_path` from a field you saw in the status
65
- tool's output (you usually called it once already). Queue/startup errors
66
- (e.g. a build number 404 while still queued) are tolerated for a while.
67
- Guards (max polls, timeout, lifetime, active cap) keep it from running
68
- away; worst case it times out and reports that.
81
+ Then **stop** — do not keep calling the status tool in the conversation.
82
+ A completion message arrives in chat; the user can cancel it from the
83
+ watch list. Pick `done_match`/`done_path` from a field you saw in the
84
+ status tool's output (you usually called it once already). Queue/startup
85
+ errors (e.g. a build number 404 while still queued) are tolerated for a
86
+ while. Guards (max polls, timeout, lifetime, active cap) keep it from
87
+ running away; worst case it times out and reports that.
88
+
89
+ ### `_raw` fallback for non-JSON tools
90
+
91
+ The poll result is parsed as JSON. If the tool returns plain text (some
92
+ shell-protocol tools), Forge wraps it as `{_raw: "...full output..."}`
93
+ so you can still grep — e.g. `done_match={path:"_raw",contains:"DONE"}`.
69
94
 
70
95
  ## Limits
71
96
 
@@ -40,6 +40,45 @@ function parseResult(content: string): any {
40
40
  try { return JSON.parse(content); } catch { return { _raw: content }; }
41
41
  }
42
42
 
43
+ /** Heuristic: spot common "this work is finished" shapes from a poll
44
+ * result, regardless of whether the connector author thought to set
45
+ * `terminal: true` or pre-declare done conditions. Walks well-known
46
+ * state-bearing fields (state / status / phase / result / done /
47
+ * finished / complete / completed) and matches their values against
48
+ * a curated vocabulary used across CI, Jenkins, k8s, generic build
49
+ * systems, etc.
50
+ * Returns { failure } when a hit is found, null otherwise. Intended
51
+ * to run AFTER user's explicit done_match/done_path, so a caller who
52
+ * configured "done when status == running" (rare but legal) still
53
+ * wins. */
54
+ function detectTerminalState(obj: any): { failure: boolean; source: string; value: string } | null {
55
+ if (!obj || typeof obj !== 'object') return null;
56
+ // Boolean done-ish flags
57
+ for (const f of ['done', 'finished', 'complete', 'completed']) {
58
+ if (truthy(obj[f])) return { failure: false, source: f, value: 'true' };
59
+ }
60
+ // State-bearing fields with a terminal vocabulary
61
+ const fields = ['state', 'status', 'phase', 'result', 'conclusion', 'lifecycle_state'];
62
+ const failureWords = new Set([
63
+ 'failed', 'failure', 'error', 'errored', 'cancelled', 'canceled',
64
+ 'aborted', 'killed', 'terminated', 'timeout', 'timed_out', 'rejected',
65
+ 'unstable', 'broken',
66
+ ]);
67
+ const successWords = new Set([
68
+ 'done', 'success', 'succeeded', 'complete', 'completed', 'finished',
69
+ 'passed', 'ok', 'green', 'healthy',
70
+ ]);
71
+ for (const f of fields) {
72
+ const raw = obj[f];
73
+ if (raw == null) continue;
74
+ const v = String(raw).toLowerCase().trim();
75
+ if (!v) continue;
76
+ if (failureWords.has(v)) return { failure: true, source: f, value: v };
77
+ if (successWords.has(v)) return { failure: false, source: f, value: v };
78
+ }
79
+ return null;
80
+ }
81
+
43
82
  const g = globalThis as any;
44
83
 
45
84
  export function startWatchRunner(hooks: WatchRunnerHooks = {}): void {
@@ -120,7 +159,27 @@ export function startWatchRunner(hooks: WatchRunnerHooks = {}): void {
120
159
  if (w.fail_path && truthy(getPath(obj, w.fail_path))) {
121
160
  return finish(w, 'failed', obj, `${w.label}: failure condition met.`);
122
161
  }
123
- // done check
162
+ // Hard terminal check — if the poll tool itself says "this is a
163
+ // terminal state" (cancelled / failed / done / etc.), believe it
164
+ // regardless of the user-configured done condition. Without this,
165
+ // a watch on get_pipeline_status with done_match={status:"done"}
166
+ // would keep polling after the user cancels the pipeline, because
167
+ // status="cancelled" never matches "done" — wasting polls until
168
+ // max_polls / timeout. The builtin status tools (get_pipeline_status,
169
+ // get_task_status) all set obj.terminal = true on cancelled/failed
170
+ // too, so honoring it here drops the watch the moment the user
171
+ // intervenes.
172
+ if (truthy(getPath(obj, 'terminal'))) {
173
+ const statusVal = String(getPath(obj, 'status') || '').toLowerCase();
174
+ const isFailureLike = statusVal === 'failed' || statusVal === 'cancelled';
175
+ return finish(
176
+ w,
177
+ isFailureLike ? 'failed' : 'done',
178
+ obj,
179
+ `${w.label}: ${statusVal || 'reached a terminal state'}.`,
180
+ );
181
+ }
182
+ // done check (user-configured)
124
183
  let done = false;
125
184
  if (w.done_match) {
126
185
  const v = getPath(obj, w.done_match.path);
@@ -132,6 +191,22 @@ export function startWatchRunner(hooks: WatchRunnerHooks = {}): void {
132
191
  if (done) {
133
192
  return finish(w, 'done', obj, `${w.label}: done.`);
134
193
  }
194
+ // Heuristic terminal detection — fallback for connector pollers
195
+ // that don't set obj.terminal AND whose authors didn't anticipate
196
+ // a particular done condition. If the poll result has a common
197
+ // "I'm finished" shape (state/status/phase/result with a known
198
+ // terminal word, or done:true / finished:true), trust it. User's
199
+ // explicit done_match/done_path runs first (above), so a watch
200
+ // wanting "done when status==running" still works as intended.
201
+ const term = detectTerminalState(obj);
202
+ if (term) {
203
+ return finish(
204
+ w,
205
+ term.failure ? 'failed' : 'done',
206
+ obj,
207
+ `${w.label}: detected ${term.source}=${term.value} — closing watch.`,
208
+ );
209
+ }
135
210
  // not done — bound by polls / timeout, else reschedule
136
211
  if (polls >= w.max_polls || now - w.created_at > w.timeout_sec * 1000) {
137
212
  return finish(w, 'timed_out', obj, `${w.label}: not done within ${w.max_polls} polls / ${w.timeout_sec}s — please verify manually.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.10.32",
3
+ "version": "0.10.34",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {