@akanjs/cli 3.0.0-beta.9 → 3.0.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.
- package/.build-stamp +1 -1
- package/CodeAgent-2vmp9qe0.js +9 -0
- package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
- package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
- package/CodeTui-5ed6bg2a.js +1457 -0
- package/README.ko.md +83 -28
- package/README.md +70 -27
- package/TunnelShare-cwh9ezsm.js +9 -0
- package/{agent.command-0q17pnrs.js → agent.command-gn73rhe7.js} +8 -9
- package/akanCode.js +102 -0
- package/application.command-e968t0hw.js +246 -0
- package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-64taxseg.js} +121 -77
- package/buildBatch.proc.js +120 -32
- package/cloud.command-0jm301rr.js +95 -0
- package/cloud.script-nemnzjpv.js +20 -0
- package/code.command-18kcm7hq.js +154 -0
- package/commandManifest.json +1 -1
- package/consoleToStderr-h5bryj94.js +7 -0
- package/context.command-trd9bn18.js +48 -0
- package/context.runner-y5hwh673.js +21 -0
- package/{dependencyScanner-shs82ynp.js → dependencyScanner-06z2dmrr.js} +2 -2
- package/{devTui-m1atgkc9.js → devTui-q7k7bakd.js} +122 -40
- package/fileSys-wzzrntkw.js +9 -0
- package/guideline.command-bdmdn2de.js +42 -0
- package/guidelines/agentRule/agentRule.instruction.md +154 -26
- package/guidelines/conventions/conventions.instruction.md +134 -48
- package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
- package/guidelines/queryRule/queryRule.instruction.md +21 -7
- package/guidelines/runtimeRule/runtimeRule.instruction.md +291 -6
- package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
- package/guidelines/transportRule/transportRule.instruction.md +10 -8
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +20 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
- package/incrementalBuilder.proc.js +564 -109
- package/index-0wpybxtr.js +2729 -0
- package/{index-kcxmv48c.js → index-2ahj896a.js} +45 -79
- package/index-2wc41k5w.js +49 -0
- package/index-3hx1z96c.js +87 -0
- package/{index-2tv3fdx4.js → index-3w94e7n3.js} +1228 -1458
- package/{index-hwkygn9s.js → index-48k96p55.js} +325 -36
- package/index-4npz05nm.js +187 -0
- package/{index-q0y2jq9j.js → index-52w0fvh8.js} +21 -4
- package/index-6he6gvfg.js +4300 -0
- package/index-7md2g9vw.js +6 -0
- package/{index-04bk599g.js → index-93j60em0.js} +10 -17
- package/index-94s080qm.js +266 -0
- package/index-9dp031by.js +12 -0
- package/{index-ktb5njgy.js → index-9z771ptz.js} +49 -99
- package/{index-y6n8nmny.js → index-abxpehf8.js} +75 -203
- package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
- package/{index-9077wzd2.js → index-by9pxkr0.js} +43 -26
- package/index-c6eggbee.js +123 -0
- package/index-d2242ksy.js +99 -0
- package/index-drehwpdt.js +1119 -0
- package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
- package/{index-ffvk10zz.js → index-f3c78nd6.js} +107 -241
- package/index-gbggbqcp.js +1058 -0
- package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
- package/index-hsmtysz4.js +53 -0
- package/{index-j9yhg404.js → index-jmrn54tt.js} +4 -7
- package/{index-a64esmtb.js → index-kcdj1z5v.js} +523 -1022
- package/{index-d43r08tf.js → index-kqabytj7.js} +77 -150
- package/{index-vyvre9en.js → index-mbxbhay5.js} +6 -2
- package/{index-vxtad5bd.js → index-mgxpx3f2.js} +5 -8
- package/index-p6pbz9n5.js +25 -0
- package/{index-sj5vxr8f.js → index-p993efkq.js} +981 -897
- package/{index-2xp0kfkt.js → index-pmhqttv3.js} +37 -25
- package/index-tqm7jjcn.js +700 -0
- package/{index-0g7r8xz3.js → index-vpj42xwt.js} +114 -232
- package/index-wa5jbxj1.js +760 -0
- package/{index-k2j1hc4r.js → index-wprnsjms.js} +12 -12
- package/{index-gyd220p2.js → index-y3mphr1m.js} +554 -577
- package/index-yg7jhz8x.js +69 -0
- package/{index-h7yw2z9n.js → index-zzvvc845.js} +8 -14
- package/index.js +23 -22
- package/{library.command-qzzbj6d8.js → library.command-9h749a1x.js} +4 -5
- package/{localRegistry.command-vyrgg4xw.js → localRegistry.command-2bqqhxyz.js} +23 -43
- package/{module.command-rq5hq3ax.js → module.command-nv8nzgft.js} +8 -12
- package/{package.command-d9hd6v9n.js → package.command-mq4bggp6.js} +6 -7
- package/package.json +15 -7
- package/{page.command-126d56te.js → page.command-j639n707.js} +4 -5
- package/{primitive.command-taxd1538.js → primitive.command-kz6849th.js} +9 -23
- package/prompter-76k56b37.js +8 -0
- package/{quality.command-6gsna0a3.js → quality.command-4hyabwkb.js} +205 -377
- package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
- package/{repair.command-tq2dgy72.js → repair.command-a9awpbqr.js} +11 -35
- package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
- package/{scalar.command-3bcmrrkv.js → scalar.command-ma4fpm9m.js} +7 -11
- package/skills/akan-code/SKILL.md +130 -0
- package/skills/akan-debug/SKILL.md +42 -0
- package/skills/akan-deps/SKILL.md +50 -0
- package/skills/akan-err/SKILL.md +39 -0
- package/skills/akan-module/SKILL.md +45 -0
- package/skills/akan-scaffold/SKILL.md +76 -0
- package/skills/akan-scalar/SKILL.md +52 -0
- package/skills/akan-service/SKILL.md +34 -0
- package/skills/akan-store/SKILL.md +67 -0
- package/skills/akan-ui/SKILL.md +63 -0
- package/skills/akan-validate/SKILL.md +44 -0
- package/{subspace.command-eddzqpx3.js → subspace.command-3k2pmrs4.js} +53 -62
- package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
- package/templates/app/page/_index.tsx +257 -258
- package/templates/app/page/_layout.tsx +5 -1
- package/templates/app/page/_overrides.tsx +12 -0
- package/templates/app/page/styles.css.template +316 -14
- package/templates/app/public/apple-touch-icon.png +0 -0
- package/templates/app/public/favicon.ico +0 -0
- package/templates/app/public/jelly/cloud.webp +0 -0
- package/templates/app/public/jelly/comet.webp +0 -0
- package/templates/app/public/jelly/moon.webp +0 -0
- package/templates/app/public/jelly/planet.webp +0 -0
- package/templates/app/public/jelly/rocket.webp +0 -0
- package/templates/app/public/jelly/star.webp +0 -0
- package/templates/app/public/jelly/sugar.webp +0 -0
- package/templates/app/public/logo.png +0 -0
- package/templates/app/ui/Recipe/index.ts +9 -0
- package/templates/app/ui/Recipe/jellyButton.ts +63 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
- package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
- package/templates/appSample/lib/task/Task.Unit.tsx +1 -1
- package/templates/appSample/lib/task/Task.Util.tsx +10 -13
- package/templates/appSample/lib/task/Task.View.tsx +2 -2
- package/templates/appSample/lib/task/Task.Zone.tsx +5 -2
- package/templates/appSample/lib/task/task.abstract.ts +12 -32
- package/templates/appSample/lib/task/task.signal.spec.ts +1 -15
- package/templates/appSample/lib/task/task.signal.test.ts +15 -1
- package/templates/appSample/lib/task/task.signal.ts +10 -8
- package/templates/appSample/page/task/[taskId]/_index.tsx +6 -6
- package/templates/appSample/page/task/[taskId]/edit.tsx +5 -4
- package/templates/appSample/page/task/_index.tsx +4 -3
- package/templates/appSample/page/task/_layout.tsx +1 -1
- package/templates/appSample/page/task/new.tsx +5 -4
- package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
- package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
- package/templates/appSample/ui/GlobalLoading.tsx +2 -1
- package/templates/appSample/ui/QuantityControl.tsx +3 -3
- package/templates/client.ts +7 -30
- package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
- package/templates/crudPages/[__model__Id]/page.tsx +2 -1
- package/templates/crudPages/new/page.tsx +2 -1
- package/templates/crudPages/page.tsx +2 -1
- package/templates/crudSinglePage/page.tsx +2 -1
- package/templates/lib/__lib/lib.dictionary.ts +0 -2
- package/templates/lib/__lib/lib.document.ts +0 -2
- package/templates/lib/sig.ts +8 -30
- package/templates/localDev/docker-compose.yaml.template +3 -14
- package/templates/module/__model__.signal.ts +12 -4
- package/templates/pkgRoot/tsconfig.json.template +2 -7
- package/templates/server.ts +1 -3
- package/templates/service/__model__.abstract.md.template +8 -0
- package/templates/workspaceRoot/.gitignore.template +0 -1
- package/templates/workspaceRoot/AGENTS.md.template +6 -0
- package/templates/workspaceRoot/package.json.template +1 -3
- package/tunnel.command-xwr46b66.js +129 -0
- package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/workflow.command-t7wqe4h7.js +79 -0
- package/{workspace.command-06wvstgr.js → workspace.command-4derhzrh.js} +79 -109
- package/abstractCompactor-gmkc0z8j.js +0 -102
- package/application.command-18y0s485.js +0 -204
- package/applicationReleasePackager-4yasxwv9.js +0 -244
- package/capacitorApp-36rbr858.js +0 -12
- package/cloud.command-av97ndh6.js +0 -94
- package/cloud.script-cm11xjdz.js +0 -20
- package/context.command-5v37ef3n.js +0 -81
- package/devTuiView-0hc2emhe.js +0 -9
- package/getDirname-dr3jz1xg.js +0 -7
- package/guideline.command-4pz9g1rt.js +0 -360
- package/guidelines/agentRule/agentRule.generate.json +0 -31
- package/guidelines/componentRule/componentRule.generate.json +0 -31
- package/guidelines/conventions/conventions.generate.json +0 -25
- package/guidelines/cssRule/cssRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
- package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
- package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
- package/guidelines/enumConstant/enumConstant.generate.json +0 -31
- package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
- package/guidelines/fieldRule/fieldRule.generate.json +0 -31
- package/guidelines/framework/framework.generate.json +0 -32
- package/guidelines/mcpRule/mcpRule.generate.json +0 -35
- package/guidelines/modelConstant/modelConstant.generate.json +0 -32
- package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
- package/guidelines/modelDocument/modelDocument.generate.json +0 -31
- package/guidelines/modelService/modelService.generate.json +0 -31
- package/guidelines/modelSignal/modelSignal.generate.json +0 -31
- package/guidelines/modelStore/modelStore.generate.json +0 -31
- package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
- package/guidelines/modelUnit/modelUnit.generate.json +0 -31
- package/guidelines/modelUtil/modelUtil.generate.json +0 -31
- package/guidelines/modelView/modelView.generate.json +0 -31
- package/guidelines/modelZone/modelZone.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
- package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
- package/guidelines/queryRule/queryRule.generate.json +0 -31
- package/guidelines/recipeRule/recipeRule.generate.json +0 -31
- package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
- package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
- package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
- package/guidelines/scalarModule/scalarModule.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
- package/guidelines/ssrRule/ssrRule.generate.json +0 -31
- package/guidelines/transportRule/transportRule.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
- package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
- package/index-47pj3aee.js +0 -76
- package/index-5ystnsd5.js +0 -841
- package/index-6wwzs1bc.js +0 -7
- package/index-a17x49vg.js +0 -229
- package/index-m0xccmmm.js +0 -228
- package/index-meh558jy.js +0 -65
- package/index-rtxxan88.js +0 -471
- package/index-te54x9zm.js +0 -338
- package/index-w8kmx1b6.js +0 -2176
- package/index-wt7qrtd4.js +0 -1516
- package/templates/__scalar/__model__/__model__.abstract.md +0 -28
- package/templates/module/__model__.abstract.md +0 -28
- package/templates/service/__model__.abstract.md +0 -28
- package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
- package/workflow.command-gtdnzvbz.js +0 -108
|
@@ -1,37 +1,17 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "./index-
|
|
3
|
+
stripAnsi
|
|
4
|
+
} from "./index-7md2g9vw.js";
|
|
5
|
+
import {
|
|
6
|
+
writeClipboard
|
|
7
|
+
} from "./index-p6pbz9n5.js";
|
|
8
|
+
import {
|
|
9
|
+
openBrowser
|
|
10
|
+
} from "./index-9dp031by.js";
|
|
8
11
|
|
|
9
12
|
// pkgs/@akanjs/cli/application/devTui.tsx
|
|
10
13
|
import { render } from "ink";
|
|
11
14
|
|
|
12
|
-
// pkgs/@akanjs/cli/clipboard.ts
|
|
13
|
-
var clipboardCommands = () => {
|
|
14
|
-
if (process.platform === "darwin")
|
|
15
|
-
return [["pbcopy"]];
|
|
16
|
-
if (process.platform === "win32")
|
|
17
|
-
return [["clip"]];
|
|
18
|
-
return [["wl-copy"], ["xclip", "-selection", "clipboard"], ["xsel", "--clipboard", "--input"]];
|
|
19
|
-
};
|
|
20
|
-
var writeClipboard = async (text) => {
|
|
21
|
-
for (const command of clipboardCommands()) {
|
|
22
|
-
try {
|
|
23
|
-
const proc = Bun.spawn(command, {
|
|
24
|
-
stdin: new TextEncoder().encode(text),
|
|
25
|
-
stdout: "ignore",
|
|
26
|
-
stderr: "ignore"
|
|
27
|
-
});
|
|
28
|
-
if (await proc.exited === 0)
|
|
29
|
-
return true;
|
|
30
|
-
} catch {}
|
|
31
|
-
}
|
|
32
|
-
return false;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
15
|
// pkgs/@akanjs/cli/ui/DevTuiApp.tsx
|
|
36
16
|
import { Box, Text, useInput } from "ink";
|
|
37
17
|
import { useEffect, useState } from "react";
|
|
@@ -176,6 +156,8 @@ var DevTuiApp = ({ actions }) => {
|
|
|
176
156
|
actions.copyLines();
|
|
177
157
|
else if (input === "Y")
|
|
178
158
|
actions.copyPath();
|
|
159
|
+
else if (input === "s")
|
|
160
|
+
actions.copyShareUrl();
|
|
179
161
|
else if (input === "o")
|
|
180
162
|
actions.openSelected();
|
|
181
163
|
else if (input === "r")
|
|
@@ -236,12 +218,95 @@ var DevTuiApp = ({ actions }) => {
|
|
|
236
218
|
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
|
|
237
219
|
dimColor: true,
|
|
238
220
|
wrap: "truncate",
|
|
239
|
-
children:
|
|
221
|
+
children: `${snapshot.hasShare ? "s share \xB7 " : ""}Tab next \xB7 1-9 app \xB7 \u2191\u2193 scroll (shift page) \xB7 G follow${snapshot.following ? "" : " (paused)"} \xB7 / grep${snapshot.grep ? ` (${snapshot.grep})` : ""} \xB7 e errors${snapshot.errorsOnly ? " on" : ""} \xB7 c clear \xB7 y copy \xB7 Y path \xB7 o open \xB7 r restart \xB7 q quit`
|
|
240
222
|
}, undefined, false, undefined, this)
|
|
241
223
|
]
|
|
242
224
|
}, undefined, true, undefined, this);
|
|
243
225
|
};
|
|
244
226
|
|
|
227
|
+
// pkgs/@akanjs/cli/application/devLogBuffer.ts
|
|
228
|
+
var plainTextOf = (lines, { withApp = false } = {}) => {
|
|
229
|
+
const width = withApp ? lines.reduce((max, line) => Math.max(max, line.app.length), 0) : 0;
|
|
230
|
+
return lines.map((line) => `${withApp ? `${line.app.padEnd(width)} \u2502 ` : ""}${stripAnsi(line.text)}`).join(`
|
|
231
|
+
`);
|
|
232
|
+
};
|
|
233
|
+
var HOST_SOURCE = "host";
|
|
234
|
+
var childPrefix = /^\[child:(\d+) ([a-z-]+)\] \[(?:stdout|stderr)\] /;
|
|
235
|
+
var sourceOf = (text) => {
|
|
236
|
+
const match = childPrefix.exec(text);
|
|
237
|
+
return match ? `#${match[1]} ${match[2]}` : HOST_SOURCE;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
class DevLogBuffer {
|
|
241
|
+
static defaultLimit = 5000;
|
|
242
|
+
#limit;
|
|
243
|
+
#lines = [];
|
|
244
|
+
#partial = new Map;
|
|
245
|
+
#sources = new Map;
|
|
246
|
+
#seq = 0;
|
|
247
|
+
constructor({ limit = DevLogBuffer.defaultLimit } = {}) {
|
|
248
|
+
this.#limit = Math.max(1, limit);
|
|
249
|
+
}
|
|
250
|
+
get size() {
|
|
251
|
+
return this.#lines.length;
|
|
252
|
+
}
|
|
253
|
+
sourcesOf(app) {
|
|
254
|
+
return [...this.#sources.get(app) ?? []];
|
|
255
|
+
}
|
|
256
|
+
push(app, kind, chunk) {
|
|
257
|
+
const key = `${app}:${kind}`;
|
|
258
|
+
const parts = `${this.#partial.get(key) ?? ""}${chunk}`.split(`
|
|
259
|
+
`);
|
|
260
|
+
this.#partial.set(key, parts.pop() ?? "");
|
|
261
|
+
return this.#append(app, kind, parts);
|
|
262
|
+
}
|
|
263
|
+
flushPartials() {
|
|
264
|
+
const flushed = [];
|
|
265
|
+
for (const [key, remainder] of [...this.#partial]) {
|
|
266
|
+
this.#partial.set(key, "");
|
|
267
|
+
if (!remainder)
|
|
268
|
+
continue;
|
|
269
|
+
const split = key.lastIndexOf(":");
|
|
270
|
+
const app = key.slice(0, split);
|
|
271
|
+
const kind = key.slice(split + 1) === "stderr" ? "stderr" : "stdout";
|
|
272
|
+
flushed.push(...this.#append(app, kind, [remainder]));
|
|
273
|
+
}
|
|
274
|
+
return flushed;
|
|
275
|
+
}
|
|
276
|
+
select({ app = null, source = null, grep, errorsOnly = false } = {}) {
|
|
277
|
+
const needle = grep?.trim().toLowerCase();
|
|
278
|
+
return this.#lines.filter((line) => {
|
|
279
|
+
if (app !== null && line.app !== app)
|
|
280
|
+
return false;
|
|
281
|
+
if (source !== null && line.source !== source)
|
|
282
|
+
return false;
|
|
283
|
+
if (errorsOnly && line.kind !== "stderr")
|
|
284
|
+
return false;
|
|
285
|
+
if (needle && !stripAnsi(line.text).toLowerCase().includes(needle))
|
|
286
|
+
return false;
|
|
287
|
+
return true;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
clear(app = null) {
|
|
291
|
+
const kept = app === null ? [] : this.#lines.filter((line) => line.app !== app);
|
|
292
|
+
this.#lines.length = 0;
|
|
293
|
+
this.#lines.push(...kept);
|
|
294
|
+
}
|
|
295
|
+
#append(app, kind, texts) {
|
|
296
|
+
const known = this.#sources.get(app) ?? new Set;
|
|
297
|
+
this.#sources.set(app, known);
|
|
298
|
+
const added = texts.map((text) => {
|
|
299
|
+
const source = sourceOf(text);
|
|
300
|
+
known.add(source);
|
|
301
|
+
return { seq: ++this.#seq, app, source, kind, text };
|
|
302
|
+
});
|
|
303
|
+
this.#lines.push(...added);
|
|
304
|
+
if (this.#lines.length > this.#limit)
|
|
305
|
+
this.#lines.splice(0, this.#lines.length - this.#limit);
|
|
306
|
+
return added;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
245
310
|
// pkgs/@akanjs/cli/application/devLogWindow.ts
|
|
246
311
|
var windowOf = (lines, rows, anchor) => {
|
|
247
312
|
const height = Math.max(1, Math.trunc(rows));
|
|
@@ -313,6 +378,7 @@ class DevTui {
|
|
|
313
378
|
clear: this.#clear,
|
|
314
379
|
copyLines: this.#copyLines,
|
|
315
380
|
copyPath: this.#copyPath,
|
|
381
|
+
copyShareUrl: this.#copyShareUrl,
|
|
316
382
|
openSelected: this.#openSelected,
|
|
317
383
|
restartSelected: this.#restartSelected,
|
|
318
384
|
quit: this.#quit
|
|
@@ -348,7 +414,7 @@ class DevTui {
|
|
|
348
414
|
process.stdout.write(`${line.app} \u2502 ${line.text}
|
|
349
415
|
`);
|
|
350
416
|
for (const status of this.#statuses)
|
|
351
|
-
process.stdout.write(`${status.name} ${status.url} \u2014 ${status.state}
|
|
417
|
+
process.stdout.write(`${status.name} ${status.url}${status.shareUrl ? ` \xB7 ${status.shareUrl}` : ""} \u2014 ${status.state}
|
|
352
418
|
`);
|
|
353
419
|
const log = this.#supervisor.sessionLog;
|
|
354
420
|
for (const name of log.appNames)
|
|
@@ -403,12 +469,12 @@ class DevTui {
|
|
|
403
469
|
const logRows = Math.max(1, bodyHeight - DevTui.paneChromeRows);
|
|
404
470
|
const rows = this.#railRows();
|
|
405
471
|
const view = windowOf(this.#filtered(this.#target), logRows, this.#anchor);
|
|
406
|
-
const status = this.#
|
|
472
|
+
const status = this.#selectedStatus();
|
|
407
473
|
const railLabelWidth = rows.reduce((max, row) => Math.max(max, row.label.length + row.depth * 2), 0);
|
|
408
474
|
this.#cachedSnapshot = {
|
|
409
475
|
rows,
|
|
410
476
|
selected: this.#indexOfTarget(rows),
|
|
411
|
-
title: status ? `${status.name}${this.#target.source ? ` \xB7 ${this.#target.source}` : ""} \xB7 ${status.url} \xB7 ${status.state}${status.detail ? ` (${status.detail})` : ""}` :
|
|
477
|
+
title: status ? `${status.name}${this.#target.source ? ` \xB7 ${this.#target.source}` : ""}${status.shareUrl ? ` \xB7 \u25C8 ${status.shareUrl}` : ""} \xB7 ${status.url} \xB7 ${status.state}${status.detail ? ` (${status.detail})` : ""}` : this.#mergedTitle(),
|
|
412
478
|
lines: view.lines,
|
|
413
479
|
above: view.above,
|
|
414
480
|
below: view.below,
|
|
@@ -419,6 +485,7 @@ class DevTui {
|
|
|
419
485
|
errorsOnly: this.#errorsOnly,
|
|
420
486
|
editingGrep: this.#editingGrep,
|
|
421
487
|
notice: this.#notice,
|
|
488
|
+
hasShare: this.#statuses.some((candidate) => !!candidate.shareUrl),
|
|
422
489
|
readyCount: this.#statuses.filter((candidate) => candidate.state === "ready").length,
|
|
423
490
|
appCount: this.#statuses.length,
|
|
424
491
|
logRows,
|
|
@@ -467,8 +534,7 @@ class DevTui {
|
|
|
467
534
|
};
|
|
468
535
|
#setTarget(target) {
|
|
469
536
|
this.#target = { app: target.app ?? null, source: target.source ?? null };
|
|
470
|
-
this.#
|
|
471
|
-
this.#renderNow();
|
|
537
|
+
this.#follow();
|
|
472
538
|
}
|
|
473
539
|
#scroll = (delta) => {
|
|
474
540
|
this.#anchor = scrollAnchor(this.#filtered(this.#target), this.#snapshot().logRows, this.#anchor, delta);
|
|
@@ -480,8 +546,7 @@ class DevTui {
|
|
|
480
546
|
};
|
|
481
547
|
#setGrep = (grep) => {
|
|
482
548
|
this.#grep = grep;
|
|
483
|
-
this.#
|
|
484
|
-
this.#renderNow();
|
|
549
|
+
this.#follow();
|
|
485
550
|
};
|
|
486
551
|
#setEditingGrep = (editing) => {
|
|
487
552
|
this.#editingGrep = editing;
|
|
@@ -489,13 +554,11 @@ class DevTui {
|
|
|
489
554
|
};
|
|
490
555
|
#toggleErrorsOnly = () => {
|
|
491
556
|
this.#errorsOnly = !this.#errorsOnly;
|
|
492
|
-
this.#
|
|
493
|
-
this.#renderNow();
|
|
557
|
+
this.#follow();
|
|
494
558
|
};
|
|
495
559
|
#clear = () => {
|
|
496
560
|
this.#buffer.clear(this.#target.app ?? null);
|
|
497
|
-
this.#
|
|
498
|
-
this.#renderNow();
|
|
561
|
+
this.#follow();
|
|
499
562
|
};
|
|
500
563
|
#copyLines = () => {
|
|
501
564
|
const lines = this.#filtered(this.#target);
|
|
@@ -520,6 +583,25 @@ class DevTui {
|
|
|
520
583
|
const app = this.#target.app;
|
|
521
584
|
return (app ? [app] : log.appNames).map((name) => log.relativePathOf(name));
|
|
522
585
|
}
|
|
586
|
+
#copyShareUrl = () => {
|
|
587
|
+
const urls = this.#shareUrls();
|
|
588
|
+
if (urls.length === 0) {
|
|
589
|
+
this.#setNotice("no public share \u2014 start with --share");
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
writeClipboard(urls.join(`
|
|
593
|
+
`)).then((copied) => {
|
|
594
|
+
this.#setNotice(copied ? `copied ${urls.join(" \xB7 ")}` : urls.join(" \xB7 "));
|
|
595
|
+
});
|
|
596
|
+
};
|
|
597
|
+
#shareUrls() {
|
|
598
|
+
const app = this.#target.app;
|
|
599
|
+
return this.#statuses.filter((status) => app === null || status.name === app).map((status) => status.shareUrl).filter((url) => !!url);
|
|
600
|
+
}
|
|
601
|
+
#mergedTitle() {
|
|
602
|
+
const urls = this.#shareUrls();
|
|
603
|
+
return urls.length === 0 ? "all apps" : `all apps \xB7 \u25C8 ${urls.join(" \xB7 ")}`;
|
|
604
|
+
}
|
|
523
605
|
#setNotice(text) {
|
|
524
606
|
this.#notice = text;
|
|
525
607
|
if (this.#noticeTimer)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
script,
|
|
4
|
+
command
|
|
5
|
+
} from "./index-vpj42xwt.js";
|
|
6
|
+
import"./index-52w0fvh8.js";
|
|
7
|
+
import"./index-3w94e7n3.js";
|
|
8
|
+
import {
|
|
9
|
+
Prompter2
|
|
10
|
+
} from "./index-zzvvc845.js";
|
|
11
|
+
|
|
12
|
+
// pkgs/@akanjs/cli/guideline/guideline.script.ts
|
|
13
|
+
import { Logger } from "akanjs/common";
|
|
14
|
+
|
|
15
|
+
class GuidelineScript extends script("guideline") {
|
|
16
|
+
async guideline(action, name = null) {
|
|
17
|
+
if (action === "list") {
|
|
18
|
+
const guidelines = await Prompter2.listGuidelines();
|
|
19
|
+
Logger.rawLog(guidelines.join(`
|
|
20
|
+
`));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (action === "show") {
|
|
24
|
+
if (!name)
|
|
25
|
+
throw new Error("Guideline name is required. Example: akan guideline show framework");
|
|
26
|
+
Logger.rawLog(await Prompter2.getInstruction(name));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`Unknown guideline action: ${action}. Use "list" or "show".`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// pkgs/@akanjs/cli/guideline/guideline.command.ts
|
|
34
|
+
class GuidelineCommand extends command("guideline", [GuidelineScript], ({ public: target }) => ({
|
|
35
|
+
guideline: target({ desc: "List or show Akan AI guideline instructions" }).arg("action", String, { desc: "list or show" }).arg("name", String, { desc: "guideline name for show", nullable: true }).exec(async function(action, name) {
|
|
36
|
+
await this.guidelineScript.guideline(action, name);
|
|
37
|
+
})
|
|
38
|
+
})) {
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
GuidelineCommand
|
|
42
|
+
};
|
|
@@ -15,6 +15,13 @@ for. `Load` scopes, the route, and the live keys complete the context. The React
|
|
|
15
15
|
apps and libs never import it directly (`no-import-external-library`) — everything reaches them through `st.*` and
|
|
16
16
|
`akanjs/ui`.
|
|
17
17
|
|
|
18
|
+
**Mounted is not published: on a CSR stack only the current page is.** The page kept under the current one for a
|
|
19
|
+
swipe back stays mounted and live, and its declarations stay registered, but its tools, resources, guides and
|
|
20
|
+
`st.use` keys are parked until it is current again — the agent sees the screen the user sees, never two. Each page
|
|
21
|
+
container renders `use-agentic`'s `<AgentActivity active>` for this; a parked registration shadows nothing, two
|
|
22
|
+
pages declaring one name never clash, and a registry kept outside the surface reads the same gate through
|
|
23
|
+
`useAgentGate()`, as the store's live keys do.
|
|
24
|
+
|
|
18
25
|
## Mounting The Chat
|
|
19
26
|
- **Mount `<Agent.Chat />` once in a layout.** That is the floating chat, the approval card, and the client-side
|
|
20
27
|
loop. The default runner drives `runAgentTurn`, which the **framework serves on every app** — no lib to mount,
|
|
@@ -45,10 +52,14 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
45
52
|
clear and close. `chrome={false}` drops the header bar whole, for an `inline` chat inside a panel the app
|
|
46
53
|
already titles, and `defaultDraft` opens the composer with text in it without sending it. A panel driven by a
|
|
47
54
|
controlled `open` with **no** `onOpenChange` draws no close button at all rather than an inert one. Then the
|
|
48
|
-
slots: `AgentLauncher`, `AgentBubble`, `AgentComposer`, `AgentApproval`,
|
|
49
|
-
`AgentQuestion`, `AgentQueued`, `AgentMenu`, `AgentMarkdown` and `AgentCode` each replace one part in `_overrides.tsx`, and
|
|
55
|
+
slots: `AgentLauncher`, `AgentSteps`, `AgentBubble`, `AgentComposer`, `AgentApproval`,
|
|
56
|
+
`AgentQuestion`, `AgentToolCard`, `AgentQueued`, `AgentMenu`, `AgentMarkdown` and `AgentCode` each replace one part in `_overrides.tsx`, and
|
|
50
57
|
`akanjs/ui` exports every default beside them (`DefaultBubble`, `DefaultComposer`, …) so a skin composes the
|
|
51
|
-
one it is replacing.
|
|
58
|
+
one it is replacing. **`AgentSteps` is one agent turn** — the messages between the user message that opened it
|
|
59
|
+
and the next one, with `isRunning` saying whether more are still coming — which is the grain a chat needs to
|
|
60
|
+
fold a turn's steps into a `details` and stand its answer outside them. It is the one boundary a per-message
|
|
61
|
+
slot cannot see, since neither message on either side of it knows it is at an edge. The default draws the turn's
|
|
62
|
+
messages flat into a Fragment, so a chat that binds nothing renders exactly what it did before the slot existed. The attachment row is exported on its own as `AgentAttachments`, because a replaced
|
|
52
63
|
`AgentBubble` that redraws it by hand forks the rule for which carrier is shown how — and then keeps whatever
|
|
53
64
|
that rule was on the day it was copied. `AgentCode` is where a highlighter binds — the fence's language reaches it — and a
|
|
54
65
|
replacement for `AgentBubble` carries its own `memo`, since the transcript re-renders on every delta. Only then
|
|
@@ -72,24 +83,41 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
72
83
|
- **The LLM is configured in `option.ts`, never through the environment.** `option.setLlm({ apiKey, model, host })`
|
|
73
84
|
— or `setLlm((options) => …)` to read the key out of the app's own env object, which is where a secret belongs —
|
|
74
85
|
fills whichever adaptor holds `LlmAdaptorRole`, reaching it as the `llmOption` use. The settings are the role's
|
|
75
|
-
rather than one provider's, so they survive a swap.
|
|
76
|
-
`https://api.
|
|
77
|
-
providers the way middleware is applied:
|
|
78
|
-
write its own — an `adapt()` class in a
|
|
79
|
-
ignoring `onDelta` still answers whole —
|
|
80
|
-
|
|
81
|
-
`
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
rather than one provider's, so they survive a swap. **`OpenaiLlm` is the built-in default**, pointed at
|
|
87
|
+
`https://api.openai.com/v1`; with no `apiKey` or no `model` the app still boots and the chat answers
|
|
88
|
+
`llmUnavailable`. Swap providers the way middleware is applied:
|
|
89
|
+
`option.applyAdaptor(LlmAdaptorRole, AnthropicLlm)`. An app may still write its own — an `adapt()` class in a
|
|
90
|
+
`srvkit/` implementing `LlmAdaptor.chat(request, onDelta?)`, where ignoring `onDelta` still answers whole — and
|
|
91
|
+
whatever settings it needs beyond `LlmOption` travel through the same `setLlm`, read back with
|
|
92
|
+
`use<MyLlmOption>()` from an interface extending it.
|
|
93
|
+
- **Two adaptors ship, one per wire — a vendor is a host and a model name, not a protocol.** `OpenaiLlm` is the
|
|
94
|
+
chat-completions dialect (`OpenaiDialect`) pointed at whatever `host` names, so OpenAI, DeepSeek, Groq,
|
|
95
|
+
Together, OpenRouter, Ollama and a self-hosted vLLM are all one class and one `option.setLlm({ host, model })`.
|
|
96
|
+
`AnthropicLlm` is the Messages API and declares `{ image: true, document: true }`; it is its own file rather
|
|
97
|
+
than a branch, because the system prompt is a field and not a message, tool calls and results are content
|
|
98
|
+
blocks, the results ride in a *user* turn, roles must alternate — so two wire turns that map to one role are
|
|
99
|
+
merged — and `max_tokens` is required. **Both require `model`**: a default would age into a 404, and worse, it
|
|
100
|
+
would decide the vision claim for the app.
|
|
101
|
+
- **`OpenaiLlm` claims vision for its default host and for nothing else.** OpenAI's own endpoint takes image
|
|
102
|
+
parts, so that is what it answers with no `host` set. A `host` the app named is a gateway the class knows
|
|
103
|
+
nothing about, and it stays text-only until `option.setLlm({ accepts })` says otherwise — handing bytes to a
|
|
104
|
+
model that cannot decode them kills the whole turn on a 400, where text-only degrades them to a note the model
|
|
105
|
+
can repeat back.
|
|
106
|
+
- **A refusal is `agent.error.llmRequestFailed`, and it names the host** — unless the prompt did not fit. The
|
|
107
|
+
provider's own sentence rides in `reason` and the hostname in `provider`. `LlmOverflow.refusal(host, status,
|
|
108
|
+
reason)` builds it, and reads the one refusal a chat can undo — a prompt past the window, recognized from each
|
|
109
|
+
provider's own sentence — as `agent.error.contextOverflow` with the `limit` it named, which the relay flags so
|
|
110
|
+
the chat compacts and asks again. An adaptor an app wrote throws through the same function, so its refusals
|
|
111
|
+
translate and recover the way the shipped ones do without a key in the framework's dictionary it cannot add.
|
|
88
112
|
- **`accepts` is answered per model, through `option.setLlm({ accepts })`.** An adaptor answers for an API and one
|
|
89
113
|
API serves models that differ, so the override rides beside the `model` it is a fact about. It is not a table
|
|
90
114
|
the framework keeps: a table is a claim about models that ship after it, and getting this wrong is the worst
|
|
91
115
|
failure available — a provider handed bytes it cannot decode either refuses the turn or accepts it having seen
|
|
92
116
|
nothing, and the model then answers confidently about a file it never read.
|
|
117
|
+
- **`contextWindow` is declared the same way: `option.setLlm({ contextWindow })`.** Every turn relays it with the
|
|
118
|
+
answer ceiling the adaptor actually sends (`LlmAdaptor.limits` — `AnthropicLlm` always sends one, `OpenaiLlm`
|
|
119
|
+
none), and the chat guards its window with it. It is not a table either; a model id is retired or renamed long
|
|
120
|
+
before a table notices.
|
|
93
121
|
- **An adaptor answers `null` for "not configured" and *throws* for a refusal it can explain.** The two are
|
|
94
122
|
different things to be told: collapsing both into `null`, the way the adapter convention otherwise reads, left a
|
|
95
123
|
user reading `llmUnavailable` — "no model is configured" — about a conversation that had merely outgrown the
|
|
@@ -160,9 +188,9 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
160
188
|
there: a voice user who is never told about the card is a conversation that simply ends.
|
|
161
189
|
The contract is a subscription rather than `listen(): Promise<string>` on purpose: a promise fits push-to-talk
|
|
162
190
|
and nothing else, so hands-free could then only arrive as a breaking change. `useSpeech` in a util lib's
|
|
163
|
-
`webkit/` is the engine — the browser's own recognition and synthesis
|
|
164
|
-
|
|
165
|
-
|
|
191
|
+
`webkit/` is the engine — the browser's own recognition and synthesis. An app's WebView **has neither on
|
|
192
|
+
Android or iOS** and `@akanjs/native` ships no speech plugin yet, so a `speech.plugin.ts` declares only the
|
|
193
|
+
permission and its usage texts. An engine answering `available()` false renders no microphone at all,
|
|
166
194
|
the same rule as publishing no tool for a control the screen does not draw.
|
|
167
195
|
- **Data the user points at rides the message too, as `references` — and its value is a snapshot.** A file is
|
|
168
196
|
something they handed over; a reference is something they pointed at while asking, so it belongs in the same
|
|
@@ -201,6 +229,15 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
201
229
|
is called from components the composer cannot see; writing the token is the composer's half, and `refer` warns
|
|
202
230
|
rather than staging silently when no chat is mounted on that session — the case where a card sits outside the
|
|
203
231
|
`<Agent.Zone>` its chat is inside.
|
|
232
|
+
- **The composer draws a pointer as the name it points at, never as the token.** Where `reference` sources are
|
|
233
|
+
declared the input is a Lexical editor rather than a textarea, and each pointer is one atomic node — a backspace
|
|
234
|
+
takes the whole thing rather than a character of a label that would then name nothing. Nothing above it changes:
|
|
235
|
+
the node's text content *is* the `@[…](mention:…)` token, so the draft string the chat reads, the `@` query, the
|
|
236
|
+
chips, the wire and the server's framing all see what they always saw, and every offset the chat hands over is
|
|
237
|
+
an offset into that string. It is its own chunk behind the chat's, fetched only where a source was declared, and
|
|
238
|
+
`mentions={false}` keeps the plain textarea — for an app that overrides `AgentComposer`, or one that would
|
|
239
|
+
rather see the tokens it is sending. An override that draws its own textarea keeps working: it fills the
|
|
240
|
+
`inputRef` it is handed, and the chat reads the caret off that when no editor filled `handleRef`.
|
|
204
241
|
- **`attach` and `voice` both carry functions, so a server layout cannot pass either.** A closure does not cross
|
|
205
242
|
the RSC boundary — `non-scalar-props-restricted` says so on `page/**` — so an app that wants either mounts the
|
|
206
243
|
chat from a small client component in the app's own `ui/` that calls the hook.
|
|
@@ -272,6 +309,14 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
272
309
|
`data-akan-action` like a store setter does. A `remove*` name defaults to a confirm gate. Reach a store action
|
|
273
310
|
from the body — `.exec((id) => st.do.removeX(id))` — which is how an agent gets CRUD; `st.do` on its own
|
|
274
311
|
reaches nobody.
|
|
312
|
+
- **`.card()` is the other way the chain ends, for an answer that is the user's to give.** Where `.exec()` runs a
|
|
313
|
+
function, `.card(({ submit, cancel }, ...args) => <Form …/>)` parks the call in the chat and renders the app's own
|
|
314
|
+
component there; what `submit` is handed is what the model reads back, and `cancel` is the refusal it reads
|
|
315
|
+
instead. A name and a phone number, a date somebody has to look up, a signature — a model that fills those in
|
|
316
|
+
has answered its own question, and every other shape of "ask the user" is `askUser`. The declared arguments are
|
|
317
|
+
checked **before** the card is parked, so a bad one reaches the model as a refusal rather than throwing inside
|
|
318
|
+
the render; `confirm` is not read at all, because the card in front of the user is already the asking. The frame
|
|
319
|
+
draws its own way out even when the card does not, and the turn is never parked on something with no exit.
|
|
275
320
|
- **The second argument is how the call behaves, never what it is.** `{ settle, confirm, guard }`:
|
|
276
321
|
`settle: false` marks a read that returns what is already there and skips the wait; every other tool is waited
|
|
277
322
|
out before its effect on the screen is reported, because a write may still be landing when `exec` resolves. A
|
|
@@ -507,18 +552,29 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
507
552
|
- **A long conversation summarizes itself, because nothing else is keeping it inside the model's window.** The
|
|
508
553
|
loop runs in the browser and the relay holds no session, so an uncompacted chat grows until the provider
|
|
509
554
|
refuses the whole request — a refusal, never a shorter answer, which is why compaction runs *before* the turn
|
|
510
|
-
that would have overflowed
|
|
511
|
-
characters to a token, over the JSON the turn posts
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
555
|
+
that would have overflowed. **Two triggers, each before every assistant turn.** `compact.at` is a ceiling on
|
|
556
|
+
cost: past that many estimated transcript tokens (four characters to a token, over the JSON the turn posts —
|
|
557
|
+
except an inlined picture, counted at the ~1,600 tokens a provider bills for its pixels rather than its base64;
|
|
558
|
+
24k by default) the history compacts, because the relay resends the whole transcript every turn and the app
|
|
559
|
+
pays for each one. The **window guard** compacts once the prompt nears the window the server reports —
|
|
560
|
+
`window − answer ceiling (8,192 when unreported) − compact.buffer (13,000, the margin Claude Code leaves)` —
|
|
561
|
+
measured by the provider's own count of the last turn (`done.usage`, kept on that assistant message) plus the
|
|
562
|
+
four-character estimate for what arrived since, so a Korean transcript the character rule reads as small is
|
|
563
|
+
still caught. The window comes from `option.setLlm({ contextWindow })` or from the first refusal that names it;
|
|
564
|
+
with neither, only the ceiling runs. **A refusal for length is answered once**: a relay flags it `overflow` on
|
|
565
|
+
the wire, and the session compacts and sends the same turn again, and a second refusal in the same send fails
|
|
566
|
+
with `agent.error.contextOverflow`. Either way the history above the last `keep` messages
|
|
567
|
+
becomes one message standing in for it, flagged `summary` on the wire — `<Agent.Chat compact={{ at, keep, buffer }} />`
|
|
568
|
+
tunes it, `{ at: Infinity }` leaves only the window guard, `{ at: 0 }` turns off the ceiling, the guard and the recovery alike, and `/compact` does
|
|
569
|
+
the same on demand keeping nothing. The header draws the estimate against whichever trigger is nearer.
|
|
515
570
|
**The cut never lands on a `tool` message**: the kept half may not open with a result whose call was summarized
|
|
516
571
|
away, a shape every provider dialect rejects. A user message is preferred where the tail holds one — everything
|
|
517
572
|
above it is settled — but one assistant turn that ran ten tools has none, and that is the transcript that
|
|
518
573
|
outgrows the window, so a cut there opens on the assistant message instead. The summarizing
|
|
519
574
|
turn carries no tools and no screen context — it summarizes the conversation, it does not act on it — and it is
|
|
520
575
|
fed a *bounded* digest rather than the transcript itself, since the transcript being summarized is the one that
|
|
521
|
-
no longer fits.
|
|
576
|
+
no longer fits. The previous summary is the exception: it rides the digest whole, under its own label and outside
|
|
577
|
+
the bound, because it is the only record of what was compacted before it. A summary that cannot be produced leaves the transcript alone, the turn goes out as it would
|
|
522
578
|
have, and the next one asks again — a summarizer that was momentarily unreachable says nothing about whether
|
|
523
579
|
this transcript can shrink. One that *landed* and shrank nothing does, so that one is not retried until another
|
|
524
580
|
threshold's worth has been added. On the
|
|
@@ -670,4 +726,76 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
|
|
|
670
726
|
is among them. The loop parks on the question exactly as it parks on an approval, a dismissal is the tool's
|
|
671
727
|
error result rather than a silent empty answer, and the settled exchange renders as question-and-answer instead
|
|
672
728
|
of a tool row. **Never re-implement it per screen** — a `st.tool("askAboutX")` that opens a modal is the same
|
|
673
|
-
thing with a worse transcript — and a hook tool named `askUser` shadows it like any other built-in.
|
|
729
|
+
thing with a worse transcript — and a hook tool named `askUser` shadows it like any other built-in. The line
|
|
730
|
+
between it and a `.card()` tool is what the answer *is*: a sentence or a pick off a list is `askUser` and belongs
|
|
731
|
+
to the conversation, while a shape the model must not invent — fields, a form, a signature — is a card the screen
|
|
732
|
+
declares, because only the screen knows what filling it in looks like.
|
|
733
|
+
|
|
734
|
+
## What The Page Draws
|
|
735
|
+
- **The page shows the agent's work, not just the transcript.** A chat panel is closed as often as it is open, and
|
|
736
|
+
a form that fills itself or a tab that switches on its own is a change the user watches happen with nothing
|
|
737
|
+
anywhere attributing it. So **the control a call was published from is ringed** where it stands, scrolled to
|
|
738
|
+
first when it is off screen, and **a pointer travels to it and presses it** — both on by default, and both
|
|
739
|
+
driven from the tool call itself rather than from a store action, which cannot tell the agent's write from the
|
|
740
|
+
user's.
|
|
741
|
+
- **It costs an app nothing, for the same reason `data-akan-action` does.** `onChange={st.do.setTitleOnTask}` by
|
|
742
|
+
reference is what annotates the control, and the annotation is what makes it findable — so the fields and verbs
|
|
743
|
+
that matter light up with no code. An inline arrow publishes no tool, carries no annotation, and therefore
|
|
744
|
+
draws nothing: one wrapper silently costs three things at once.
|
|
745
|
+
- **For a control the framework did not render, spread `agentAttrs`** from `akanjs/ui`:
|
|
746
|
+
`<button {...agentAttrs(run)} onClick={run}>`, where `run` is what `st.tool(…).exec(…)` returned. It reads the
|
|
747
|
+
name off the callable, so there is nothing to keep in sync and no `ref` to thread — and it earns the same E2E
|
|
748
|
+
selector and accessibility name an `akanjs/ui` control has. Never hand-write `data-akan-*`.
|
|
749
|
+
- **A control that shares its handler with its siblings takes a key**: `agentAttrs(switchTab, menu)`, in the same
|
|
750
|
+
vocabulary the call's argument uses. One tool serves a whole tab strip or a whole list, so without it every
|
|
751
|
+
namesake is interchangeable in the DOM and the page can say *what* the agent did but never *where* — which the
|
|
752
|
+
no-guessing rule then turns into drawing nothing. `Tab.Menu` passes its `menu` key; do the same for a row verb
|
|
753
|
+
whose id the call names.
|
|
754
|
+
- **Almost nothing is waited on.** The call starts the moment the effect is handed its event; a ring that lands a
|
|
755
|
+
frame later costs the turn nothing, and an animation that held a call would make the agent slower for a
|
|
756
|
+
decoration. A turn past four scrolls stops scrolling and keeps ringing — eight scrolls across a page is motion
|
|
757
|
+
sickness rather than attribution. **The one exception is `navigate`**, whose press has to land before the router
|
|
758
|
+
replaces the tree the link is in; it is capped at 600ms and the call goes either way.
|
|
759
|
+
- **What it refuses to draw is the point.** A name several rows answer to rings nothing rather than guessing a row,
|
|
760
|
+
a call an approval or a guard turned back is never drawn at all, a tool no control carries draws nothing, and a
|
|
761
|
+
backgrounded tab draws nothing. A ring on the wrong element is worse than no ring: it is the screen telling the
|
|
762
|
+
user something untrue about what just happened. **A control the screen is not actually showing is not pointed at
|
|
763
|
+
either, and the pointer goes rather than travelling to it**: `checkVisibility` answers about an element alone, so
|
|
764
|
+
one under a modal's backdrop, inside a drawer that has slid off, or faded to nothing all pass it while being
|
|
765
|
+
invisible, and a pointer sent there lands on a blank patch of overlay. The test is `elementFromPoint` at the
|
|
766
|
+
control's own centre, taken after the reveal has settled. **`navigate` is mostly in that last class** — the router is not
|
|
767
|
+
an element, so it draws nothing. A bar across the top of the page was tried for it and removed: it read as
|
|
768
|
+
chrome the page had grown rather than as the agent doing something, which is the opposite of attribution. What
|
|
769
|
+
it does draw is the **link already on screen that goes where it is going**, when exactly one does and the user
|
|
770
|
+
can already see it: that one is an element, and the pointer presses it before the route moves. Addresses are
|
|
771
|
+
compared as routes (`router.routeOf`), since the locale and base-path segments are on the `<a>` and never on the
|
|
772
|
+
tool argument. An off-screen link is left alone — scrolling to a link and then leaving the page it is on is two
|
|
773
|
+
motions for one act. **Link presence is a drawing condition, never a permission one**: the agent may navigate
|
|
774
|
+
anywhere the user could type, and a zone that must not leave one screen drops `navigate` through `builtins`.
|
|
775
|
+
- **Turn it off with `visual`** on `Agent.Chat` / `Agent.Zone` — `false` for all of it, `{ cursor: false }` or
|
|
776
|
+
`{ reveal: false }` for one effect. The pointer is chrome (`data-agent-ui`, `aria-hidden`,
|
|
777
|
+
`pointer-events: none`), so `readScreen` never reads it back and `highlight` can never aim at it.
|
|
778
|
+
- **`ToolActivity` is the channel underneath**, a `ToolRunner` host option and an `AgentSession.onActivity`, firing
|
|
779
|
+
`start` / `end` around the execution alone. It is deliberately not `progress`: that one fires only for a tool
|
|
780
|
+
that chose to report, and a tool that says nothing about itself is exactly the one whose effect arrives
|
|
781
|
+
unexplained. The `start` may answer a promise, and the call waits for it — the whole of what makes drawing a
|
|
782
|
+
click on a link possible.
|
|
783
|
+
- **`AgentSession.onTurn(running)` is the other half**, and the unit the pointer's life is measured in. A model's
|
|
784
|
+
calls arrive with its own writing between them, seconds each, so a pointer that lived for the length of a *call*
|
|
785
|
+
spent every turn vanishing and coming back. Only the conversation loop reports there — `compact` runs under the
|
|
786
|
+
same internal flag and drives nothing on screen.
|
|
787
|
+
- **The pointer presses on arrival, never on departure** — a press fired as it leaves is a click on whatever it
|
|
788
|
+
was still over. It glides only when the hop is far enough to be worth following (a third of a second to cross
|
|
789
|
+
fifty pixels reads as lag, not motion), and teleports otherwise. It appears at the first control it presses,
|
|
790
|
+
**drifts clear of what it pressed** and waits out the gap between calls there as a spinner — a person clicks and
|
|
791
|
+
takes the hand away, and a spinner left sitting on the button covers the change it just caused — and fades when
|
|
792
|
+
the turn ends; **a turn that
|
|
793
|
+
drove no control draws no pointer at all**, since parking one in a corner for a turn that only answered a
|
|
794
|
+
question claims something that did not happen. While a reveal scrolls, it holds still — as a person's does — and
|
|
795
|
+
carries a chevron pointing the way the view is travelling, because stillness over a sliding page otherwise reads
|
|
796
|
+
as a pointer that has come loose rather than as the one doing the scrolling. The ring answers *where* and the
|
|
797
|
+
pointer answers *who*, which is why they are one default rather than two.
|
|
798
|
+
- **A form patch fans out over the fields it named.** `fill<Model>Form` is published by the form rather than by any
|
|
799
|
+
one control, so it rings one control per field in the arguments — resolved through the same
|
|
800
|
+
`data-akan-state="<model>Form.<field>"` the setter annotates — capped at five. `writeOn<Model>(path, value)`
|
|
801
|
+
draws nothing, for the same reason it carries no annotation: there is no control to put one on.
|