@aion0/forge 0.10.30 → 0.10.31

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 CHANGED
@@ -1,13 +1,11 @@
1
- # Forge v0.10.30
1
+ # Forge v0.10.31
2
2
 
3
3
  Released: 2026-06-02
4
4
 
5
- ## Changes since v0.10.29
5
+ ## Changes since v0.10.30
6
6
 
7
7
  ### Other
8
- - fix(chat): tune MAX_ITERATIONS 50→24
9
- - fix(chat): raise MAX_ITERATIONS 12→50 for complex multi-step tasks
10
- - fix(chat): raise MAX_ITERATIONS 6→12 + emit sentinel when cap hit
8
+ - fix(watch): allow builtin tool names (no connector prefix) in start_watch
11
9
 
12
10
 
13
- **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.29...v0.10.30
11
+ **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.30...v0.10.31
@@ -35,12 +35,12 @@ export function buildStartWatchTool(sessionId: string | null): StartWatchTool {
35
35
  const def: BuiltinToolDef = {
36
36
  name: 'start_watch',
37
37
  description:
38
- 'Register a BACKGROUND WATCH that polls a tool until done, then posts the result back here — for long-running jobs you just kicked off (a Jenkins build, a test run, a device upgrade). Use this INSTEAD of polling in conversation: call the trigger tool, then call start_watch and STOP — Forge polls in the background and a completion message arrives in this chat. ' +
39
- 'Pick `poll` = the read tool that reports status (e.g. "jenkins.get_build") and `poll_args` to call it with (e.g. the build number you predicted via get_next_build_number). Give a done condition: `done_match` {path, equals} on the poll result (e.g. path "result" equals "SUCCESS"), or `done_path` (a result path that becomes truthy). You usually already saw the poll tool\'s output once, so you know the right field. Optional `fail_path` (truthy = failed). Tune `interval_sec`/`timeout_sec` to the job (build ≈ 60s / 1800s).',
38
+ 'Register a BACKGROUND WATCH that polls a tool until done, then posts the result back here — for long-running jobs you just kicked off (a Forge pipeline, a Jenkins build, a test run, a device upgrade). Use this INSTEAD of polling in conversation: call the trigger tool, then call start_watch and STOP — Forge polls in the background and a completion message arrives in this chat. ' +
39
+ 'Pick `poll` = the read tool that reports status. Two forms accepted: (a) connector tool "<connector>.<tool>" e.g. "jenkins.get_build", "gitlab.get_pipeline"; (b) BARE builtin name e.g. "get_pipeline_status" (Forge pipelines — pair with poll_args={pipeline_id} and done_match={path:"status",equals:"done"}). Set `poll_args` to call it with (e.g. the build number you predicted via get_next_build_number). Give a done condition: `done_match` {path, equals} on the poll result (e.g. path "result" equals "SUCCESS"), or `done_path` (a result path that becomes truthy). You usually already saw the poll tool\'s output once, so you know the right field. Optional `fail_path` (truthy = failed). Tune `interval_sec`/`timeout_sec` to the job (pipeline ≈ 30s / 1800s, build ≈ 60s / 1800s).',
40
40
  input_schema: {
41
41
  type: 'object',
42
42
  properties: {
43
- poll: { type: 'string', description: 'Tool to poll, "<connector>.<tool>" e.g. "jenkins.get_build". Must be a read/status tool.' },
43
+ poll: { type: 'string', description: 'Tool to poll. Either "<connector>.<tool>" e.g. "jenkins.get_build", OR a bare builtin name e.g. "get_pipeline_status" (for Forge pipelines). Must be a read/status tool.' },
44
44
  poll_args: { type: 'object', description: 'Args to call the poll tool with each tick, e.g. {"job_path":"job/foo","build_number":18}. Concrete values, not templates.' },
45
45
  done_match: {
46
46
  type: 'object',
@@ -68,9 +68,16 @@ export function buildStartWatchTool(sessionId: string | null): StartWatchTool {
68
68
  const a = (input ?? {}) as Record<string, any>;
69
69
  const poll = String(a.poll || '').trim();
70
70
  const dot = poll.indexOf('.');
71
- if (dot < 1) return JSON.stringify({ ok: false, error: 'poll must be "<connector>.<tool>", e.g. jenkins.get_build' });
72
- const connectorId = poll.slice(0, dot);
73
- const pollTool = poll.slice(dot + 1);
71
+ // Two forms accepted:
72
+ // "connector.tool" → connector_id="connector", poll_tool="tool"
73
+ // "tool" → builtin (e.g. get_pipeline_status), connector_id=""
74
+ // The watch-runner detects empty connector_id and dispatches as a
75
+ // bare builtin name instead of "connector.tool".
76
+ const connectorId = dot >= 1 ? poll.slice(0, dot) : '';
77
+ const pollTool = dot >= 1 ? poll.slice(dot + 1) : poll;
78
+ if (!pollTool) {
79
+ return JSON.stringify({ ok: false, error: 'poll is required, e.g. "jenkins.get_build" or builtin "get_pipeline_status"' });
80
+ }
74
81
 
75
82
  const doneMatch = a.done_match && typeof a.done_match === 'object' && a.done_match.path
76
83
  ? { path: String(a.done_match.path), ...(a.done_match.equals != null ? { equals: String(a.done_match.equals) } : {}), ...(a.done_match.contains != null ? { contains: String(a.done_match.contains) } : {}) }
@@ -83,8 +90,8 @@ export function buildStartWatchTool(sessionId: string | null): StartWatchTool {
83
90
  return JSON.stringify({ ok: false, error: `active watch limit reached (${MAX_ACTIVE_WATCHES})` });
84
91
  }
85
92
 
86
- const hint = ['build_number', 'host', 'ip', 'lab', 'id', 'name'].map((k) => a.poll_args?.[k]).find((v) => v != null && v !== '');
87
- const label = `${connectorId}.${pollTool}${hint != null ? ` ${hint}` : ''}`;
93
+ const hint = ['build_number', 'host', 'ip', 'lab', 'id', 'name', 'pipeline_id'].map((k) => a.poll_args?.[k]).find((v) => v != null && v !== '');
94
+ const label = `${connectorId ? `${connectorId}.${pollTool}` : pollTool}${hint != null ? ` ${hint}` : ''}`;
88
95
 
89
96
  const w = createWatch({
90
97
  session_id: sessionId,
@@ -93,7 +93,10 @@ export function startWatchRunner(hooks: WatchRunnerHooks = {}): void {
93
93
  // preamble — the body comes back as raw JSON so done_path/done_match can
94
94
  // see it. (Without this, every http-protocol watch would silently never
95
95
  // hit its done condition, e.g. jenkins.get_build never resolving.)
96
- res = await dispatchTool({ id: `watch-${w.id}-${w.polls}`, name: `${w.connector_id}.${w.poll_tool}`, input: w.poll_args }, { noTruncation: true } as any);
96
+ // Empty connector_id builtin (e.g. get_pipeline_status); dispatch
97
+ // bare name so dispatchTool routes to the global BUILTINS table.
98
+ const dispatchName = w.connector_id ? `${w.connector_id}.${w.poll_tool}` : w.poll_tool;
99
+ res = await dispatchTool({ id: `watch-${w.id}-${w.polls}`, name: dispatchName, input: w.poll_args }, { noTruncation: true } as any);
97
100
  } catch (e) {
98
101
  res = { content: String(e), is_error: true };
99
102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.10.30",
3
+ "version": "0.10.31",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {