aiden-runtime 4.1.0 → 4.1.1
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/README.md +13 -9
- package/dist/cli/v4/aidenCLI.js +3 -2
- package/dist/cli/v4/doctor.js +61 -1
- package/dist/cli/v4/doctorLiveness.js +329 -0
- package/dist/core/v4/skillMining/extractorPrompt.js +28 -21
- package/dist/core/v4/skillMining/proposalBuilder.js +3 -2
- package/dist/core/version.js +1 -1
- package/dist/moat/dangerousPatterns.js +1 -1
- package/dist/providers/v4/codexResponsesAdapter.js +7 -2
- package/dist/providers/v4/errors.js +51 -1
- package/dist/providers/v4/ollamaPromptToolsAdapter.js +9 -2
- package/dist/tools/v4/subagent/subagentFanout.js +24 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
<img width="1672" height="941" alt="AIDEN BOOTUP LOGO" src="https://github.com/user-attachments/assets/c0809009-73e2-4d58-9292-12fbd0324952" />
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
1
6
|
```
|
|
2
7
|
█████╗ ██╗██████╗ ███████╗███╗ ██╗
|
|
3
8
|
██╔══██╗██║██╔══██╗██╔════╝████╗ ██║
|
|
@@ -8,7 +13,7 @@
|
|
|
8
13
|
|
|
9
14
|
Autonomous AI Engine
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
72 skills · 42 tools · 19 providers · 9 channels · AGPL-3.0
|
|
12
17
|
|
|
13
18
|
Windows · Linux · WSL · macOS (API Mode)
|
|
14
19
|
|
|
@@ -16,12 +21,13 @@ Local-first · Self-healing routing · Browser & terminal control · Persistent
|
|
|
16
21
|
```
|
|
17
22
|
|
|
18
23
|
<p align="center">
|
|
19
|
-
<a href="https://github.com/taracodlabs/aiden
|
|
20
|
-
<a href="https://github.com/taracodlabs/aiden
|
|
24
|
+
<a href="https://github.com/taracodlabs/aiden/releases/latest"><img src="https://img.shields.io/github/v/release/taracodlabs/aiden?color=f97316&label=version&style=for-the-badge" alt="Latest version" /></a>
|
|
25
|
+
<a href="https://github.com/taracodlabs/aiden/releases"><img src="https://img.shields.io/github/downloads/taracodlabs/aiden/total?color=f97316&label=downloads&style=for-the-badge" alt="Downloads" /></a>
|
|
21
26
|
<a href="https://discord.gg/gMZ3hUnQTm"><img src="https://img.shields.io/badge/chat-discord-7289da?logo=discord&logoColor=white&style=for-the-badge" alt="Discord" /></a>
|
|
22
27
|
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0-orange?style=for-the-badge" alt="License: AGPL-3.0" /></a>
|
|
23
28
|
<a href="https://github.com/taracodlabs/aiden/stargazers"><img src="https://img.shields.io/github/stars/taracodlabs/aiden?style=for-the-badge&color=f9d71c" alt="Stars" /></a>
|
|
24
29
|
<a href="https://www.npmjs.com/package/aiden-runtime"><img src="https://img.shields.io/npm/v/aiden-runtime?color=f97316&label=npm&style=for-the-badge" alt="npm" /></a>
|
|
30
|
+
<a href="https://amzn.to/4tpiXwM"><img src="https://img.shields.io/badge/book-Omega-ff9900?logo=amazon&logoColor=white&style=for-the-badge" alt="Book: Omega" /></a>
|
|
25
31
|
</p>
|
|
26
32
|
|
|
27
33
|
<p align="center">
|
|
@@ -103,14 +109,14 @@ Local-first · Self-healing routing · Browser & terminal control · Persistent
|
|
|
103
109
|
<a href="https://aiden.taracod.com"><b>Website</b></a> ·
|
|
104
110
|
<a href="https://aiden.taracod.com/contact"><b>Contact</b></a> ·
|
|
105
111
|
<a href="https://discord.gg/gMZ3hUnQTm"><b>Discord</b></a> ·
|
|
106
|
-
<a href="https://github.com/taracodlabs/aiden
|
|
107
|
-
<a href="https://
|
|
112
|
+
<a href="https://github.com/taracodlabs/aiden/releases/latest"><b>Download</b></a> ·
|
|
113
|
+
<a href="https://amzn.to/4tpiXwM"><b>Book</b></a>
|
|
108
114
|
</p>
|
|
109
115
|
|
|
110
116
|
---
|
|
111
117
|
|
|
112
|
-
> **v4.1.0 —
|
|
113
|
-
>
|
|
118
|
+
> **v4.1.0 — Multi-channel autonomous AI engine**
|
|
119
|
+
> Telegram + MCP server + subagent fanout + voice CLI + skill mining. Hardened cron, structured markdown, cross-platform CI. See [changelog](#changelog) below.
|
|
114
120
|
|
|
115
121
|
---
|
|
116
122
|
|
|
@@ -326,7 +332,6 @@ We're shipping honest. Things that work, things that don't:
|
|
|
326
332
|
|
|
327
333
|
**Not in v4.0:**
|
|
328
334
|
|
|
329
|
-
- Subagent fanout / parallel agent swarm — single-loop only; deferred to v4.x
|
|
330
335
|
- OCR — not bundled (vision-loop screen capture works, but no Tesseract)
|
|
331
336
|
- Full agentskills.io ecosystem install — held pending license review
|
|
332
337
|
- Docker sandbox backend — dropped in v4 rewrite
|
|
@@ -727,7 +732,6 @@ aiden # CLI
|
|
|
727
732
|
|
|
728
733
|
- **npm package renamed** — `aiden-os` → `aiden-runtime`. Run `npm uninstall -g aiden-os && npm install -g aiden-runtime`.
|
|
729
734
|
- **Slash commands consolidated** — v3's `/switch`, `/budget`, `/memory`, `/profile`, `/permissions`, `/sandbox`, `/retry`, `/failed`, `/publish` are gone. Use `/model`, `/usage`, `/identity`, `/yolo` for equivalent functionality. See `/help` for the v4 list.
|
|
730
|
-
- **Subagent fanout removed** — v4 is single-loop only; subagent support deferred to v4.x.
|
|
731
735
|
- **Docker sandbox dropped** — `AIDEN_SANDBOX_MODE` no longer applies. Tools run on the host. The `tirithScanner` secret/PII guard, `ssrfProtection`, and tiered approval engine remain as the safety layer.
|
|
732
736
|
- **Skill registry install changed** — auto-fetch from external repos held pending license review. Skills install via `/skills install <local-path-or-url>` only at v4.0.
|
|
733
737
|
- **Config compatible** — most environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GROQ_API_KEY`, etc.) are recognised as-is. Copy your existing `.env` and Aiden picks them up.
|
package/dist/cli/v4/aidenCLI.js
CHANGED
|
@@ -282,12 +282,13 @@ async function main(argv, opts = {}) {
|
|
|
282
282
|
program
|
|
283
283
|
.command('doctor')
|
|
284
284
|
.description('Run diagnostic checks')
|
|
285
|
-
.
|
|
285
|
+
.option('--providers', 'Also ping each configured / authed provider and report live status (deep check). Slower; useful before shipping or when a provider regression is suspected.')
|
|
286
|
+
.action(async (cmdOpts) => {
|
|
286
287
|
if (opts.runDoctorHook) {
|
|
287
288
|
await opts.runDoctorHook();
|
|
288
289
|
return;
|
|
289
290
|
}
|
|
290
|
-
await (0, doctor_1.runDoctorCli)();
|
|
291
|
+
await (0, doctor_1.runDoctorCli)({ liveness: cmdOpts.providers === true });
|
|
291
292
|
});
|
|
292
293
|
program
|
|
293
294
|
.command('sessions <action> [arg]')
|
package/dist/cli/v4/doctor.js
CHANGED
|
@@ -18,6 +18,39 @@
|
|
|
18
18
|
* will normally be sub-second.
|
|
19
19
|
*
|
|
20
20
|
*/
|
|
21
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
24
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
25
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
26
|
+
}
|
|
27
|
+
Object.defineProperty(o, k2, desc);
|
|
28
|
+
}) : (function(o, m, k, k2) {
|
|
29
|
+
if (k2 === undefined) k2 = k;
|
|
30
|
+
o[k2] = m[k];
|
|
31
|
+
}));
|
|
32
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
33
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
34
|
+
}) : function(o, v) {
|
|
35
|
+
o["default"] = v;
|
|
36
|
+
});
|
|
37
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
38
|
+
var ownKeys = function(o) {
|
|
39
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
40
|
+
var ar = [];
|
|
41
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
42
|
+
return ar;
|
|
43
|
+
};
|
|
44
|
+
return ownKeys(o);
|
|
45
|
+
};
|
|
46
|
+
return function (mod) {
|
|
47
|
+
if (mod && mod.__esModule) return mod;
|
|
48
|
+
var result = {};
|
|
49
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
50
|
+
__setModuleDefault(result, mod);
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
})();
|
|
21
54
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
55
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
56
|
};
|
|
@@ -760,6 +793,33 @@ async function runDoctorCli(opts) {
|
|
|
760
793
|
}
|
|
761
794
|
}
|
|
762
795
|
process.stdout.write(`\n${report.passed ? 'all checks passed' : 'some checks failed'} in ${report.totalMs} ms\n`);
|
|
763
|
-
|
|
796
|
+
// Phase v4.1.1-oauth-fix Phase 5: discoverability hint for the deep
|
|
797
|
+
// mode. Only emitted in standard mode — if the user already passed
|
|
798
|
+
// `--providers`, the section right below this is the answer to the hint.
|
|
799
|
+
if (!opts?.liveness) {
|
|
800
|
+
process.stdout.write(' hint: Run `aiden doctor --providers` for live provider checks\n');
|
|
801
|
+
}
|
|
802
|
+
// Phase v4.1.1-oauth-fix Phase 4: opt-in provider liveness.
|
|
803
|
+
// Runs after the standard report so a failed config check is visible
|
|
804
|
+
// before the network probes start. Section header + summary line are
|
|
805
|
+
// rendered by doctorLiveness.renderProviderLivenessSection so the
|
|
806
|
+
// formatting stays alongside its consumer.
|
|
807
|
+
let livenessFailed = false;
|
|
808
|
+
if (opts?.liveness) {
|
|
809
|
+
process.stdout.write('\n Running provider liveness checks...\n');
|
|
810
|
+
const { runProviderLiveness, renderProviderLivenessSection } = await Promise.resolve().then(() => __importStar(require('./doctorLiveness')));
|
|
811
|
+
const paths = opts.paths ?? (0, paths_1.resolveAidenPaths)();
|
|
812
|
+
const { results, summary } = await runProviderLiveness({
|
|
813
|
+
paths,
|
|
814
|
+
env: opts.env,
|
|
815
|
+
fetchImpl: opts.fetchImpl,
|
|
816
|
+
timeoutMs: opts.livenessTimeoutMs,
|
|
817
|
+
});
|
|
818
|
+
process.stdout.write(renderProviderLivenessSection(results, summary));
|
|
819
|
+
livenessFailed = summary.red > 0;
|
|
820
|
+
}
|
|
821
|
+
// Liveness reds count toward the overall exit code so CI / scripts
|
|
822
|
+
// can `aiden doctor --providers && deploy`.
|
|
823
|
+
process.exitCode = (report.passed && !livenessFailed) ? 0 : 1;
|
|
764
824
|
return report;
|
|
765
825
|
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* cli/v4/doctorLiveness.ts — Phase v4.1.1-oauth-fix Phase 4.
|
|
10
|
+
*
|
|
11
|
+
* `aiden doctor --providers` deep-mode helper. Pings every configured /
|
|
12
|
+
* authed provider with a minimal request, reports green / red / skipped
|
|
13
|
+
* + per-provider latency + verbatim upstream error message.
|
|
14
|
+
*
|
|
15
|
+
* Why a separate file:
|
|
16
|
+
* - Default doctor (`aiden doctor` with no flag) stays unchanged and
|
|
17
|
+
* fast — config-shape checks only.
|
|
18
|
+
* - `--providers` is opt-in. When the user types it we extend the
|
|
19
|
+
* report with one liveness row per probe, then render a summary
|
|
20
|
+
* line at the bottom.
|
|
21
|
+
* - Tool-catalog validation is deliberately OUT of scope. Liveness
|
|
22
|
+
* probes ship `tools: []` (see comment in checkProviderLiveness)
|
|
23
|
+
* so one bad tool schema doesn't false-red every provider that
|
|
24
|
+
* validates strictly. The eval-harness / registration-time schema
|
|
25
|
+
* validator (v4.1.1 main) is the right home for that concern.
|
|
26
|
+
*
|
|
27
|
+
* Trust artifact:
|
|
28
|
+
* - On failure we surface `err.message` VERBATIM (truncated to 200
|
|
29
|
+
* chars). Phase 3 (`providers/v4/errors.ts`) already composes the
|
|
30
|
+
* upstream response body into ProviderError.message — so a 400
|
|
31
|
+
* prints the actual OpenAI reason, not a generic "provider failed."
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.enumerateConfiguredProviders = enumerateConfiguredProviders;
|
|
35
|
+
exports.checkProviderLiveness = checkProviderLiveness;
|
|
36
|
+
exports.runProviderLiveness = runProviderLiveness;
|
|
37
|
+
exports.renderProviderLivenessSection = renderProviderLivenessSection;
|
|
38
|
+
const registry_1 = require("../../providers/v4/registry");
|
|
39
|
+
const runtimeResolver_1 = require("../../providers/v4/runtimeResolver");
|
|
40
|
+
const credentialResolver_1 = require("../../providers/v4/credentialResolver");
|
|
41
|
+
const tokenStore_1 = require("../../core/v4/auth/tokenStore");
|
|
42
|
+
const DEFAULT_LIVENESS_TIMEOUT_MS = 8000;
|
|
43
|
+
const PROBE_MAX_TOKENS = 4;
|
|
44
|
+
const ERROR_TRUNCATE_CHARS = 200;
|
|
45
|
+
const OLLAMA_PROBE_TIMEOUT_MS = 1500;
|
|
46
|
+
const OLLAMA_HEALTH_URL = 'http://127.0.0.1:11434/api/tags';
|
|
47
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* Truncate a long error string for display. The full error remains on
|
|
50
|
+
* the in-memory `LivenessResult.error` for programmatic consumers /
|
|
51
|
+
* test assertions.
|
|
52
|
+
*/
|
|
53
|
+
function truncate(s, max = ERROR_TRUNCATE_CHARS) {
|
|
54
|
+
if (s.length <= max)
|
|
55
|
+
return s;
|
|
56
|
+
return `${s.slice(0, max - 1)}…`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Wrap a promise with a hard timeout. Resolves to the inner result on
|
|
60
|
+
* success, throws a clearly-labelled `Error` on timeout. Cleans up the
|
|
61
|
+
* timer either way.
|
|
62
|
+
*/
|
|
63
|
+
async function withTimeout(p, ms, label) {
|
|
64
|
+
let timer;
|
|
65
|
+
try {
|
|
66
|
+
return await Promise.race([
|
|
67
|
+
p,
|
|
68
|
+
new Promise((_, reject) => {
|
|
69
|
+
timer = setTimeout(() => reject(new Error(`${label}: timeout after ${ms}ms`)), ms);
|
|
70
|
+
}),
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
if (timer)
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Decide whether a provider counts as "configured" for liveness purposes.
|
|
80
|
+
* Three paths:
|
|
81
|
+
* 1. API key in env (`apiKeyEnvVar` set and the env var is non-empty).
|
|
82
|
+
* 2. OAuth (entry.oauth present and a non-expired token sits in the
|
|
83
|
+
* tokenStore at <paths.root>/auth/<providerId>.json).
|
|
84
|
+
* 3. Local providers (ollama) — probed via a quick HTTP health check.
|
|
85
|
+
*
|
|
86
|
+
* Anything else is `configured: false` and gets a `skip_reason`.
|
|
87
|
+
*/
|
|
88
|
+
async function enumerateConfiguredProviders(opts) {
|
|
89
|
+
const env = opts.env ?? process.env;
|
|
90
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const entry of Object.values(registry_1.PROVIDER_REGISTRY)) {
|
|
93
|
+
// Every provider needs at least one model to probe against.
|
|
94
|
+
const model = entry.modelIds[0];
|
|
95
|
+
if (!model) {
|
|
96
|
+
out.push({
|
|
97
|
+
entry,
|
|
98
|
+
model: '',
|
|
99
|
+
configured: false,
|
|
100
|
+
reason: 'no models declared in registry',
|
|
101
|
+
});
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
// 1. API-key providers.
|
|
105
|
+
if (entry.apiKeyEnvVar) {
|
|
106
|
+
const value = env[entry.apiKeyEnvVar];
|
|
107
|
+
if (value && value.length > 0) {
|
|
108
|
+
out.push({ entry, model, configured: true });
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
out.push({
|
|
112
|
+
entry,
|
|
113
|
+
model,
|
|
114
|
+
configured: false,
|
|
115
|
+
reason: `env ${entry.apiKeyEnvVar} not set`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
// 2. OAuth providers — check tokenStore.
|
|
121
|
+
if (entry.oauth) {
|
|
122
|
+
try {
|
|
123
|
+
const tokens = await (0, tokenStore_1.loadTokens)(opts.paths, entry.oauth.providerId);
|
|
124
|
+
if (tokens && tokens.accessToken) {
|
|
125
|
+
if ((0, tokenStore_1.isExpired)(tokens, tokenStore_1.PREFLIGHT_REFRESH_WINDOW_MS)) {
|
|
126
|
+
out.push({
|
|
127
|
+
entry,
|
|
128
|
+
model,
|
|
129
|
+
configured: false,
|
|
130
|
+
reason: 'OAuth token expired — run `/auth refresh` or `/auth login`',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
out.push({ entry, model, configured: true });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
out.push({
|
|
139
|
+
entry,
|
|
140
|
+
model,
|
|
141
|
+
configured: false,
|
|
142
|
+
reason: 'no OAuth token — run `/auth login`',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
out.push({
|
|
148
|
+
entry,
|
|
149
|
+
model,
|
|
150
|
+
configured: false,
|
|
151
|
+
reason: `tokenStore read failed: ${err.message}`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
// 3. Local / no-credential providers (ollama). Configured = the
|
|
157
|
+
// local daemon answers a health probe.
|
|
158
|
+
const controller = new AbortController();
|
|
159
|
+
const timer = setTimeout(() => controller.abort(), OLLAMA_PROBE_TIMEOUT_MS);
|
|
160
|
+
try {
|
|
161
|
+
const res = await fetchImpl(OLLAMA_HEALTH_URL, { signal: controller.signal });
|
|
162
|
+
if (res.ok) {
|
|
163
|
+
out.push({ entry, model, configured: true });
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
out.push({
|
|
167
|
+
entry,
|
|
168
|
+
model,
|
|
169
|
+
configured: false,
|
|
170
|
+
reason: `local daemon HTTP ${res.status}`,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
out.push({
|
|
176
|
+
entry,
|
|
177
|
+
model,
|
|
178
|
+
configured: false,
|
|
179
|
+
reason: `not running on ${OLLAMA_HEALTH_URL}`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
clearTimeout(timer);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Probe one provider/model pair via a minimal `adapter.call()`. Returns
|
|
190
|
+
* a structured result; never throws.
|
|
191
|
+
*
|
|
192
|
+
* On failure, `result.error` is `err.message` verbatim truncated to
|
|
193
|
+
* 200 chars. Phase v4.1.1-oauth-fix Phase 3 made `ProviderError.message`
|
|
194
|
+
* carry the upstream response body, so a 400 surfaces the actual
|
|
195
|
+
* reason (e.g. "Invalid schema for function 'subagent_fanout': …").
|
|
196
|
+
*/
|
|
197
|
+
async function checkProviderLiveness(provider, model, adapter, opts) {
|
|
198
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_LIVENESS_TIMEOUT_MS;
|
|
199
|
+
const start = Date.now();
|
|
200
|
+
// Liveness probes "is this provider reachable + authenticated?".
|
|
201
|
+
// Tool-catalog validation is a separate concern (eval harness,
|
|
202
|
+
// v4.1.1 main). Sending tools: [] ensures one bad tool schema
|
|
203
|
+
// doesn't false-red every provider that validates strictly.
|
|
204
|
+
const input = {
|
|
205
|
+
messages: [{ role: 'user', content: 'ping' }],
|
|
206
|
+
tools: [],
|
|
207
|
+
maxTokens: PROBE_MAX_TOKENS,
|
|
208
|
+
};
|
|
209
|
+
try {
|
|
210
|
+
await withTimeout(adapter.call(input), timeoutMs, `liveness ${provider}`);
|
|
211
|
+
return {
|
|
212
|
+
provider,
|
|
213
|
+
model,
|
|
214
|
+
status: 'green',
|
|
215
|
+
latency_ms: Date.now() - start,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
220
|
+
return {
|
|
221
|
+
provider,
|
|
222
|
+
model,
|
|
223
|
+
status: 'red',
|
|
224
|
+
latency_ms: Date.now() - start,
|
|
225
|
+
error: truncate(raw),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Run liveness probes against every provider returned by
|
|
231
|
+
* `enumerateConfiguredProviders`. Unconfigured providers come back as
|
|
232
|
+
* `status: 'skipped'` without any network traffic.
|
|
233
|
+
*
|
|
234
|
+
* Returns `{ results, summary }`. Summary tallies + a wall-clock total
|
|
235
|
+
* so the UI can print "X green · Y red · Z skipped · NNNms total".
|
|
236
|
+
*/
|
|
237
|
+
async function runProviderLiveness(opts) {
|
|
238
|
+
const start = Date.now();
|
|
239
|
+
const parallel = opts.parallel ?? true;
|
|
240
|
+
const resolver = opts.resolverImpl ?? new runtimeResolver_1.RuntimeResolver(new credentialResolver_1.CredentialResolver(opts.paths.authJson));
|
|
241
|
+
const configured = await enumerateConfiguredProviders({
|
|
242
|
+
paths: opts.paths,
|
|
243
|
+
env: opts.env,
|
|
244
|
+
fetchImpl: opts.fetchImpl,
|
|
245
|
+
});
|
|
246
|
+
const probe = async (c) => {
|
|
247
|
+
if (!c.configured) {
|
|
248
|
+
return {
|
|
249
|
+
provider: c.entry.id,
|
|
250
|
+
status: 'skipped',
|
|
251
|
+
latency_ms: 0,
|
|
252
|
+
skip_reason: c.reason ?? 'not configured',
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
const adapter = await resolver.resolve({
|
|
257
|
+
providerId: c.entry.id,
|
|
258
|
+
modelId: c.model,
|
|
259
|
+
paths: opts.paths,
|
|
260
|
+
});
|
|
261
|
+
return await checkProviderLiveness(c.entry.id, c.model, adapter, { timeoutMs: opts.timeoutMs });
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
// Resolve failure (missing credential, unknown model, etc.).
|
|
265
|
+
// Same surface treatment as a probe failure.
|
|
266
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
267
|
+
return {
|
|
268
|
+
provider: c.entry.id,
|
|
269
|
+
model: c.model,
|
|
270
|
+
status: 'red',
|
|
271
|
+
latency_ms: 0,
|
|
272
|
+
error: truncate(raw),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const results = parallel
|
|
277
|
+
? await Promise.all(configured.map(probe))
|
|
278
|
+
: await runSequential(configured.map((c) => () => probe(c)));
|
|
279
|
+
const summary = {
|
|
280
|
+
green: results.filter((r) => r.status === 'green').length,
|
|
281
|
+
red: results.filter((r) => r.status === 'red').length,
|
|
282
|
+
skipped: results.filter((r) => r.status === 'skipped').length,
|
|
283
|
+
total_ms: Date.now() - start,
|
|
284
|
+
};
|
|
285
|
+
return { results, summary };
|
|
286
|
+
}
|
|
287
|
+
async function runSequential(thunks) {
|
|
288
|
+
const out = [];
|
|
289
|
+
for (const t of thunks)
|
|
290
|
+
out.push(await t());
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Render the liveness section as plain-text rows. The doctor command
|
|
295
|
+
* prints this BELOW the standard health box so the default `aiden doctor`
|
|
296
|
+
* output stays byte-identical.
|
|
297
|
+
*
|
|
298
|
+
* Visual style matches the existing doctor rows (✓ / ✗ / -) but lays
|
|
299
|
+
* out as a tabular block rather than a box — the rows can be long
|
|
300
|
+
* (upstream error bodies) and forcing them into the 100-col box would
|
|
301
|
+
* truncate the diagnostic that's the whole point of the feature.
|
|
302
|
+
*/
|
|
303
|
+
function renderProviderLivenessSection(results, summary) {
|
|
304
|
+
const nameWidth = Math.max(8, ...results.map((r) => r.provider.length));
|
|
305
|
+
const lines = [];
|
|
306
|
+
lines.push('');
|
|
307
|
+
lines.push(' Provider liveness (deep check)');
|
|
308
|
+
lines.push(` ${'─'.repeat(60)}`);
|
|
309
|
+
for (const r of results) {
|
|
310
|
+
const icon = r.status === 'green' ? '✓'
|
|
311
|
+
: r.status === 'red' ? '✗'
|
|
312
|
+
: '-';
|
|
313
|
+
const name = r.provider.padEnd(nameWidth);
|
|
314
|
+
const status = r.status === 'green' ? 'green'.padEnd(8)
|
|
315
|
+
: r.status === 'red' ? 'red '.padEnd(8)
|
|
316
|
+
: 'skip '.padEnd(8);
|
|
317
|
+
const latency = r.latency_ms > 0
|
|
318
|
+
? `${r.latency_ms}ms`.padEnd(8)
|
|
319
|
+
: ''.padEnd(8);
|
|
320
|
+
const tail = r.status === 'green' ? (r.model ?? '')
|
|
321
|
+
: r.status === 'red' ? (r.error ?? 'unknown error')
|
|
322
|
+
: (r.skip_reason ?? 'not configured');
|
|
323
|
+
lines.push(` ${icon} ${name} ${status}${latency}${tail}`);
|
|
324
|
+
}
|
|
325
|
+
lines.push(` ${'─'.repeat(60)}`);
|
|
326
|
+
lines.push(` ${summary.green} green · ${summary.red} red · ${summary.skipped} skipped · ${summary.total_ms}ms total`);
|
|
327
|
+
lines.push('');
|
|
328
|
+
return lines.join('\n');
|
|
329
|
+
}
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
* metadata.aiden.* must round-trip unchanged. We re-parse the
|
|
17
17
|
* refined output and discard it if any required field drifts.
|
|
18
18
|
*
|
|
19
|
-
* 2. Never write attribution tokens
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* 2. Never write attribution tokens or "portions adapted from..." /
|
|
20
|
+
* "original copyright" strings. The permanent attribution
|
|
21
|
+
* sweep validates this; if a refined output contains any
|
|
22
|
+
* forbidden token, we fall back to the skeleton.
|
|
23
23
|
*
|
|
24
24
|
* If the auxiliary client is unavailable, the call times out, or
|
|
25
25
|
* the refined output fails validation, the function returns the
|
|
@@ -29,23 +29,30 @@
|
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
30
|
exports.refine = refine;
|
|
31
31
|
const skillSpec_1 = require("../skillSpec");
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
const BANNED_TOKENS = [
|
|
33
|
+
'portions adapted from',
|
|
34
|
+
'original copyright',
|
|
35
|
+
'derived from',
|
|
36
|
+
'based on the',
|
|
37
|
+
'adapted from',
|
|
38
|
+
];
|
|
39
|
+
const FORBIDDEN_TOKENS_RE = new RegExp(`\\b(${BANNED_TOKENS.join('|')})\\b`, 'i');
|
|
40
|
+
const REFINER_SYSTEM_PROMPT = `
|
|
41
|
+
You polish auto-generated skill markdown for a local-first AI agent.
|
|
42
|
+
|
|
43
|
+
Your job is to improve the WORDING ONLY of an already-valid SKILL.md
|
|
44
|
+
file. The frontmatter (everything between the leading "---" markers)
|
|
45
|
+
must round-trip BYTE-FOR-BYTE unchanged. The "# <name>" heading must
|
|
46
|
+
stay first in the body. The numbered "## Steps" list must remain
|
|
47
|
+
numbered and in the same order; you may rephrase step descriptions
|
|
48
|
+
but must NOT add or remove steps.
|
|
49
|
+
|
|
50
|
+
Hard rules:
|
|
51
|
+
- Output the COMPLETE SKILL.md, not a diff.
|
|
52
|
+
- Do not add boilerplate, citations, or attribution to any other
|
|
53
|
+
agent or codebase. The skill is 100% the user's own.
|
|
54
|
+
- Do not introduce mock/fake values into commands.
|
|
55
|
+
- Keep total length under 6000 characters.
|
|
49
56
|
`.trim();
|
|
50
57
|
/**
|
|
51
58
|
* Refine `skeleton` via the auxiliary client. Always returns a
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
* - emits required fields `name`, `description`, `version`
|
|
19
19
|
* - emits `metadata.aiden` with the mining provenance fields
|
|
20
20
|
* - body is numbered tool-call steps in markdown
|
|
21
|
-
* - never writes
|
|
22
|
-
*
|
|
21
|
+
* - never writes attribution strings — the banned-token regex
|
|
22
|
+
* strips them at extraction time and the permanent attribution
|
|
23
|
+
* sweep validates the result
|
|
23
24
|
*/
|
|
24
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
26
|
exports.deriveName = deriveName;
|
package/dist/core/version.js
CHANGED
|
@@ -52,7 +52,7 @@ exports.DANGEROUS_PATTERNS = [
|
|
|
52
52
|
{ name: 'kill_all', regex: /\bkill\s+-9\s+-1\b/, tier: 'dangerous', description: 'kill all processes' },
|
|
53
53
|
{ name: 'pkill_force', regex: /\bpkill\s+-9\b/, tier: 'caution', description: 'force kill processes' },
|
|
54
54
|
{ name: 'systemctl_disable', regex: /\bsystemctl\s+(-[^\s]+\s+)*(stop|restart|disable|mask)\b/i, tier: 'caution', description: 'stop/restart system service' },
|
|
55
|
-
{ name: 'pkill_aiden', regex: /\b(pkill|killall)\b.*\b(aiden|gateway
|
|
55
|
+
{ name: 'pkill_aiden', regex: /\b(pkill|killall)\b.*\b(aiden|gateway)\b/i, tier: 'dangerous', description: 'kill aiden/gateway process (self-termination)' },
|
|
56
56
|
// ── Sensitive write targets ───────────────────────────────────
|
|
57
57
|
{ name: 'write_etc', regex: />\s*\/etc\//, tier: 'dangerous', description: 'overwrite system config' },
|
|
58
58
|
{ name: 'tee_etc', regex: /\btee\b.*\/etc\//, tier: 'dangerous', description: 'overwrite system file via tee' },
|
|
@@ -129,14 +129,19 @@ class CodexResponsesAdapter {
|
|
|
129
129
|
const body = {
|
|
130
130
|
model: this.model,
|
|
131
131
|
input: items,
|
|
132
|
-
tool_choice: 'auto',
|
|
133
|
-
parallel_tool_calls: true,
|
|
134
132
|
store: false,
|
|
135
133
|
};
|
|
136
134
|
if (instructions)
|
|
137
135
|
body.instructions = instructions;
|
|
136
|
+
// Phase v4.1.1-oauth-fix Phase 5: `tool_choice` and
|
|
137
|
+
// `parallel_tool_calls` are only meaningful when tools are present.
|
|
138
|
+
// OpenAI Codex returns HTTP 400 (empty body) for `tool_choice: 'auto'`
|
|
139
|
+
// without a `tools` field — surfaced by `aiden doctor --providers`'s
|
|
140
|
+
// no-tools liveness probe.
|
|
138
141
|
if (input.tools && input.tools.length > 0) {
|
|
139
142
|
body.tools = input.tools.map(toWireTool);
|
|
143
|
+
body.tool_choice = 'auto';
|
|
144
|
+
body.parallel_tool_calls = true;
|
|
140
145
|
}
|
|
141
146
|
if (typeof input.temperature === 'number') {
|
|
142
147
|
body.temperature = input.temperature;
|
|
@@ -16,9 +16,59 @@
|
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.ProviderRateLimitError = exports.ProviderTimeoutError = exports.ProviderError = void 0;
|
|
19
|
+
exports.formatRawForMessage = formatRawForMessage;
|
|
20
|
+
/**
|
|
21
|
+
* Format a raw response body for inclusion in the user-facing error
|
|
22
|
+
* message. Recognises the OpenAI / Anthropic JSON envelope shape
|
|
23
|
+
* (`{ error: { message: "..." } }`) and falls back to the raw string
|
|
24
|
+
* for plain-text bodies. Returns null when nothing useful is available
|
|
25
|
+
* so callers can omit the ": <detail>" tail entirely.
|
|
26
|
+
*
|
|
27
|
+
* Truncates to 300 chars to keep multi-line responses from blowing
|
|
28
|
+
* up the user's terminal — full body remains on `error.raw` for
|
|
29
|
+
* programmatic consumers / `aiden doctor --providers` deep mode.
|
|
30
|
+
*/
|
|
31
|
+
function formatRawForMessage(raw) {
|
|
32
|
+
if (raw === undefined || raw === null)
|
|
33
|
+
return null;
|
|
34
|
+
// OpenAI / Anthropic JSON envelope: { error: { message: "..." } }
|
|
35
|
+
if (typeof raw === 'object') {
|
|
36
|
+
const err = raw.error;
|
|
37
|
+
if (err && typeof err === 'object') {
|
|
38
|
+
const msg = err.message;
|
|
39
|
+
if (typeof msg === 'string' && msg.length > 0) {
|
|
40
|
+
return msg.length > 300 ? `${msg.slice(0, 300)}…` : msg;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Some providers put the message at the top level.
|
|
44
|
+
const topMsg = raw.message;
|
|
45
|
+
if (typeof topMsg === 'string' && topMsg.length > 0) {
|
|
46
|
+
return topMsg.length > 300 ? `${topMsg.slice(0, 300)}…` : topMsg;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// Plain string body.
|
|
51
|
+
if (typeof raw === 'string') {
|
|
52
|
+
const trimmed = raw.trim();
|
|
53
|
+
if (trimmed.length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
return trimmed.length > 300 ? `${trimmed.slice(0, 300)}…` : trimmed;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Compose the final `Error.message` from the short summary and (when
|
|
61
|
+
* available) the parsed/truncated raw response body. The body remains
|
|
62
|
+
* stashed on `ProviderError.raw` either way — this only enriches what
|
|
63
|
+
* users see when the error is rendered.
|
|
64
|
+
*/
|
|
65
|
+
function composeMessage(message, raw) {
|
|
66
|
+
const tail = formatRawForMessage(raw);
|
|
67
|
+
return tail ? `${message}: ${tail}` : message;
|
|
68
|
+
}
|
|
19
69
|
class ProviderError extends Error {
|
|
20
70
|
constructor(message, providerName, statusCode, raw, retryable = false) {
|
|
21
|
-
super(message);
|
|
71
|
+
super(composeMessage(message, raw));
|
|
22
72
|
this.providerName = providerName;
|
|
23
73
|
this.statusCode = statusCode;
|
|
24
74
|
this.raw = raw;
|
|
@@ -71,7 +71,12 @@ class OllamaPromptToolsAdapter {
|
|
|
71
71
|
const status = response.status;
|
|
72
72
|
const rawText = await this.safeReadText(response);
|
|
73
73
|
const retryable = status >= 500 || status === 429;
|
|
74
|
-
|
|
74
|
+
// Phase v4.1.1-oauth-fix Phase 5: short message only. The raw
|
|
75
|
+
// body flows via the .raw arg and composeMessage in errors.ts
|
|
76
|
+
// appends a truncated summary into the final .message — this
|
|
77
|
+
// file used to inline it, producing duplicated output in
|
|
78
|
+
// `aiden doctor --providers` and anywhere else err.message is logged.
|
|
79
|
+
const err = new errors_1.ProviderError(`Provider ${this.providerName} returned ${status}`, this.providerName, status, rawText, retryable);
|
|
75
80
|
if (!retryable || attempt >= totalAttempts)
|
|
76
81
|
throw err;
|
|
77
82
|
lastError = err;
|
|
@@ -149,7 +154,9 @@ class OllamaPromptToolsAdapter {
|
|
|
149
154
|
clearTimeout(timer);
|
|
150
155
|
const status = response.status;
|
|
151
156
|
const rawText = await this.safeReadText(response);
|
|
152
|
-
|
|
157
|
+
// Phase v4.1.1-oauth-fix Phase 5: composeMessage handles body
|
|
158
|
+
// rendering centrally; inlining it here would duplicate.
|
|
159
|
+
throw new errors_1.ProviderError(`Provider ${this.providerName} returned ${status}`, this.providerName, status, rawText, status >= 500);
|
|
153
160
|
}
|
|
154
161
|
if (!response.body) {
|
|
155
162
|
clearTimeout(timer);
|
|
@@ -73,6 +73,30 @@ function makeSubagentFanoutTool(factory) {
|
|
|
73
73
|
tasks: {
|
|
74
74
|
type: 'array',
|
|
75
75
|
description: 'Per-child task list (partition mode only). Length must equal n.',
|
|
76
|
+
// Schema mirrors PartitionTask interface in
|
|
77
|
+
// core/v4/subagent/fanout.ts:70-75. If you change one, change
|
|
78
|
+
// the other. OpenAI Codex backend strictly validates schemas
|
|
79
|
+
// and rejects `type: "array"` declarations missing `items`,
|
|
80
|
+
// so the inner shape must be explicit here.
|
|
81
|
+
items: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
description: 'One unit of work for a partition-mode child.',
|
|
84
|
+
properties: {
|
|
85
|
+
goal: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
description: 'The task this child should accomplish.',
|
|
88
|
+
},
|
|
89
|
+
context: {
|
|
90
|
+
type: 'string',
|
|
91
|
+
description: 'Optional shared context for the child.',
|
|
92
|
+
},
|
|
93
|
+
role: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
description: 'Optional role tag, diagnostic only.',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
required: ['goal'],
|
|
99
|
+
},
|
|
76
100
|
},
|
|
77
101
|
merge: {
|
|
78
102
|
type: 'string',
|