aiden-runtime 4.1.2 → 4.1.3
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/dist/cli/v4/aidenCLI.js +10 -0
- package/dist/cli/v4/callbacks.js +15 -0
- package/dist/cli/v4/chatSession.js +120 -2
- package/dist/cli/v4/commands/doctor.js +23 -27
- package/dist/cli/v4/commands/model.js +30 -1
- package/dist/cli/v4/display/capabilityCard.js +135 -0
- package/dist/cli/v4/display/sessionEndCard.js +127 -0
- package/dist/cli/v4/display/toolTrail.js +172 -0
- package/dist/cli/v4/display.js +464 -132
- package/dist/cli/v4/doctor.js +377 -75
- package/dist/cli/v4/promotionPrompt.js +135 -5
- package/dist/cli/v4/replyRenderer.js +311 -20
- package/dist/cli/v4/skinEngine.js +14 -3
- package/dist/cli/v4/toolPreview.js +14 -0
- package/dist/core/tools/nowPlaying.js +7 -15
- package/dist/core/v4/sessionDistiller.js +48 -1
- package/dist/core/v4/toolRegistry.js +16 -1
- package/dist/core/version.js +1 -1
- package/dist/moat/plannerGuard.js +19 -0
- package/dist/providers/v4/errors.js +92 -0
- package/dist/tools/v4/index.js +24 -1
- package/dist/tools/v4/sessions/recallSession.js +14 -0
- package/dist/tools/v4/system/_psHelpers.js +70 -2
- package/dist/tools/v4/system/appInput.js +154 -0
- package/dist/tools/v4/system/appLaunch.js +136 -10
- package/dist/tools/v4/system/mediaKey.js +35 -4
- package/dist/tools/v4/system/mediaSessions.js +163 -0
- package/dist/tools/v4/system/mediaTransport.js +211 -0
- package/package.json +1 -1
- package/skills/system_control.md +56 -6
|
@@ -192,7 +192,19 @@ function recoverBullets(raw) {
|
|
|
192
192
|
}
|
|
193
193
|
return [];
|
|
194
194
|
}
|
|
195
|
-
|
|
195
|
+
/**
|
|
196
|
+
* v4.1.3-essentials distillation-fix: default raised from 4000ms to
|
|
197
|
+
* 12000ms after visual smoke showed chatgpt-plus Codex regularly
|
|
198
|
+
* exceeded the original budget for 800-token summaries on
|
|
199
|
+
* cold-start. Symptom: every `/quit` distillation returned
|
|
200
|
+
* `partial:true` with empty bullets/decisions/open_items, killing
|
|
201
|
+
* both the MEMORY.md update path AND the promotion prompt.
|
|
202
|
+
*
|
|
203
|
+
* 12s gives comfortable headroom while still aborting genuinely
|
|
204
|
+
* stuck calls. Power users can override via `AIDEN_SUMMARY_TIMEOUT_MS`
|
|
205
|
+
* env var (consumed by `resolveSummaryTimeoutMs()` in chatSession).
|
|
206
|
+
*/
|
|
207
|
+
const DEFAULT_TIMEOUT_MS = 12000;
|
|
196
208
|
/**
|
|
197
209
|
* Phase v4.1.2-bug-Y: max chars of tool-result content surfaced to the
|
|
198
210
|
* auxiliary LLM. Covers typical error messages + JSON-payload heads
|
|
@@ -372,11 +384,46 @@ async function distillSession(opts) {
|
|
|
372
384
|
setTimeout(() => resolve({ ok: false, error: new Error(`auxiliary call timed out after ${timeoutMs}ms`), timedOut: true }), timeoutMs);
|
|
373
385
|
}),
|
|
374
386
|
]);
|
|
387
|
+
// v4.1.3-essentials distillation-fix: emit a diagnostic line for
|
|
388
|
+
// each of the three failure classes so the caller can surface the
|
|
389
|
+
// root cause. Previously all three paths produced an identical
|
|
390
|
+
// `partial:true + empty` result with no signal about WHICH failure
|
|
391
|
+
// fired. Safe to call onDiagnostic synchronously — caller wraps in
|
|
392
|
+
// try/catch so a throwing sink doesn't break distillation.
|
|
393
|
+
const diag = (msg) => {
|
|
394
|
+
if (!opts.onDiagnostic)
|
|
395
|
+
return;
|
|
396
|
+
try {
|
|
397
|
+
opts.onDiagnostic(msg);
|
|
398
|
+
}
|
|
399
|
+
catch { /* never break distillation */ }
|
|
400
|
+
};
|
|
375
401
|
let semantic;
|
|
376
402
|
if (llmRaw.ok) {
|
|
377
403
|
semantic = parseLLMDistillation(llmRaw.content);
|
|
404
|
+
if (semantic.partial) {
|
|
405
|
+
// Parser fell back to bullets-only or fully-empty — the LLM
|
|
406
|
+
// returned content but it wasn't valid JSON. First-200-chars
|
|
407
|
+
// hint lets the user / debugger see what shape the model
|
|
408
|
+
// actually emitted (often a chatty preamble that confused
|
|
409
|
+
// the JSON extractor).
|
|
410
|
+
const head = llmRaw.content.trim().slice(0, 200).replace(/\n/g, ' ');
|
|
411
|
+
diag(`auxiliary returned unparseable JSON (first 200 chars: ${head})`);
|
|
412
|
+
}
|
|
378
413
|
}
|
|
379
414
|
else {
|
|
415
|
+
// Race resolved with the failure branch — either the timeout
|
|
416
|
+
// fired or auxiliaryClient.call threw. Hoist `error` into a
|
|
417
|
+
// local so the narrowed type stays stable inside the branch
|
|
418
|
+
// (TS can't infer `error` exists on `llmRaw` because the union
|
|
419
|
+
// overlaps with the success branch in its type literal).
|
|
420
|
+
const failure = llmRaw;
|
|
421
|
+
if (failure.timedOut === true) {
|
|
422
|
+
diag(`auxiliary call timed out after ${timeoutMs}ms`);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
diag(`auxiliary call failed: ${failure.error.message}`);
|
|
426
|
+
}
|
|
380
427
|
semantic = {
|
|
381
428
|
bullets: [],
|
|
382
429
|
decisions: [],
|
|
@@ -154,7 +154,22 @@ class ToolRegistry {
|
|
|
154
154
|
}
|
|
155
155
|
try {
|
|
156
156
|
const result = await handler.execute(args, context);
|
|
157
|
-
|
|
157
|
+
// v4.1.3-repl-polish: lift `degraded` + `degradedReason` from the
|
|
158
|
+
// handler's inner result to the outer ToolCallResult so the CLI
|
|
159
|
+
// trail row can render the partial-yellow state. Tools opt in by
|
|
160
|
+
// setting these on the object they return; without this lift the
|
|
161
|
+
// flags would sit on `out.result.degraded` where callbacks.ts
|
|
162
|
+
// can't see them. Strict typeof checks avoid promoting truthy-
|
|
163
|
+
// but-wrong-shape junk (numbers, strings, nested objects).
|
|
164
|
+
const inner = result;
|
|
165
|
+
const out = { id: call.id, name: call.name, result };
|
|
166
|
+
if (typeof inner?.degraded === 'boolean' && inner.degraded) {
|
|
167
|
+
out.degraded = true;
|
|
168
|
+
if (typeof inner.degradedReason === 'string') {
|
|
169
|
+
out.degradedReason = inner.degradedReason;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
158
173
|
}
|
|
159
174
|
catch (err) {
|
|
160
175
|
const message = err instanceof Error ? err.message : String(err);
|
package/dist/core/version.js
CHANGED
|
@@ -91,6 +91,25 @@ const RULES = [
|
|
|
91
91
|
keywords: /\b(process|background|long.?running|server|spawn|kill|daemon)\b/i,
|
|
92
92
|
toolsets: ['process'],
|
|
93
93
|
},
|
|
94
|
+
// Media playback control (v4.1.4-media)
|
|
95
|
+
//
|
|
96
|
+
// Without this, intents like "list media sessions" matched the
|
|
97
|
+
// `sessions` rule via the bare word "session" → narrowed surface to
|
|
98
|
+
// toolset `sessions` only → `media_sessions` (toolset `system`) was
|
|
99
|
+
// filtered out and the model honestly reported it as unavailable.
|
|
100
|
+
// UNION semantics mean both rules contribute on phrases that hit
|
|
101
|
+
// both ("media sessions" → sessions + system), giving the model the
|
|
102
|
+
// full picture without needing a dedicated `media` toolset.
|
|
103
|
+
//
|
|
104
|
+
// Toolset is `system` (broad but minimal blast radius): the bundle
|
|
105
|
+
// covers media_sessions, media_transport, media_key, app_input,
|
|
106
|
+
// now_playing, plus the plausibly-relevant app_launch / volume_set /
|
|
107
|
+
// os_process_list. Carving out a dedicated `media` toolset is a
|
|
108
|
+
// separate slice if the surface noise becomes a real problem.
|
|
109
|
+
{
|
|
110
|
+
keywords: /\b(play|pause|skip|spotify|music|song|video|youtube|track|playback|media)\b/i,
|
|
111
|
+
toolsets: ['system'],
|
|
112
|
+
},
|
|
94
113
|
];
|
|
95
114
|
/** Always-on tools regardless of mode. The agent needs schema lookup
|
|
96
115
|
* + skill discovery + session search to be useful even on cold turns. */
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.ProviderRateLimitError = exports.ProviderTimeoutError = exports.ProviderError = void 0;
|
|
19
19
|
exports.formatRawForMessage = formatRawForMessage;
|
|
20
|
+
exports.classifyProviderError = classifyProviderError;
|
|
21
|
+
exports.suggestForErrorClass = suggestForErrorClass;
|
|
20
22
|
/**
|
|
21
23
|
* Format a raw response body for inclusion in the user-facing error
|
|
22
24
|
* message. Recognises three JSON envelope shapes and falls back to the
|
|
@@ -109,3 +111,93 @@ class ProviderRateLimitError extends ProviderError {
|
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
exports.ProviderRateLimitError = ProviderRateLimitError;
|
|
114
|
+
function classifyProviderError(err) {
|
|
115
|
+
if (err == null)
|
|
116
|
+
return 'other';
|
|
117
|
+
// 1. Type-based class detection (fastest, most structured).
|
|
118
|
+
if (err instanceof ProviderRateLimitError)
|
|
119
|
+
return 'rate_limit';
|
|
120
|
+
if (err instanceof ProviderTimeoutError)
|
|
121
|
+
return 'transport';
|
|
122
|
+
if (err instanceof ProviderError) {
|
|
123
|
+
if (err.statusCode === 413)
|
|
124
|
+
return 'context_overflow';
|
|
125
|
+
if (err.statusCode === 429)
|
|
126
|
+
return 'rate_limit';
|
|
127
|
+
if (err.statusCode === 401 || err.statusCode === 403)
|
|
128
|
+
return 'auth';
|
|
129
|
+
}
|
|
130
|
+
// 2. Fall back to message scanning. Adapters that pass through the
|
|
131
|
+
// upstream JSON `error.message` verbatim land here.
|
|
132
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
133
|
+
const lc = msg.toLowerCase();
|
|
134
|
+
// Context overflow / 413 family. Groq's free-tier TPM cap triggers
|
|
135
|
+
// these on the first turn once the prompt + tool schemas inflate.
|
|
136
|
+
if (lc.includes('413') ||
|
|
137
|
+
lc.includes('context_length_exceeded') ||
|
|
138
|
+
lc.includes('context length') ||
|
|
139
|
+
lc.includes('too large') ||
|
|
140
|
+
lc.includes('maximum context length') ||
|
|
141
|
+
lc.includes('payload too large')) {
|
|
142
|
+
return 'context_overflow';
|
|
143
|
+
}
|
|
144
|
+
// Rate-limit family — 429 / TPM / quota / "too many requests".
|
|
145
|
+
if (lc.includes('429') ||
|
|
146
|
+
lc.includes('rate_limit') ||
|
|
147
|
+
lc.includes('rate limit') ||
|
|
148
|
+
lc.includes('too many requests') ||
|
|
149
|
+
lc.includes('quota') ||
|
|
150
|
+
lc.includes('tpm')) {
|
|
151
|
+
return 'rate_limit';
|
|
152
|
+
}
|
|
153
|
+
// Auth family — 401 / 403 / invalid keys / unauthenticated.
|
|
154
|
+
if (lc.includes('401') ||
|
|
155
|
+
lc.includes('403') ||
|
|
156
|
+
lc.includes('invalid_api_key') ||
|
|
157
|
+
lc.includes('invalid api key') ||
|
|
158
|
+
lc.includes('unauthenticated') ||
|
|
159
|
+
lc.includes('unauthorized') ||
|
|
160
|
+
lc.includes('forbidden')) {
|
|
161
|
+
return 'auth';
|
|
162
|
+
}
|
|
163
|
+
// Transport — network, DNS, timeouts that escaped the typed path.
|
|
164
|
+
if (lc.includes('econnrefused') ||
|
|
165
|
+
lc.includes('enotfound') ||
|
|
166
|
+
lc.includes('etimedout') ||
|
|
167
|
+
lc.includes('socket hang up') ||
|
|
168
|
+
lc.includes('network')) {
|
|
169
|
+
return 'transport';
|
|
170
|
+
}
|
|
171
|
+
return 'other';
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* v4.1.3-prebump: produce a single-sentence actionable hint for the
|
|
175
|
+
* given error class. Returns null for `'other'` so the caller can keep
|
|
176
|
+
* its existing default suggestion. Provider name is surfaced where it
|
|
177
|
+
* sharpens the advice ("groq rate-limited" reads clearer than
|
|
178
|
+
* "rate limit").
|
|
179
|
+
*
|
|
180
|
+
* Pure helper. The REPL displays the result; tests assert it. No
|
|
181
|
+
* registry / state access — feed it the class + provider name.
|
|
182
|
+
*/
|
|
183
|
+
function suggestForErrorClass(cls, providerName) {
|
|
184
|
+
const p = providerName ?? 'this provider';
|
|
185
|
+
switch (cls) {
|
|
186
|
+
case 'context_overflow':
|
|
187
|
+
return (`${p} returned 413 (context too large). The combined system prompt ` +
|
|
188
|
+
`+ tool schemas exceed ${p}'s context window. Try \`/model\` to ` +
|
|
189
|
+
`switch to a provider with more headroom (chatgpt-plus, anthropic, ` +
|
|
190
|
+
`deepseek).`);
|
|
191
|
+
case 'rate_limit':
|
|
192
|
+
return (`${p} is rate-limited. Wait a minute, or run \`/model\` to switch ` +
|
|
193
|
+
`to another authed provider while ${p} cools off.`);
|
|
194
|
+
case 'auth':
|
|
195
|
+
return (`${p} rejected the credentials. Run \`/auth status\` (or check the ` +
|
|
196
|
+
`relevant API key env var) and \`/auth login\` if needed.`);
|
|
197
|
+
case 'transport':
|
|
198
|
+
return (`Network or transport error reaching ${p}. Check connectivity, then ` +
|
|
199
|
+
`retry — or \`/model\` to a local provider (ollama) for offline work.`);
|
|
200
|
+
case 'other':
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
package/dist/tools/v4/index.js
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
* Status: PHASE 8.
|
|
22
22
|
*/
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.
|
|
24
|
+
exports.memoryReplaceTool = exports.memoryAddTool = exports.processWaitTool = exports.processKillTool = exports.processLogReadTool = exports.processListTool = exports.processSpawnTool = exports.executeCodeTool = exports.shellExecTool = exports.appInputTool = exports.mediaTransportTool = exports.mediaSessionsTool = exports.clipboardWriteTool = exports.clipboardReadTool = exports.appCloseTool = exports.appLaunchTool = exports.volumeSetTool = exports.mediaKeyTool = exports.osProcessListTool = exports.screenshotTool = exports.naturalEventsTool = exports.nowPlayingTool = exports.systemInfoTool = exports.makeLookupToolSchema = exports.skillManageTool = exports.skillViewTool = exports.skillsListTool = exports.sessionListTool = exports.sessionSearchTool = exports.browserCloseTool = exports.browserScrollTool = exports.browserFillTool = exports.browserTypeTool = exports.browserClickTool = exports.browserNavigateTool = exports.browserGetUrlTool = exports.browserExtractTool = exports.browserScreenshotTool = exports.fileCopyTool = exports.fileMoveTool = exports.fileDeleteTool = exports.filePatchTool = exports.fileWriteTool = exports.fileListTool = exports.fileReadTool = exports.deepResearchTool = exports.webPageTool = exports.webFetchTool = exports.webSearchTool = exports.makeSubagentFanoutTool = void 0;
|
|
25
|
+
exports.sessionSummaryTool = exports.memoryRemoveTool = void 0;
|
|
25
26
|
exports.registerReadOnlyTools = registerReadOnlyTools;
|
|
26
27
|
exports.registerWriteTools = registerWriteTools;
|
|
27
28
|
exports.registerAllTools = registerAllTools;
|
|
@@ -66,6 +67,13 @@ const appLaunch_1 = require("./system/appLaunch");
|
|
|
66
67
|
const appClose_1 = require("./system/appClose");
|
|
67
68
|
const clipboardRead_1 = require("./system/clipboardRead");
|
|
68
69
|
const clipboardWrite_1 = require("./system/clipboardWrite");
|
|
70
|
+
// v4.1.4-media — three-layer media-control bundle.
|
|
71
|
+
// Layer 2 (OS media session): mediaSessions (read) + mediaTransport (write).
|
|
72
|
+
// Layer 3 fallback (mediaKey, blind keystroke) remains unchanged.
|
|
73
|
+
// Layer 1 (semantic API) is per-app and out of this slice.
|
|
74
|
+
const mediaSessions_1 = require("./system/mediaSessions");
|
|
75
|
+
const mediaTransport_1 = require("./system/mediaTransport");
|
|
76
|
+
const appInput_1 = require("./system/appInput");
|
|
69
77
|
// Phase v4.1.2-update — natural-language self-update entry point.
|
|
70
78
|
// Routes through the same shared executeInstall executor as `/update install`.
|
|
71
79
|
const aidenSelfUpdate_1 = require("./system/aidenSelfUpdate");
|
|
@@ -123,6 +131,9 @@ function registerReadOnlyTools(registry) {
|
|
|
123
131
|
registry.register(screenshot_1.screenshotTool);
|
|
124
132
|
registry.register(osProcessList_1.osProcessListTool);
|
|
125
133
|
registry.register(clipboardRead_1.clipboardReadTool);
|
|
134
|
+
// v4.1.4-media — GSMTC session enumeration (read). Pair with
|
|
135
|
+
// mediaTransport (write) in the write-tools registration below.
|
|
136
|
+
registry.register(mediaSessions_1.mediaSessionsTool);
|
|
126
137
|
registry.register((0, lookupToolSchema_1.makeLookupToolSchema)(registry));
|
|
127
138
|
// Phase v4.1-subagent — register a stub for subagent_fanout so its
|
|
128
139
|
// schema is visible to the agent loop, the MCP server, and the
|
|
@@ -198,6 +209,11 @@ function registerWriteTools(registry) {
|
|
|
198
209
|
registry.register(appLaunch_1.appLaunchTool);
|
|
199
210
|
registry.register(appClose_1.appCloseTool);
|
|
200
211
|
registry.register(clipboardWrite_1.clipboardWriteTool);
|
|
212
|
+
// v4.1.4-media — verified GSMTC transport (replaces mediaKey for
|
|
213
|
+
// the "name an app, play/pause it" case) + focused-window SendKeys
|
|
214
|
+
// (escape hatch when GSMTC doesn't enumerate the surface).
|
|
215
|
+
registry.register(mediaTransport_1.mediaTransportTool);
|
|
216
|
+
registry.register(appInput_1.appInputTool);
|
|
201
217
|
}
|
|
202
218
|
/** Register every v4 tool. Most callers want this. */
|
|
203
219
|
function registerAllTools(registry) {
|
|
@@ -281,6 +297,13 @@ var clipboardRead_2 = require("./system/clipboardRead");
|
|
|
281
297
|
Object.defineProperty(exports, "clipboardReadTool", { enumerable: true, get: function () { return clipboardRead_2.clipboardReadTool; } });
|
|
282
298
|
var clipboardWrite_2 = require("./system/clipboardWrite");
|
|
283
299
|
Object.defineProperty(exports, "clipboardWriteTool", { enumerable: true, get: function () { return clipboardWrite_2.clipboardWriteTool; } });
|
|
300
|
+
// v4.1.4-media exports — three-layer media-control bundle.
|
|
301
|
+
var mediaSessions_2 = require("./system/mediaSessions");
|
|
302
|
+
Object.defineProperty(exports, "mediaSessionsTool", { enumerable: true, get: function () { return mediaSessions_2.mediaSessionsTool; } });
|
|
303
|
+
var mediaTransport_2 = require("./system/mediaTransport");
|
|
304
|
+
Object.defineProperty(exports, "mediaTransportTool", { enumerable: true, get: function () { return mediaTransport_2.mediaTransportTool; } });
|
|
305
|
+
var appInput_2 = require("./system/appInput");
|
|
306
|
+
Object.defineProperty(exports, "appInputTool", { enumerable: true, get: function () { return appInput_2.appInputTool; } });
|
|
284
307
|
var shellExec_2 = require("./terminal/shellExec");
|
|
285
308
|
Object.defineProperty(exports, "shellExecTool", { enumerable: true, get: function () { return shellExec_2.shellExecTool; } });
|
|
286
309
|
var executeCode_2 = require("./executeCode");
|
|
@@ -132,6 +132,14 @@ exports.recallSessionTool = {
|
|
|
132
132
|
include_full: args.include_full === true,
|
|
133
133
|
};
|
|
134
134
|
const ranked = (0, distillationIndex_1.rankDistillations)(dists, recallQuery);
|
|
135
|
+
// v4.1.3-repl-polish: mark degraded when any matched session was
|
|
136
|
+
// distilled with the Phase A+B `partial: true` flag (LLM-timeout
|
|
137
|
+
// path — deterministic fields present, semantic bullets/decisions
|
|
138
|
+
// may be empty). The model still gets the full match list; the
|
|
139
|
+
// trail row renders yellow so the user knows recall completed
|
|
140
|
+
// against partial data.
|
|
141
|
+
const partialCount = ranked.matches.filter((m) => m.partial === true).length;
|
|
142
|
+
const degraded = partialCount > 0;
|
|
135
143
|
return {
|
|
136
144
|
success: true,
|
|
137
145
|
query: recallQuery.query,
|
|
@@ -142,6 +150,12 @@ exports.recallSessionTool = {
|
|
|
142
150
|
// delta is malformed files; the agent can suggest running aiden
|
|
143
151
|
// doctor to inspect.
|
|
144
152
|
scanned: ids.length,
|
|
153
|
+
...(degraded && {
|
|
154
|
+
degraded: true,
|
|
155
|
+
degradedReason: partialCount === 1
|
|
156
|
+
? '1 matched session has partial distillation data'
|
|
157
|
+
: `${partialCount} matched sessions have partial distillation data`,
|
|
158
|
+
}),
|
|
145
159
|
};
|
|
146
160
|
// Note re: subsystem health — wire-up happens at the runtime
|
|
147
161
|
// construction layer (cli/v4/aidenCLI.ts) where the registry is
|
|
@@ -19,6 +19,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
19
19
|
exports.isWindows = exports.execAsync = void 0;
|
|
20
20
|
exports.windowsOnlyError = windowsOnlyError;
|
|
21
21
|
exports.runPowerShell = runPowerShell;
|
|
22
|
+
exports.winRtAwaitPreamble = winRtAwaitPreamble;
|
|
22
23
|
const node_child_process_1 = require("node:child_process");
|
|
23
24
|
const node_util_1 = require("node:util");
|
|
24
25
|
exports.execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
@@ -26,13 +27,43 @@ exports.execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
|
26
27
|
* Standard "not supported on this platform" error payload. Surfaces a
|
|
27
28
|
* link the user can file an issue against rather than pretending the
|
|
28
29
|
* call quietly no-op'd.
|
|
30
|
+
*
|
|
31
|
+
* v4.1.3-essentials: now also returns a structured `capabilityCard`
|
|
32
|
+
* payload (per ToolCallResult.capabilityCard contract). The REPL
|
|
33
|
+
* renders the card as a bordered block above the bare-error fallback,
|
|
34
|
+
* giving non-Windows users a clear "here's what you can still do"
|
|
35
|
+
* surface instead of a one-line "platform unsupported" wall.
|
|
36
|
+
*
|
|
37
|
+
* The `canStill` / `cannotReliably` lists are passed by the caller so
|
|
38
|
+
* each tool can be specific (e.g. `app_input` mentions Chrome DevTools
|
|
39
|
+
* Protocol as a non-Windows alternative; `media_transport` points at
|
|
40
|
+
* `media_key` or a Spotify Web API skill instead). Falls back to a
|
|
41
|
+
* generic "use shell_exec for platform commands" hint when caller
|
|
42
|
+
* doesn't supply alternatives.
|
|
29
43
|
*/
|
|
30
|
-
function windowsOnlyError(toolName) {
|
|
44
|
+
function windowsOnlyError(toolName, alternatives) {
|
|
45
|
+
const canStill = alternatives?.canStill ?? [
|
|
46
|
+
'Use `shell_exec` to run platform-native commands directly',
|
|
47
|
+
'Use `os_process_list` to inspect what\'s running',
|
|
48
|
+
];
|
|
49
|
+
const cannotReliably = alternatives?.cannotReliably ?? [
|
|
50
|
+
`Call \`${toolName}\` until cross-platform support lands`,
|
|
51
|
+
];
|
|
52
|
+
const fix = alternatives?.fix
|
|
53
|
+
?? `Run Aiden on Windows for full \`${toolName}\` support, or file an ` +
|
|
54
|
+
`issue at github.com/taracodlabs/aiden if your platform is a priority.`;
|
|
31
55
|
return {
|
|
32
56
|
success: false,
|
|
33
|
-
error: `Tool '${toolName}' is Windows-only
|
|
57
|
+
error: `Tool '${toolName}' is Windows-only. macOS/Linux ` +
|
|
34
58
|
`support tracked at github.com/taracodlabs/aiden — please file an ` +
|
|
35
59
|
`issue if needed. (Detected platform: ${process.platform})`,
|
|
60
|
+
requires: ['Windows'],
|
|
61
|
+
capabilityCard: {
|
|
62
|
+
title: `${toolName} requires Windows`,
|
|
63
|
+
canStill,
|
|
64
|
+
cannotReliably,
|
|
65
|
+
fix,
|
|
66
|
+
},
|
|
36
67
|
};
|
|
37
68
|
}
|
|
38
69
|
/**
|
|
@@ -53,3 +84,40 @@ async function runPowerShell(script, options = {}) {
|
|
|
53
84
|
}
|
|
54
85
|
const isWindows = () => process.platform === 'win32';
|
|
55
86
|
exports.isWindows = isWindows;
|
|
87
|
+
/**
|
|
88
|
+
* v4.1.4-media: PowerShell 5.1 preamble that bridges WinRT
|
|
89
|
+
* `IAsyncOperation<T>` into a .NET `Task<T>` via
|
|
90
|
+
* `System.WindowsRuntimeSystemExtensions.AsTask`.
|
|
91
|
+
*
|
|
92
|
+
* Why: every WinRT call surface we touch — `GlobalSystemMediaTransport-
|
|
93
|
+
* ControlsSessionManager.RequestAsync()`, `Session.TryGetMediaPropertiesAsync()`,
|
|
94
|
+
* `Session.TryPlayAsync()`, etc. — returns `IAsyncOperation<T>`. PS5.1
|
|
95
|
+
* (the shell we target — it ships on every stock Win10/11 install) cannot
|
|
96
|
+
* call `.GetAwaiter().GetResult()` on those because WinRT awaiters aren't
|
|
97
|
+
* recognized as TPL-compatible. The reflection dance below grabs the
|
|
98
|
+
* single-arg overload of `AsTask`, specializes it to `T`, and invokes —
|
|
99
|
+
* yielding a `Task<T>` we can `.Wait()` on.
|
|
100
|
+
*
|
|
101
|
+
* Three callers consume this string:
|
|
102
|
+
* - `core/tools/nowPlaying.ts` (read GSMTC properties)
|
|
103
|
+
* - `tools/v4/system/mediaSessions.ts` (enumerate GSMTC sessions)
|
|
104
|
+
* - `tools/v4/system/mediaTransport.ts` (play/pause/skip on a target)
|
|
105
|
+
*
|
|
106
|
+
* Returned as a literal string — caller composes it into a larger
|
|
107
|
+
* PS script. Pure (no side effects, no PowerShell exec). No leading/
|
|
108
|
+
* trailing whitespace so callers can interpolate without surprises.
|
|
109
|
+
*/
|
|
110
|
+
function winRtAwaitPreamble() {
|
|
111
|
+
return `Add-Type -AssemblyName System.Runtime.WindowsRuntime
|
|
112
|
+
function Await($WinRtTask, $ResultType) {
|
|
113
|
+
$m = ([System.WindowsRuntimeSystemExtensions].GetMethods() | Where-Object {
|
|
114
|
+
$_.Name -eq 'AsTask' -and
|
|
115
|
+
$_.GetParameters().Count -eq 1 -and
|
|
116
|
+
$_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation\`1'
|
|
117
|
+
})[0]
|
|
118
|
+
$m = $m.MakeGenericMethod($ResultType)
|
|
119
|
+
$t = $m.Invoke($null, @($WinRtTask))
|
|
120
|
+
$t.Wait(-1) | Out-Null
|
|
121
|
+
$t.Result
|
|
122
|
+
}`;
|
|
123
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
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
|
+
* tools/v4/system/appInput.ts — `app_input` tool. v4.1.4-media.
|
|
10
|
+
*
|
|
11
|
+
* Focus a window by process name, then send a SendKeys keystroke
|
|
12
|
+
* sequence to it. Useful escape hatch when neither the semantic API
|
|
13
|
+
* (layer 1) nor GSMTC (layer 2) surface a control — e.g. "press space
|
|
14
|
+
* in Chrome to pause this YouTube tab" when GSMTC doesn't enumerate
|
|
15
|
+
* the page as a media session.
|
|
16
|
+
*
|
|
17
|
+
* Honest about what it doesn't do: SendKeys lands keys in whatever
|
|
18
|
+
* window has focus AT THE MOMENT of the keystroke. We try
|
|
19
|
+
* AppActivate, but Windows refuses foreground activation when the
|
|
20
|
+
* calling process didn't recently receive input — the call returns
|
|
21
|
+
* a result we surface, but receipt at the target app is not
|
|
22
|
+
* guaranteed. Hence `degraded: true` on every successful invocation
|
|
23
|
+
* (mirrors the v4.1.3 honesty-degraded convention from `media_key`).
|
|
24
|
+
*
|
|
25
|
+
* Scope (v4.1.4): focus + SendKeys only. Mouse click coordinates,
|
|
26
|
+
* window-coords resolution, UI Automation deferred.
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.appInputTool = void 0;
|
|
30
|
+
const _psHelpers_1 = require("./_psHelpers");
|
|
31
|
+
/**
|
|
32
|
+
* Build the PowerShell snippet. Calls AppActivate on the process by
|
|
33
|
+
* name, then SendKeys.SendWait. Both PowerShell calls return booleans /
|
|
34
|
+
* void; we capture stdout to JSON with the activation outcome so the
|
|
35
|
+
* model can see whether focus probably landed.
|
|
36
|
+
*
|
|
37
|
+
* Note on AppActivate: it returns $true if the process exists and a
|
|
38
|
+
* window was activated, $false otherwise. It does NOT confirm the
|
|
39
|
+
* window is the foreground from the OS's perspective — Windows
|
|
40
|
+
* sometimes flashes the taskbar entry instead. We pass that flag
|
|
41
|
+
* through as `activated` for transparency.
|
|
42
|
+
*/
|
|
43
|
+
function buildPs(processName, keys) {
|
|
44
|
+
// Single-quote escape both inputs for the PowerShell string literals.
|
|
45
|
+
const safeProc = processName.replace(/'/g, "''");
|
|
46
|
+
const safeKeys = keys.replace(/'/g, "''");
|
|
47
|
+
return [
|
|
48
|
+
'Add-Type -AssemblyName Microsoft.VisualBasic;',
|
|
49
|
+
'Add-Type -AssemblyName System.Windows.Forms;',
|
|
50
|
+
'$shell = New-Object -ComObject WScript.Shell;',
|
|
51
|
+
`$activated = $shell.AppActivate('${safeProc}');`,
|
|
52
|
+
// Give the OS ~150ms to settle before keystrokes — without this
|
|
53
|
+
// the keys can land in the calling shell on slower hardware.
|
|
54
|
+
'Start-Sleep -Milliseconds 150;',
|
|
55
|
+
`[System.Windows.Forms.SendKeys]::SendWait('${safeKeys}');`,
|
|
56
|
+
"@{ activated=[bool]$activated } | ConvertTo-Json -Compress;",
|
|
57
|
+
].join(' ');
|
|
58
|
+
}
|
|
59
|
+
exports.appInputTool = {
|
|
60
|
+
schema: {
|
|
61
|
+
name: 'app_input',
|
|
62
|
+
description: 'Focus a Windows application window by process name and send a ' +
|
|
63
|
+
'SendKeys keystroke sequence to it. Use as a layer-3 fallback when ' +
|
|
64
|
+
'neither a semantic API (layer 1, e.g. Spotify Web API) nor GSMTC ' +
|
|
65
|
+
'(layer 2, `media_transport`) can do the job. Examples: "{SPACE}" to ' +
|
|
66
|
+
'pause a YouTube tab in Chrome, "^l" for Ctrl+L address-bar focus. ' +
|
|
67
|
+
'Receipt at the target app is best-effort — Windows can refuse ' +
|
|
68
|
+
'foreground activation; the tool reports `degraded:true` even on ' +
|
|
69
|
+
'apparent success. Windows-only in v4.1.4.',
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
app: {
|
|
74
|
+
type: 'string',
|
|
75
|
+
description: 'Process name (with or without .exe) or window-title substring ' +
|
|
76
|
+
'AppActivate accepts: "chrome", "Spotify", "Notepad", etc.',
|
|
77
|
+
},
|
|
78
|
+
keys: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
description: 'SendKeys-format keystroke sequence. Examples: "{SPACE}" = ' +
|
|
81
|
+
'space, "^c" = Ctrl+C, "%{TAB}" = Alt+Tab, "Hello{ENTER}" = ' +
|
|
82
|
+
'literal text + Enter. See Microsoft\'s SendKeys docs for the ' +
|
|
83
|
+
'full grammar.',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
required: ['app', 'keys'],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
category: 'execute',
|
|
90
|
+
mutates: true,
|
|
91
|
+
toolset: 'system',
|
|
92
|
+
async execute(args, _ctx) {
|
|
93
|
+
if (!(0, _psHelpers_1.isWindows)()) {
|
|
94
|
+
return (0, _psHelpers_1.windowsOnlyError)('app_input', {
|
|
95
|
+
canStill: [
|
|
96
|
+
'`browser_*` tools for any browser-hosted UI (Playwright cross-platform)',
|
|
97
|
+
'`shell_exec` with `xdotool` (Linux X11) for arbitrary window input',
|
|
98
|
+
'`shell_exec` with `osascript` (macOS) for AppleScript-driven keystrokes',
|
|
99
|
+
],
|
|
100
|
+
cannotReliably: [
|
|
101
|
+
'AppActivate + SendKeys against a specific Windows process',
|
|
102
|
+
'VBA-style window focus by process-name substring',
|
|
103
|
+
],
|
|
104
|
+
fix: 'Run Aiden on Windows for native AppActivate, or use Playwright ' +
|
|
105
|
+
'(`browser_*`) / xdotool / osascript via `shell_exec` for your platform.',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const app = typeof args.app === 'string' ? args.app.trim() : '';
|
|
109
|
+
const keys = typeof args.keys === 'string' ? args.keys : '';
|
|
110
|
+
if (!app) {
|
|
111
|
+
return { success: false, error: '`app` is required and must be non-empty.' };
|
|
112
|
+
}
|
|
113
|
+
if (!keys) {
|
|
114
|
+
return { success: false, error: '`keys` is required and must be non-empty.' };
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const { stdout } = await (0, _psHelpers_1.runPowerShell)(buildPs(app, keys), {
|
|
118
|
+
timeoutMs: 5000,
|
|
119
|
+
});
|
|
120
|
+
const trimmed = stdout.trim();
|
|
121
|
+
let activated = false;
|
|
122
|
+
if (trimmed.length > 0) {
|
|
123
|
+
try {
|
|
124
|
+
const parsed = JSON.parse(trimmed);
|
|
125
|
+
activated = parsed.activated === true;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Non-JSON output — degraded but not failed; the SendKeys
|
|
129
|
+
// call likely still ran. Surface in degradedReason.
|
|
130
|
+
activated = false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
success: true,
|
|
135
|
+
app,
|
|
136
|
+
activated,
|
|
137
|
+
// v4.1.3-repl-polish honesty pattern: SendKeys cannot confirm
|
|
138
|
+
// receipt at the target window. AppActivate returning $true
|
|
139
|
+
// narrows the gap but doesn't close it — Windows can reject
|
|
140
|
+
// foreground activation silently. Always degraded.
|
|
141
|
+
degraded: true,
|
|
142
|
+
degradedReason: activated
|
|
143
|
+
? `keys sent to ${app}; activation reported success but cannot verify receipt`
|
|
144
|
+
: `keys sent; ${app} window activation reported failure — receipt unlikely`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: e instanceof Error ? e.message : String(e),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
};
|