wisp-agents 0.24.0 → 0.25.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.md +44 -4
  3. package/bin/wisp.js +55 -1
  4. package/package.json +2 -2
  5. package/server/adapters/codex.js +3 -0
  6. package/server/client.js +2 -2
  7. package/server/index.js +17 -2
  8. package/server/mcp-ask.js +44 -2
  9. package/server/openapi.js +39 -1
  10. package/server/protocol.js +32 -3
  11. package/server/render.js +16 -1
  12. package/server/routes/automations.js +8 -5
  13. package/server/routes/internal.js +61 -0
  14. package/server/routes/meta.js +5 -1
  15. package/server/routes/settings.js +17 -3
  16. package/server/routes/spells.js +147 -0
  17. package/server/runner.js +10 -3
  18. package/server/services/launch.js +7 -2
  19. package/server/services/spell-hooks.js +99 -0
  20. package/server/spell-guide.js +135 -0
  21. package/server/spells-data.js +60 -0
  22. package/server/spells.js +647 -0
  23. package/server/update.js +50 -16
  24. package/web/css/feed.css +1 -1
  25. package/web/css/spells.css +205 -0
  26. package/web/index.html +4 -0
  27. package/web/js/app.js +3 -0
  28. package/web/js/blocks.js +7 -1
  29. package/web/js/features/actions.js +3 -0
  30. package/web/js/features/commands.js +11 -0
  31. package/web/js/features/composer.js +12 -0
  32. package/web/js/features/feed.js +3 -0
  33. package/web/js/features/keys.js +15 -0
  34. package/web/js/features/launcher.js +56 -11
  35. package/web/js/features/live.js +9 -1
  36. package/web/js/features/settings.js +21 -0
  37. package/web/js/features/slash.js +2 -1
  38. package/web/js/features/spell-actions.js +64 -0
  39. package/web/js/features/spell-ui.js +398 -0
  40. package/web/js/features/spells.js +715 -0
  41. package/web/js/features/turns.js +2 -0
  42. package/web/js/features/update.js +1 -2
  43. package/web/js/spell-schema.js +246 -0
@@ -0,0 +1,60 @@
1
+ // Spell data sources, fetched by wisp on a spell's behalf (spell code never fetches anything):
2
+ // { wisp: '/sessions' } a read-only GET of wisp's own API (an allowlist, see spell-schema.js)
3
+ // { gh: ['pr', 'list', …] } a read-only GitHub CLI command, in your home folder or the task's
4
+ // Each answer is cached for the source's `refresh` seconds and capped at MAX_BYTES.
5
+
6
+ import os from 'node:os';
7
+ import { gh } from './git.js';
8
+ import { fillTemplate, ghProblem, wispPathProblem } from '../web/js/spell-schema.js';
9
+ import { bad } from './util.js';
10
+
11
+ const MAX_BYTES = 1024 * 1024;
12
+ const TIMEOUT_MS = 20_000;
13
+
14
+ export function createSpellData(app) {
15
+ const cache = new Map(); // key -> { at, value }
16
+
17
+ async function fetchWisp(p) {
18
+ const e = wispPathProblem(p);
19
+ if (e) throw bad(e);
20
+ const res = await fetch(`http://127.0.0.1:${app.port}/api${p.replace(/^\/api(?=\/)/, '')}`, { signal: AbortSignal.timeout(TIMEOUT_MS) });
21
+ const text = await res.text();
22
+ if (text.length > MAX_BYTES) throw bad('That source returned more than 1 MB.');
23
+ if (!res.ok) throw bad(`wisp ${p}: ${res.status}`);
24
+ return JSON.parse(text);
25
+ }
26
+
27
+ async function fetchGh(args, cwd) {
28
+ const e = ghProblem(args);
29
+ if (e) throw bad(e);
30
+ let out;
31
+ try {
32
+ out = await gh(cwd, args, { timeout: TIMEOUT_MS });
33
+ } catch (err) {
34
+ throw bad(`gh ${args.slice(0, 2).join(' ')}: ${String(err.message || err).split('\n')[0]}`);
35
+ }
36
+ if (out.length > MAX_BYTES) throw bad('That source returned more than 1 MB.');
37
+ try {
38
+ return JSON.parse(out);
39
+ } catch {
40
+ return out;
41
+ }
42
+ }
43
+
44
+ // source: a normalized spell data source; session: the card's task, whose summary fills {field}
45
+ // templates in gh arguments ({pr.number}) and whose folder gh runs in with cwd: 'task'.
46
+ async function get(spellId, name, source, { session = null, summary = null, fresh = false } = {}) {
47
+ const cwd = source.cwd === 'task' && session ? session.worktree?.cwd || session.cwd : os.homedir();
48
+ const args = source.gh?.map((a) => fillTemplate(a, summary || {}));
49
+ const perTask = session && (source.cwd === 'task' || source.gh?.some((a) => /\{/.test(a)) || /\{/.test(source.wisp || ''));
50
+ const key = `${spellId}:${name}:${perTask ? session.id : ''}`;
51
+ const hit = cache.get(key);
52
+ if (!fresh && hit && Date.now() - hit.at < (source.refresh || 60) * 1000) return hit.value;
53
+ const value = source.wisp !== undefined ? await fetchWisp(fillTemplate(source.wisp, summary || {})) : await fetchGh(args, cwd);
54
+ cache.set(key, { at: Date.now(), value });
55
+ if (cache.size > 500) cache.delete(cache.keys().next().value);
56
+ return value;
57
+ }
58
+
59
+ return { get, clear: () => cache.clear() };
60
+ }