aiden-runtime 4.1.1 → 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/README.md +78 -26
- package/dist/cli/v4/aidenCLI.js +169 -9
- package/dist/cli/v4/callbacks.js +20 -2
- package/dist/cli/v4/chatSession.js +644 -16
- package/dist/cli/v4/commands/auth.js +6 -3
- package/dist/cli/v4/commands/doctor.js +23 -27
- package/dist/cli/v4/commands/help.js +4 -0
- package/dist/cli/v4/commands/index.js +10 -1
- package/dist/cli/v4/commands/model.js +30 -1
- package/dist/cli/v4/commands/reloadSoul.js +37 -0
- package/dist/cli/v4/commands/update.js +102 -0
- package/dist/cli/v4/defaultSoul.js +68 -2
- 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 +492 -142
- package/dist/cli/v4/doctor.js +472 -58
- package/dist/cli/v4/doctorLiveness.js +65 -10
- package/dist/cli/v4/promotionPrompt.js +332 -0
- package/dist/cli/v4/providerBootSelector.js +144 -0
- package/dist/cli/v4/replyRenderer.js +311 -20
- package/dist/cli/v4/sessionSummaryGate.js +66 -0
- package/dist/cli/v4/skinEngine.js +14 -3
- package/dist/cli/v4/toolPreview.js +153 -0
- package/dist/core/tools/nowPlaying.js +7 -15
- package/dist/core/v4/aidenAgent.js +91 -29
- package/dist/core/v4/capabilities.js +89 -0
- package/dist/core/v4/contextCompressor.js +25 -8
- package/dist/core/v4/distillationIndex.js +167 -0
- package/dist/core/v4/distillationStore.js +98 -0
- package/dist/core/v4/logger/logger.js +40 -9
- package/dist/core/v4/promotionCandidates.js +234 -0
- package/dist/core/v4/promptBuilder.js +145 -1
- package/dist/core/v4/sessionDistiller.js +452 -0
- package/dist/core/v4/skillMining/skillMiner.js +43 -6
- package/dist/core/v4/skillOutcomeTracker.js +323 -0
- package/dist/core/v4/subsystemHealth.js +143 -0
- package/dist/core/v4/toolRegistry.js +16 -1
- package/dist/core/v4/update/executeInstall.js +233 -0
- package/dist/core/version.js +1 -1
- package/dist/moat/memoryGuard.js +111 -0
- package/dist/moat/plannerGuard.js +19 -0
- package/dist/moat/skillTeacher.js +14 -5
- package/dist/providers/v4/chatCompletionsAdapter.js +9 -0
- package/dist/providers/v4/errors.js +112 -4
- package/dist/providers/v4/modelDefaults.js +65 -0
- package/dist/providers/v4/registry.js +9 -2
- package/dist/providers/v4/runtimeResolver.js +6 -0
- package/dist/tools/v4/index.js +80 -1
- package/dist/tools/v4/memory/memoryRemove.js +57 -2
- package/dist/tools/v4/memory/sessionSummary.js +151 -0
- package/dist/tools/v4/sessions/recallSession.js +177 -0
- package/dist/tools/v4/sessions/sessionSearch.js +5 -1
- package/dist/tools/v4/system/_psHelpers.js +123 -0
- package/dist/tools/v4/system/aidenSelfUpdate.js +162 -0
- package/dist/tools/v4/system/appClose.js +79 -0
- package/dist/tools/v4/system/appInput.js +154 -0
- package/dist/tools/v4/system/appLaunch.js +218 -0
- package/dist/tools/v4/system/clipboardRead.js +54 -0
- package/dist/tools/v4/system/clipboardWrite.js +84 -0
- package/dist/tools/v4/system/mediaKey.js +109 -0
- package/dist/tools/v4/system/mediaSessions.js +163 -0
- package/dist/tools/v4/system/mediaTransport.js +211 -0
- package/dist/tools/v4/system/osProcessList.js +99 -0
- package/dist/tools/v4/system/screenshot.js +106 -0
- package/dist/tools/v4/system/volumeSet.js +157 -0
- package/package.json +4 -1
- package/skills/system_control.md +185 -69
|
@@ -0,0 +1,123 @@
|
|
|
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/_psHelpers.ts — Phase v4.1.2-followup-3.
|
|
10
|
+
*
|
|
11
|
+
* Shared utilities for the computer-control tool family. Each tool
|
|
12
|
+
* (screenshot / os_process_list / media_key / volume_set / app_launch /
|
|
13
|
+
* app_close / clipboard_read / clipboard_write) gates on `win32` and
|
|
14
|
+
* shells out to PowerShell. The gate + exec boilerplate is identical
|
|
15
|
+
* across all eight tools — extracted here so the per-tool files stay
|
|
16
|
+
* focused on the one PowerShell snippet that matters.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.isWindows = exports.execAsync = void 0;
|
|
20
|
+
exports.windowsOnlyError = windowsOnlyError;
|
|
21
|
+
exports.runPowerShell = runPowerShell;
|
|
22
|
+
exports.winRtAwaitPreamble = winRtAwaitPreamble;
|
|
23
|
+
const node_child_process_1 = require("node:child_process");
|
|
24
|
+
const node_util_1 = require("node:util");
|
|
25
|
+
exports.execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
26
|
+
/**
|
|
27
|
+
* Standard "not supported on this platform" error payload. Surfaces a
|
|
28
|
+
* link the user can file an issue against rather than pretending the
|
|
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.
|
|
43
|
+
*/
|
|
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.`;
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: `Tool '${toolName}' is Windows-only. macOS/Linux ` +
|
|
58
|
+
`support tracked at github.com/taracodlabs/aiden — please file an ` +
|
|
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
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Run a PowerShell snippet and return stdout. Defaults to a 15-second
|
|
71
|
+
* timeout — caller passes a different one when a slower operation
|
|
72
|
+
* (screenshot, app launch) is expected.
|
|
73
|
+
*
|
|
74
|
+
* Single source of truth for the `shell: 'powershell.exe'` invocation
|
|
75
|
+
* shape so future powershell-CLI / `pwsh` migration is one-line.
|
|
76
|
+
*/
|
|
77
|
+
async function runPowerShell(script, options = {}) {
|
|
78
|
+
const opts = {
|
|
79
|
+
shell: 'powershell.exe',
|
|
80
|
+
timeout: options.timeoutMs ?? 15000,
|
|
81
|
+
maxBuffer: (options.maxBufferMb ?? 4) * 1024 * 1024,
|
|
82
|
+
};
|
|
83
|
+
return await (0, exports.execAsync)(script, opts);
|
|
84
|
+
}
|
|
85
|
+
const isWindows = () => process.platform === 'win32';
|
|
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,162 @@
|
|
|
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/aidenSelfUpdate.ts — Phase v4.1.2-update.
|
|
10
|
+
*
|
|
11
|
+
* Natural-language entry point for self-update. When the user asks
|
|
12
|
+
* Aiden to update / install latest / upgrade itself, the model calls
|
|
13
|
+
* this tool. Routes to the same shared executor (`executeInstall`)
|
|
14
|
+
* that `/update install` uses — single source of truth for install
|
|
15
|
+
* behavior.
|
|
16
|
+
*
|
|
17
|
+
* Two-step confirmation contract (consent gate):
|
|
18
|
+
* 1. First call with `confirm: false` — returns status + a prompt
|
|
19
|
+
* asking the user to confirm. NEVER spawns.
|
|
20
|
+
* 2. Model surfaces the prompt to the user; waits for explicit
|
|
21
|
+
* agreement ("yes update", "go ahead", "do it").
|
|
22
|
+
* 3. Second call with `confirm: true` — only after explicit user
|
|
23
|
+
* agreement; spawns the install.
|
|
24
|
+
*
|
|
25
|
+
* The contract is enforced via tool DESCRIPTION (model-facing rule).
|
|
26
|
+
* Tool-side, we don't track "did the user actually consent" — that
|
|
27
|
+
* needs a runtime approval object (request_id + fresh-confirmation
|
|
28
|
+
* verification) which is a v4.2+ design. For v4.1.2 the description
|
|
29
|
+
* carries the rule and the tool trusts the model to follow it.
|
|
30
|
+
*
|
|
31
|
+
* Acceptable risk: failure mode of a misbehaving model is "user gets
|
|
32
|
+
* an unwanted install they have to /quit to apply" — not data loss.
|
|
33
|
+
* Phase D's promotion path is the more sensitive consent surface and
|
|
34
|
+
* is user-driven UI.
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.aidenSelfUpdateTool = void 0;
|
|
38
|
+
const version_1 = require("../../../core/version");
|
|
39
|
+
const checkUpdate_1 = require("../../../core/v4/update/checkUpdate");
|
|
40
|
+
const executeInstall_1 = require("../../../core/v4/update/executeInstall");
|
|
41
|
+
exports.aidenSelfUpdateTool = {
|
|
42
|
+
schema: {
|
|
43
|
+
name: 'aiden_self_update',
|
|
44
|
+
description: 'Update Aiden to the latest version via npm install -g aiden-runtime@latest. ' +
|
|
45
|
+
'TWO-STEP CONFIRMATION REQUIRED: first call with confirm:false to check status ' +
|
|
46
|
+
'and surface to the user; only call with confirm:true AFTER the user explicitly ' +
|
|
47
|
+
'agrees in their next message ("yes update", "go ahead", "do it"). NEVER call ' +
|
|
48
|
+
'with confirm:true autonomously. ' +
|
|
49
|
+
'Call this tool ONLY when the user explicitly asks Aiden to update / install ' +
|
|
50
|
+
'latest / upgrade itself. Example user phrases that warrant a call: ' +
|
|
51
|
+
'"update yourself", "can you install the latest version?", "upgrade to the latest", ' +
|
|
52
|
+
'"self-update". DO NOT call when: user asks about update status without requesting ' +
|
|
53
|
+
'action ("are there updates?") — for status queries, just answer from your context; ' +
|
|
54
|
+
'user mentions updates of OTHER software ("update VSCode"); user has not explicitly ' +
|
|
55
|
+
'asked Aiden to update itself.',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
confirm: {
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
description: 'False on first call (status check, no install). True on second call AFTER ' +
|
|
62
|
+
'the user explicitly agreed to proceed in their last message.',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
required: ['confirm'],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
category: 'write',
|
|
69
|
+
mutates: true,
|
|
70
|
+
toolset: 'system',
|
|
71
|
+
async execute(args, ctx) {
|
|
72
|
+
if (!ctx.paths) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
error: 'aiden_self_update needs Aiden user-data paths — not configured in this context.',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const confirm = args.confirm === true;
|
|
79
|
+
// Status probe — bypass the 6h boot cache for user-initiated checks.
|
|
80
|
+
const status = await (0, checkUpdate_1.checkForUpdate)({
|
|
81
|
+
paths: ctx.paths,
|
|
82
|
+
installedVersion: version_1.VERSION,
|
|
83
|
+
cacheTtlMs: 0,
|
|
84
|
+
});
|
|
85
|
+
// ── First call: confirm:false → status + prompt. NEVER spawn. ─────
|
|
86
|
+
if (!confirm) {
|
|
87
|
+
if (status.latest === null) {
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
stage: 'status',
|
|
91
|
+
message: "Couldn't check for updates (registry unreachable). " +
|
|
92
|
+
'Try again in a moment, or run `npm install -g aiden-runtime@latest` manually.',
|
|
93
|
+
installed: status.installed,
|
|
94
|
+
latest: null,
|
|
95
|
+
updateAvailable: false,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (!status.updateAvailable) {
|
|
99
|
+
return {
|
|
100
|
+
success: true,
|
|
101
|
+
stage: 'status',
|
|
102
|
+
message: `You're on the latest version (v${status.installed}). Nothing to update.`,
|
|
103
|
+
installed: status.installed,
|
|
104
|
+
latest: status.latest,
|
|
105
|
+
updateAvailable: false,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
stage: 'status',
|
|
111
|
+
message: `Update available: v${status.installed} → v${status.latest}. ` +
|
|
112
|
+
`Confirm by saying "yes update" or "go ahead". This runs ` +
|
|
113
|
+
`\`npm install -g aiden-runtime@latest\` and you'll need to ` +
|
|
114
|
+
`restart Aiden after.`,
|
|
115
|
+
installed: status.installed,
|
|
116
|
+
latest: status.latest,
|
|
117
|
+
updateAvailable: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// ── Second call: confirm:true → install. ────────────────────────
|
|
121
|
+
if (status.latest === null) {
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
stage: 'install',
|
|
125
|
+
error: "Can't install — registry unreachable. " +
|
|
126
|
+
'Try again or run `npm install -g aiden-runtime@latest` manually.',
|
|
127
|
+
installed: status.installed,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (!status.updateAvailable) {
|
|
131
|
+
return {
|
|
132
|
+
success: true,
|
|
133
|
+
stage: 'install',
|
|
134
|
+
message: `Already on v${status.installed}. Nothing to install.`,
|
|
135
|
+
installed: status.installed,
|
|
136
|
+
latest: status.latest,
|
|
137
|
+
updateAvailable: false,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const result = await (0, executeInstall_1.executeInstall)();
|
|
141
|
+
if (result.success) {
|
|
142
|
+
const v = result.installedVersion ?? status.latest;
|
|
143
|
+
return {
|
|
144
|
+
success: true,
|
|
145
|
+
stage: 'install',
|
|
146
|
+
message: `✓ aiden-runtime v${v} installed. Restart Aiden to apply: ` +
|
|
147
|
+
`type /quit then re-run \`aiden\`.`,
|
|
148
|
+
installed: status.installed,
|
|
149
|
+
installedVersion: v,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
stage: 'install',
|
|
155
|
+
error: result.error ?? 'Install failed (no error message).',
|
|
156
|
+
installed: status.installed,
|
|
157
|
+
latestSeen: status.latest,
|
|
158
|
+
// Keep stdout/stderr off the model-visible response to avoid
|
|
159
|
+
// prompt bloat; user-actionable copy-paste is already in error.
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
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/appClose.ts — `app_close` tool.
|
|
10
|
+
*
|
|
11
|
+
* Close one or more Windows processes by process name. Accepts the
|
|
12
|
+
* bare name without `.exe` (matches `Stop-Process -Name` semantics).
|
|
13
|
+
* Returns the count of processes successfully terminated.
|
|
14
|
+
*
|
|
15
|
+
* The .exe stripper handles user input like "close notepad.exe" /
|
|
16
|
+
* "close notepad" identically — both resolve to the same call.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.appCloseTool = void 0;
|
|
20
|
+
const _psHelpers_1 = require("./_psHelpers");
|
|
21
|
+
function normalise(name) {
|
|
22
|
+
return name.trim().replace(/\.exe$/i, '');
|
|
23
|
+
}
|
|
24
|
+
function buildPs(processName, force) {
|
|
25
|
+
const safe = processName.replace(/'/g, "''");
|
|
26
|
+
const forceFlag = force ? '-Force' : '';
|
|
27
|
+
return [
|
|
28
|
+
`$procs = Get-Process -Name '${safe}' -ErrorAction SilentlyContinue;`,
|
|
29
|
+
`$count = ($procs | Measure-Object).Count;`,
|
|
30
|
+
`if ($count -gt 0) { $procs | Stop-Process ${forceFlag} -ErrorAction SilentlyContinue; }`,
|
|
31
|
+
`Write-Output ('closed:' + $count);`,
|
|
32
|
+
].join(' ');
|
|
33
|
+
}
|
|
34
|
+
exports.appCloseTool = {
|
|
35
|
+
schema: {
|
|
36
|
+
name: 'app_close',
|
|
37
|
+
description: 'Close one or more Windows processes by name (with or without the .exe suffix). Matches all running instances of that name. Set `force: true` to skip the app\'s graceful-shutdown prompt. Windows-only in v4.1.2.',
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
app: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
description: 'Process name (e.g. "notepad", "spotify"). The .exe suffix is stripped automatically. Matches ALL running instances of that name.',
|
|
44
|
+
},
|
|
45
|
+
force: {
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
description: 'If true, terminate without giving the app a chance to save unsaved work. Default false (graceful close).',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
required: ['app'],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
category: 'execute',
|
|
54
|
+
mutates: true,
|
|
55
|
+
toolset: 'system',
|
|
56
|
+
async execute(args, _ctx) {
|
|
57
|
+
if (!(0, _psHelpers_1.isWindows)())
|
|
58
|
+
return (0, _psHelpers_1.windowsOnlyError)('app_close');
|
|
59
|
+
const app = typeof args.app === 'string' ? normalise(args.app) : '';
|
|
60
|
+
if (!app) {
|
|
61
|
+
return { success: false, error: '`app` is required and must be non-empty.' };
|
|
62
|
+
}
|
|
63
|
+
const force = args.force === true;
|
|
64
|
+
try {
|
|
65
|
+
const { stdout } = await (0, _psHelpers_1.runPowerShell)(buildPs(app, force), {
|
|
66
|
+
timeoutMs: 10000,
|
|
67
|
+
});
|
|
68
|
+
const m = stdout.trim().match(/closed:(\d+)/);
|
|
69
|
+
const closed = m ? Number(m[1]) : 0;
|
|
70
|
+
return { success: true, app, closed, force };
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
error: e instanceof Error ? e.message : String(e),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -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
|
+
};
|