@akanjs/devkit 3.0.0-beta.14 → 3.0.0-beta.16
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 +1 -0
- package/codeAgent/agent/AkanAuthStorageBackend.ts +105 -0
- package/codeAgent/agent/AkanCodeServices.ts +104 -0
- package/codeAgent/agent/CodeAgent.ts +543 -0
- package/codeAgent/agent/CodeAgentAsks.ts +87 -0
- package/codeAgent/agent/CodeAgentEventMapper.ts +260 -0
- package/codeAgent/agent/CodeAgentGate.ts +119 -0
- package/codeAgent/agent/CodeAgentRpcHost.ts +96 -0
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +93 -0
- package/codeAgent/agent/CodeAgentUi.ts +52 -0
- package/codeAgent/agent/CodeMailbox.ts +175 -0
- package/codeAgent/agent/CodeSessionFork.ts +71 -0
- package/codeAgent/agent/CodeSessionIndex.ts +104 -0
- package/codeAgent/agent/SubagentPool.ts +173 -0
- package/codeAgent/agent/akanCodeModel.ts +81 -0
- package/codeAgent/agent/akanCodePaths.ts +31 -0
- package/codeAgent/agent/akanSystemPrompt.ts +49 -0
- package/codeAgent/agent/codeAgentContract.test.ts +529 -0
- package/codeAgent/agent/codeMailbox.test.ts +80 -0
- package/codeAgent/agent/codeSessionFork.test.ts +60 -0
- package/codeAgent/agent/codeSessionIndex.test.ts +72 -0
- package/codeAgent/agent/consoleToStderr.ts +18 -0
- package/codeAgent/feedback/DevLogFeedback.ts +97 -0
- package/codeAgent/feedback/PreviewChrome.ts +106 -0
- package/codeAgent/feedback/PreviewFeedback.ts +53 -0
- package/codeAgent/feedback/PreviewView.ts +107 -0
- package/codeAgent/feedback/TurnFeedback.ts +113 -0
- package/codeAgent/feedback/VerifyFeedback.ts +46 -0
- package/codeAgent/feedback/previewChrome.test.ts +73 -0
- package/codeAgent/index.ts +43 -0
- package/codeAgent/skills/akan-debug/SKILL.md +42 -0
- package/codeAgent/skills/akan-deps/SKILL.md +50 -0
- package/codeAgent/skills/akan-err/SKILL.md +39 -0
- package/codeAgent/skills/akan-module/SKILL.md +45 -0
- package/codeAgent/skills/akan-scaffold/SKILL.md +76 -0
- package/codeAgent/skills/akan-scalar/SKILL.md +52 -0
- package/codeAgent/skills/akan-service/SKILL.md +34 -0
- package/codeAgent/skills/akan-store/SKILL.md +63 -0
- package/codeAgent/skills/akan-ui/SKILL.md +63 -0
- package/codeAgent/skills/akan-validate/SKILL.md +44 -0
- package/codeAgent/tools/AkanCli.ts +62 -0
- package/codeAgent/tools/AkanCodePlugins.ts +135 -0
- package/codeAgent/tools/AkanEditScope.ts +58 -0
- package/codeAgent/tools/AkanEnvKeys.ts +56 -0
- package/codeAgent/tools/AkanToolPack.ts +114 -0
- package/codeAgent/tools/AkanVerifier.ts +55 -0
- package/codeAgent/tools/MailToolPack.ts +85 -0
- package/codeAgent/tools/McpClient.ts +162 -0
- package/codeAgent/tools/McpToolPack.ts +134 -0
- package/codeAgent/tools/SessionToolPack.ts +101 -0
- package/codeAgent/tools/WebToolPack.ts +153 -0
- package/codeAgent/tools/akanCodePlugins.test.ts +476 -0
- package/executors.ts +5 -0
- package/index.ts +2 -0
- package/package.json +8 -2
- package/piDependencyPins.test.ts +55 -0
- package/stripAnsi.ts +3 -0
- package/testHelpers.ts +179 -0
- package/types.ts +1 -0
package/README.md
CHANGED
|
@@ -44,6 +44,7 @@ await runner.build();
|
|
|
44
44
|
- Command/script decorators used by `@akanjs/cli`.
|
|
45
45
|
- AI prompt, guideline, and code-generation support utilities.
|
|
46
46
|
- Capacitor and mobile release helpers.
|
|
47
|
+
- The `akan code` agent engine, the tools it is given, and the shipped akan skills (`@akanjs/devkit/codeAgent`).
|
|
47
48
|
|
|
48
49
|
## Dev Server Sizing
|
|
49
50
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closeSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
openSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
renameSync,
|
|
8
|
+
unlinkSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import type { AuthStorageBackend } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Credential store for `akan code`, in akan's own file rather than the engine's.
|
|
16
|
+
*
|
|
17
|
+
* The engine only asks for a locked read-modify-write over one opaque string, so satisfying its interface with
|
|
18
|
+
* our own file keeps every key under `~/.akan/` — nothing lands in `~/.pi/` and a user who removes akan takes
|
|
19
|
+
* their credentials with them.
|
|
20
|
+
*
|
|
21
|
+
* The lock is an exclusive-create sentinel rather than an advisory flock: two `akan code` processes refreshing
|
|
22
|
+
* an OAuth token at the same moment would otherwise each write a token the other invalidates.
|
|
23
|
+
*/
|
|
24
|
+
export class AkanAuthStorageBackend implements AuthStorageBackend {
|
|
25
|
+
readonly #authPath: string;
|
|
26
|
+
readonly #lockPath: string;
|
|
27
|
+
|
|
28
|
+
constructor(authPath: string) {
|
|
29
|
+
this.#authPath = authPath;
|
|
30
|
+
this.#lockPath = `${authPath}.lock`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
withLock<T>(fn: (current: string | undefined) => { result: T; next?: string }) {
|
|
34
|
+
this.#acquire();
|
|
35
|
+
try {
|
|
36
|
+
const { result, next } = fn(this.#read());
|
|
37
|
+
if (next !== undefined) this.#write(next);
|
|
38
|
+
return result;
|
|
39
|
+
} finally {
|
|
40
|
+
this.#release();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async withLockAsync<T>(fn: (current: string | undefined) => Promise<{ result: T; next?: string }>) {
|
|
45
|
+
this.#acquire();
|
|
46
|
+
try {
|
|
47
|
+
const { result, next } = await fn(this.#read());
|
|
48
|
+
if (next !== undefined) this.#write(next);
|
|
49
|
+
return result;
|
|
50
|
+
} finally {
|
|
51
|
+
this.#release();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#read() {
|
|
56
|
+
if (!existsSync(this.#authPath)) return undefined;
|
|
57
|
+
const text = readFileSync(this.#authPath, "utf8");
|
|
58
|
+
return text.trim() ? text : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#write(next: string) {
|
|
62
|
+
mkdirSync(path.dirname(this.#authPath), { recursive: true, mode: 0o700 });
|
|
63
|
+
const temp = `${this.#authPath}.${process.pid}.tmp`;
|
|
64
|
+
writeFileSync(temp, next, { mode: 0o600 });
|
|
65
|
+
renameSync(temp, this.#authPath);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#acquire() {
|
|
69
|
+
mkdirSync(path.dirname(this.#lockPath), { recursive: true, mode: 0o700 });
|
|
70
|
+
const deadline = Date.now() + 5_000;
|
|
71
|
+
for (;;) {
|
|
72
|
+
try {
|
|
73
|
+
closeSync(openSync(this.#lockPath, "wx"));
|
|
74
|
+
return;
|
|
75
|
+
} catch {
|
|
76
|
+
// A lock left behind by a killed process would block every later run, so it expires.
|
|
77
|
+
if (this.#lockIsStale() || Date.now() > deadline) {
|
|
78
|
+
try {
|
|
79
|
+
unlinkSync(this.#lockPath);
|
|
80
|
+
} catch {
|
|
81
|
+
// Another process won the race to clear it; retry the create.
|
|
82
|
+
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#lockIsStale() {
|
|
91
|
+
try {
|
|
92
|
+
return Date.now() - Bun.file(this.#lockPath).lastModified > 30_000;
|
|
93
|
+
} catch {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#release() {
|
|
99
|
+
try {
|
|
100
|
+
unlinkSync(this.#lockPath);
|
|
101
|
+
} catch {
|
|
102
|
+
// Already gone — a stale-lock sweep by another process.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
AuthStorage,
|
|
4
|
+
type InlineExtension,
|
|
5
|
+
ModelRegistry,
|
|
6
|
+
SessionManager,
|
|
7
|
+
SettingsManager,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import type { CodeAgentProfile } from "akanjs/common";
|
|
10
|
+
import { AkanEnvKeys } from "../tools/AkanEnvKeys";
|
|
11
|
+
import { AkanAuthStorageBackend } from "./AkanAuthStorageBackend";
|
|
12
|
+
import { akanCodePaths } from "./akanCodePaths";
|
|
13
|
+
import { akanSystemPrompt } from "./akanSystemPrompt";
|
|
14
|
+
import { CodeSessionIndex } from "./CodeSessionIndex";
|
|
15
|
+
|
|
16
|
+
export interface AkanCodeServicesOptions {
|
|
17
|
+
workspaceRoot: string;
|
|
18
|
+
cwd: string;
|
|
19
|
+
profile: CodeAgentProfile;
|
|
20
|
+
extensions: InlineExtension[];
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Assembles the engine's injectable services from akan's own storage.
|
|
26
|
+
*
|
|
27
|
+
* Every one of the five is passed explicitly. Leave any of them out and the engine falls back to `~/.pi/` —
|
|
28
|
+
* measured: with all five injected it writes nothing outside the paths named here.
|
|
29
|
+
*/
|
|
30
|
+
export class AkanCodeServices {
|
|
31
|
+
static settings() {
|
|
32
|
+
// Settings are in memory on purpose: the engine's file settings would be a second configuration surface
|
|
33
|
+
// beside `akan.config.ts` with its own precedence, for options akan already decides through the profile.
|
|
34
|
+
// `enableInstallTelemetry` defaults to true and attaches pi-branded headers to OpenRouter, NVIDIA and
|
|
35
|
+
// Cloudflare requests, so it is turned off here rather than per host — a host that changes provider later
|
|
36
|
+
// would otherwise turn it back on without meaning to.
|
|
37
|
+
return SettingsManager.inMemory({
|
|
38
|
+
enableInstallTelemetry: false,
|
|
39
|
+
enableAnalytics: false,
|
|
40
|
+
quietStartup: true,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static auth(workspaceRoot: string) {
|
|
45
|
+
mkdirSync(akanCodePaths.globalDir(), { recursive: true, mode: 0o700 });
|
|
46
|
+
const authStorage = AuthStorage.fromStorage(new AkanAuthStorageBackend(akanCodePaths.authFile()));
|
|
47
|
+
AkanEnvKeys.apply(authStorage, workspaceRoot);
|
|
48
|
+
return authStorage;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static models(authStorage: AuthStorage) {
|
|
52
|
+
return ModelRegistry.create(authStorage, akanCodePaths.modelsFile());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static sessions(workspaceRoot: string, cwd: string, profile: CodeAgentProfile, resume?: string) {
|
|
56
|
+
if (profile.session.store !== "file") return SessionManager.inMemory(cwd);
|
|
57
|
+
const dir = akanCodePaths.sessionsDir(workspaceRoot);
|
|
58
|
+
mkdirSync(dir, { recursive: true });
|
|
59
|
+
const manager = SessionManager.create(cwd, dir);
|
|
60
|
+
if (!resume) return manager;
|
|
61
|
+
const file = CodeSessionIndex.fileOf(dir, resume);
|
|
62
|
+
// A mistyped id that quietly opened a new session would look like a session that lost its history.
|
|
63
|
+
if (!file) throw new Error(`No stored session ${resume} in ${dir}`);
|
|
64
|
+
manager.setSessionFile(file);
|
|
65
|
+
return manager;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The akan skill set, then the workspace's own.
|
|
70
|
+
*
|
|
71
|
+
* A path that does not exist is a reported load error rather than a skip, so both are checked first.
|
|
72
|
+
*/
|
|
73
|
+
static #skillPaths(profile: CodeAgentProfile, workspaceRoot: string) {
|
|
74
|
+
if (!profile.context.skills) return [];
|
|
75
|
+
const workspace = akanCodePaths.skillsDir(workspaceRoot);
|
|
76
|
+
return [akanCodePaths.builtinSkillsDir(), existsSync(workspace) ? workspace : undefined].filter(
|
|
77
|
+
(dir): dir is string => !!dir,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* What the engine's resource loader may discover. Extensions and skills come from the workspace, never from
|
|
83
|
+
* the home directory: a coding agent that picks up a globally installed extension behaves differently in two
|
|
84
|
+
* checkouts of the same repo for reasons nothing in the repo explains.
|
|
85
|
+
*/
|
|
86
|
+
static resourceOptions(options: AkanCodeServicesOptions) {
|
|
87
|
+
// A path that does not exist is reported as a load error rather than skipped, and these two are optional.
|
|
88
|
+
const present = (dir: string) => (existsSync(dir) ? [dir] : []);
|
|
89
|
+
return {
|
|
90
|
+
noExtensions: true,
|
|
91
|
+
// Always on: it turns off the engine's *default* skill locations, which are `~/.pi/skills` and the
|
|
92
|
+
// checkout's own `.pi/`. Explicit `additionalSkillPaths` are merged either way, so this is the switch
|
|
93
|
+
// that keeps a skill in whoever's home directory from changing how the agent works here.
|
|
94
|
+
noSkills: true,
|
|
95
|
+
noPromptTemplates: true,
|
|
96
|
+
noThemes: true,
|
|
97
|
+
noContextFiles: !options.profile.context.projectFiles,
|
|
98
|
+
appendSystemPrompt: [akanSystemPrompt(options.profile)],
|
|
99
|
+
additionalExtensionPaths: present(akanCodePaths.extensionsDir(options.workspaceRoot)),
|
|
100
|
+
additionalSkillPaths: AkanCodeServices.#skillPaths(options.profile, options.workspaceRoot),
|
|
101
|
+
extensionFactories: options.extensions,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|