@akanjs/devkit 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/DEV_RUNTIME_KNOBS.md +44 -26
- package/README.ko.md +21 -7
- package/README.md +17 -8
- package/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +6 -8
- package/agentsIndex.test.ts +4 -7
- package/agentsIndex.ts +6 -22
- package/akanApp/BackendImportGraph.test.ts +8 -22
- package/akanApp/BackendImportGraph.ts +1 -20
- package/akanApp/akanApp.host.test.ts +56 -3
- package/akanApp/akanApp.host.ts +218 -278
- package/akanApp/devBootLatch.test.ts +39 -0
- package/akanApp/devBootLatch.ts +39 -0
- package/akanApp/devHostPolicy.test.ts +81 -35
- package/akanApp/devHostPolicy.ts +41 -102
- package/akanConfig/akanBin.test.ts +62 -0
- package/akanConfig/akanBin.ts +78 -0
- package/akanConfig/akanConfig.test.ts +439 -141
- package/akanConfig/akanConfig.ts +359 -275
- package/akanConfig/index.ts +1 -0
- package/akanConfig/types.ts +22 -8
- package/akanContext.ts +110 -160
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.test.ts +41 -5
- package/applicationBuildRunner.ts +71 -63
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +32 -30
- package/artifact/implicitRootLayout.ts +58 -106
- package/artifact/routeSeedIndex.test.ts +7 -6
- package/artifact/routeSeedIndex.ts +11 -44
- package/biome.base.json +35 -2
- package/biomeBase.ts +2 -5
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +77 -39
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +26 -5
- package/cloud/globalConfig.ts +71 -32
- package/codeAgent/agent/AkanCodeServices.ts +97 -0
- package/codeAgent/agent/AkanContextFiles.ts +94 -0
- package/codeAgent/agent/CodeAgent.ts +551 -0
- package/codeAgent/agent/CodeAgentAsks.ts +66 -0
- package/codeAgent/agent/CodeAgentEventMapper.ts +225 -0
- package/codeAgent/agent/CodeAgentGate.ts +93 -0
- package/codeAgent/agent/CodeAgentProxy.ts +82 -0
- package/codeAgent/agent/CodeAgentRpcHost.ts +103 -0
- package/codeAgent/agent/CodeAgentRpcListener.ts +109 -0
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +86 -0
- package/codeAgent/agent/CodeAgentSuspended.ts +85 -0
- package/codeAgent/agent/CodeAgentUi.ts +47 -0
- package/codeAgent/agent/CodeMailbox.ts +148 -0
- package/codeAgent/agent/CodeSessionFork.ts +56 -0
- package/codeAgent/agent/CodeSessionIndex.ts +78 -0
- package/codeAgent/agent/SubagentPool.ts +214 -0
- package/codeAgent/agent/akanCodeModel.ts +47 -0
- package/codeAgent/agent/akanCodePaths.ts +31 -0
- package/codeAgent/agent/akanContextFiles.test.ts +114 -0
- package/codeAgent/agent/akanSystemPrompt.ts +46 -0
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +523 -0
- package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
- package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
- package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
- package/codeAgent/agent/codeMailbox.test.ts +76 -0
- package/codeAgent/agent/codeSessionFork.test.ts +58 -0
- package/codeAgent/agent/codeSessionIndex.test.ts +77 -0
- package/codeAgent/agent/consoleToStderr.ts +8 -0
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +82 -0
- package/codeAgent/feedback/PreviewChrome.ts +82 -0
- package/codeAgent/feedback/PreviewFeedback.ts +46 -0
- package/codeAgent/feedback/PreviewView.ts +95 -0
- package/codeAgent/feedback/TurnFeedback.ts +98 -0
- package/codeAgent/feedback/VerifyFeedback.ts +40 -0
- package/codeAgent/feedback/previewChrome.test.ts +73 -0
- package/codeAgent/index.ts +34 -0
- package/codeAgent/skills/akan-code/SKILL.md +130 -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 +67 -0
- package/codeAgent/skills/akan-ui/SKILL.md +63 -0
- package/codeAgent/skills/akan-validate/SKILL.md +44 -0
- package/codeAgent/tools/AkanCli.ts +50 -0
- package/codeAgent/tools/AkanCodePlugins.ts +136 -0
- package/codeAgent/tools/AkanEditScope.ts +48 -0
- package/codeAgent/tools/AkanEnvKeys.ts +50 -0
- package/codeAgent/tools/AkanToolPack.ts +92 -0
- package/codeAgent/tools/AkanVerifier.ts +48 -0
- package/codeAgent/tools/AskToolPack.ts +99 -0
- package/codeAgent/tools/MailToolPack.ts +76 -0
- package/codeAgent/tools/McpClient.ts +187 -0
- package/codeAgent/tools/McpOAuth.ts +233 -0
- package/codeAgent/tools/McpOAuthLoopback.ts +64 -0
- package/codeAgent/tools/McpServerConfig.ts +129 -0
- package/codeAgent/tools/McpSignIn.ts +158 -0
- package/codeAgent/tools/McpTokenStore.ts +81 -0
- package/codeAgent/tools/McpToolPack.ts +199 -0
- package/codeAgent/tools/SessionToolPack.ts +91 -0
- package/codeAgent/tools/WebToolPack.ts +147 -0
- package/codeAgent/tools/akanCodePlugins.test.ts +677 -0
- package/codeAgent/tools/askToolPack.test.ts +116 -0
- package/codeAgent/tools/mcpOAuth.test.ts +612 -0
- package/codeAgent/tools/mcpServerConfig.test.ts +212 -0
- package/codegenLock.test.ts +4 -13
- package/codegenLock.ts +3 -17
- package/commandDecorators/argMeta.ts +1 -5
- package/commandDecorators/camelToKebabCase.ts +1 -0
- package/commandDecorators/command.ts +46 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +2 -10
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +68 -145
- package/commandDecorators/index.ts +0 -2
- package/commandDecorators/targetMeta.ts +9 -7
- package/createTunnel.ts +7 -14
- package/csrE2e/csrE2eHarness.fixture.ts +505 -0
- package/csrE2e/csrFastRefresh.e2e.test.ts +233 -0
- package/csrE2e/csrPageStack.e2e.test.ts +252 -0
- package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
- package/csrE2e/csrRootLayout.e2e.test.ts +134 -0
- package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
- package/csrE2e/registryVendor.e2e.test.ts +221 -0
- package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
- package/dependencyScanner.test.ts +4 -16
- package/dependencyScanner.ts +31 -80
- package/devkitUtils.test.ts +48 -20
- package/executors.linkFallback.test.ts +103 -0
- package/executors.test.ts +176 -106
- package/executors.ts +212 -498
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +5 -3
- package/fileSys.ts +22 -9
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +27 -38
- package/frontendBuild/autoImportSync.ts +31 -65
- package/frontendBuild/buildRouteClient.test.ts +123 -37
- package/frontendBuild/bundleDefine.test.ts +12 -0
- package/frontendBuild/bundleDefine.ts +16 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.test.ts +112 -0
- package/frontendBuild/clientEntryDiscovery.ts +122 -110
- package/frontendBuild/csrArtifactBuilder.ts +9 -31
- package/frontendBuild/csrDevArtifactWriter.test.ts +151 -0
- package/frontendBuild/csrDevArtifactWriter.ts +307 -0
- package/frontendBuild/csrDevBundler.test.ts +66 -0
- package/frontendBuild/csrDevBundler.ts +95 -0
- package/frontendBuild/csrDevFactoryCheck.test.ts +33 -0
- package/frontendBuild/csrDevFactoryCheck.ts +39 -0
- package/frontendBuild/csrDevModuleCompiler.test.ts +293 -0
- package/frontendBuild/csrDevModuleCompiler.ts +422 -0
- package/frontendBuild/csrDevPatcher.test.ts +572 -0
- package/frontendBuild/csrDevPatcher.ts +548 -0
- package/frontendBuild/csrDevPaths.ts +145 -0
- package/frontendBuild/csrDevResolver.test.ts +239 -0
- package/frontendBuild/csrDevResolver.ts +363 -0
- package/frontendBuild/csrDevTypes.ts +61 -0
- package/frontendBuild/csrEntryFiles.ts +24 -0
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +31 -82
- package/frontendBuild/cssImportResolver.ts +6 -19
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/devRegistryBundler.ts +192 -0
- package/frontendBuild/fontOptimizer.test.ts +6 -14
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +57 -70
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +59 -32
- package/frontendBuild/hmrWatcher.ts +31 -74
- package/frontendBuild/index.ts +4 -0
- package/frontendBuild/pagesBundleBuilder.ts +28 -30
- package/frontendBuild/pagesEntrySourceGenerator.ts +56 -6
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
- package/frontendBuild/registryMetadataFingerprint.ts +28 -0
- package/frontendBuild/routeClientBuilder.ts +100 -62
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/serverGraphFile.test.ts +77 -0
- package/frontendBuild/serverGraphFile.ts +93 -0
- package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +19 -13
- package/frontendBuild/ssrDevBundler.ts +85 -0
- package/frontendBuild/styleContract.ts +1 -8
- package/frontendBuild/themeValidator.ts +4 -26
- package/frontendBuild/watchRootResolver.test.ts +23 -42
- package/frontendBuild/watchRootResolver.ts +5 -27
- package/getRelatedCnsts.ts +12 -92
- package/incrementalBuilder/buildBatch.proc.ts +147 -63
- package/incrementalBuilder/buildBatchProtocol.ts +13 -38
- package/incrementalBuilder/buildBatchRunner.test.ts +65 -0
- package/incrementalBuilder/buildBatchRunner.ts +26 -38
- package/incrementalBuilder/builderChannel.test.ts +24 -49
- package/incrementalBuilder/builderChannel.ts +6 -37
- package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
- package/incrementalBuilder/builderRequestRouter.ts +5 -24
- package/incrementalBuilder/builderWorkQueue.test.ts +94 -0
- package/incrementalBuilder/builderWorkQueue.ts +126 -0
- package/incrementalBuilder/devWatchBatch.test.ts +21 -24
- package/incrementalBuilder/incrementalBuilder.host.test.ts +53 -56
- package/incrementalBuilder/incrementalBuilder.host.ts +68 -81
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +270 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +596 -208
- package/index.ts +5 -19
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +105 -199
- package/integration/devStabilityHarness.test.ts +13 -28
- package/integration/devStabilityHarness.ts +65 -330
- package/integration/ssrMemoryProbe.ts +41 -167
- package/libSource.test.ts +4 -15
- package/libSource.ts +19 -31
- package/lint/__fixtures__/no-document-cookie/bad.tsx +4 -0
- package/lint/__fixtures__/no-document-cookie/good.tsx +7 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/bad.tsx +7 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/good.tsx +11 -0
- package/lint/__fixtures__/no-web-storage/bad.tsx +6 -0
- package/lint/__fixtures__/no-web-storage/good.tsx +7 -0
- package/lint/gritRules.test.ts +8 -24
- package/lint/no-deep-internal-import.grit +2 -2
- package/lint/no-document-cookie.grit +29 -0
- package/lint/no-redeclare-predefined-endpoint.grit +2 -1
- package/lint/no-web-only-api-outside-webkit.grit +44 -0
- package/lint/no-web-storage.grit +30 -0
- package/linter.test.ts +1 -2
- package/linter.ts +26 -116
- package/mobile/desktopBin.test.ts +182 -0
- package/mobile/desktopBin.ts +144 -0
- package/mobile/desktopServerStage.test.ts +252 -0
- package/mobile/desktopServerStage.ts +178 -0
- package/mobile/index.ts +9 -0
- package/mobile/mobileTarget.test.ts +57 -8
- package/mobile/mobileTarget.ts +45 -26
- package/mobile/nativeApi.test.ts +30 -0
- package/mobile/nativeApi.ts +40 -0
- package/mobile/nativeApp.test.ts +184 -0
- package/mobile/nativeApp.ts +454 -0
- package/mobile/nativeAppLine.test.ts +69 -0
- package/mobile/nativeAppLine.ts +71 -0
- package/mobile/nativeConfig.test.ts +438 -0
- package/mobile/nativeConfig.ts +294 -0
- package/mobile/nativePluginFolders.test.ts +81 -0
- package/mobile/nativePluginFolders.ts +60 -0
- package/mobile/nativeWebDir.test.ts +80 -0
- package/mobile/nativeWebDir.ts +66 -0
- package/mobile/placeholderAppId.test.ts +13 -0
- package/mobile/placeholderAppId.ts +16 -0
- package/mobile/usageDescriptions.test.ts +27 -0
- package/mobile/usageDescriptions.ts +24 -0
- package/package.json +11 -11
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +46 -0
- package/platformTest/BunTestReport.test.ts +60 -0
- package/platformTest/BunTestReport.ts +65 -0
- package/platformTest/IdleWatchedProcess.test.ts +56 -0
- package/platformTest/IdleWatchedProcess.ts +73 -0
- package/platformTest/LinuxDockerTarget.ts +105 -0
- package/platformTest/PlatformTestRun.test.ts +64 -0
- package/platformTest/PlatformTestRun.ts +141 -0
- package/platformTest/PlatformTestTarget.ts +142 -0
- package/platformTest/TestSnapshot.test.ts +72 -0
- package/platformTest/TestSnapshot.ts +103 -0
- package/platformTest/WindowsSshTarget.ts +151 -0
- package/prompter.ts +3 -13
- package/qualityScanner.test.ts +9 -26
- package/qualityScanner.ts +200 -286
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.test.ts +0 -1
- package/routeSourceValidator.ts +7 -42
- package/scanInfo.ts +91 -225
- package/selectModel.ts +1 -2
- package/semver.ts +3 -5
- package/slicePlanner.test.ts +6 -24
- package/slicePlanner.ts +11 -32
- package/spinner.test.ts +3 -8
- package/spinner.ts +4 -16
- package/sshTunnel.ts +1 -6
- package/ssrScanner.test.ts +1 -6
- package/ssrScanner.ts +103 -113
- package/stripAnsi.ts +3 -0
- package/subspace.test.ts +9 -25
- package/subspace.ts +48 -115
- package/subspaceConfig.ts +3 -11
- package/testHelpers.ts +194 -0
- package/transforms/asyncDefaultExportDetector.ts +3 -11
- package/transforms/barrelAnalyzer.ts +43 -135
- package/transforms/barrelImportsPlugin.ts +38 -175
- package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
- package/transforms/externalizeFrameworkPlugin.ts +13 -87
- package/transforms/moduleSyntax.ts +32 -0
- package/transforms/rscUseClientTransform.ts +42 -20
- package/transforms/serverEnvPlugin.test.ts +103 -0
- package/transforms/serverEnvPlugin.ts +41 -0
- package/transforms/transforms.test.ts +166 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +6 -23
- package/typeChecker.ts +7 -63
- package/types.ts +2 -0
- package/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +101 -110
- package/workflow/index.ts +0 -1
- package/workflow/moduleIndex.test.ts +4 -15
- package/workflow/moduleIndex.ts +9 -49
- package/workflow/plan.ts +18 -25
- package/workflow/render.ts +80 -96
- package/workflow/rolloutGate.test.ts +1 -1
- package/workflow/source.ts +52 -69
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.test.ts +18 -3
- package/workspaceLayout.ts +22 -23
- package/abstractCompactor.ts +0 -112
- package/aiEditor.test.ts +0 -60
- package/aiEditor.ts +0 -387
- package/applicationReleasePackager.ts +0 -209
- package/capacitorApp.test.ts +0 -372
- package/capacitorApp.ts +0 -1648
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- package/guideline.ts +0 -19
- package/local/optimize-resource/ipcprobe/child.ts +0 -1
- package/local/optimize-resource/ipcprobe/parent.ts +0 -11
- package/workflow/primitive.ts +0 -59
package/cloud/globalConfig.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import dayjs from "dayjs";
|
|
3
3
|
import { FileSys } from "../fileSys";
|
|
4
|
+
import { ConfigLock } from "./configLock";
|
|
4
5
|
import {
|
|
5
6
|
type AccessToken,
|
|
6
7
|
type AccessTokenDto,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
type HostConfig,
|
|
13
14
|
type HostConfigDto,
|
|
14
15
|
type RemoteEnvServerConfig,
|
|
16
|
+
type TestTargetsConfig,
|
|
15
17
|
} from "./constants";
|
|
16
18
|
|
|
17
19
|
export class GlobalConfig {
|
|
@@ -27,58 +29,95 @@ export class GlobalConfig {
|
|
|
27
29
|
...akanConfig,
|
|
28
30
|
cloudHost: akanConfig.cloudHost ?? defaultAkanGlobalConfig.cloudHost,
|
|
29
31
|
remoteEnvServers: akanConfig.remoteEnvServers ?? defaultAkanGlobalConfig.remoteEnvServers,
|
|
32
|
+
testTargets: akanConfig.testTargets ?? defaultAkanGlobalConfig.testTargets,
|
|
30
33
|
};
|
|
31
34
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* written owner-only — the same `0600` the runtime gives its control socket. `Bun.write` takes no mode
|
|
35
|
-
* and lands on `0666 & ~umask` (0644 on a default shell), so the mode is applied after the write; an
|
|
36
|
-
* existing world-readable file is tightened by the next write rather than left as it was found.
|
|
37
|
-
*/
|
|
35
|
+
// Holds the cloud jwt and refresh token, so owner-only 0600: written to a 0600 sibling and renamed over the file,
|
|
36
|
+
// which also replaces a world-readable one and never shows another CLI process a half-written file.
|
|
38
37
|
static async #setAkanGlobalConfig(akanConfig: AkanGlobalConfig) {
|
|
39
38
|
await mkdir(basePath, { recursive: true, mode: 0o700 });
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
const temp = `${configPath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
40
|
+
try {
|
|
41
|
+
await writeFile(temp, JSON.stringify(akanConfig, null, 2), { mode: 0o600 });
|
|
42
|
+
// Every CLI reads this file outside the lock, which on Windows makes the rename wait for the reader.
|
|
43
|
+
await FileSys.replace(temp, configPath);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
await rm(temp, { force: true });
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
static readonly #lockPath = `${configPath}.lock`;
|
|
50
|
+
// Every write re-reads under the lock: one built on an earlier read could restore a rotated-away refresh token.
|
|
51
|
+
static async #update(mutate: (akanConfig: AkanGlobalConfig) => AkanGlobalConfig) {
|
|
52
|
+
await ConfigLock.run(GlobalConfig.#lockPath, async () => {
|
|
53
|
+
await GlobalConfig.#setAkanGlobalConfig(mutate(await GlobalConfig.#getAkanGlobalConfig()));
|
|
54
|
+
});
|
|
42
55
|
}
|
|
43
56
|
static async getHostConfig(host = GlobalConfig.akanCloudHost): Promise<HostConfig> {
|
|
44
57
|
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
45
58
|
return GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? getDefaultHostConfig(host));
|
|
46
59
|
}
|
|
47
60
|
static async setHostConfig(config: HostConfig = getDefaultHostConfig()) {
|
|
48
|
-
|
|
49
|
-
akanConfig.cloudHost[config.host] = GlobalConfig.toHostConfigDto(config);
|
|
50
|
-
await GlobalConfig.#setAkanGlobalConfig(akanConfig);
|
|
61
|
+
await GlobalConfig.#update((akanConfig) => GlobalConfig.#withHost(akanConfig, config));
|
|
51
62
|
}
|
|
52
|
-
static
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
static #withHost(akanConfig: AkanGlobalConfig, config: HostConfig): AkanGlobalConfig {
|
|
64
|
+
return {
|
|
65
|
+
...akanConfig,
|
|
66
|
+
cloudHost: { ...akanConfig.cloudHost, [config.host]: GlobalConfig.toHostConfigDto(config) },
|
|
67
|
+
};
|
|
55
68
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
// The cloud rotates the refresh token and revokes every session of the account when one is presented twice, so it
|
|
70
|
+
// leaves the disk before it is sent: a refresh killed in flight leaves nothing to replay, only a sign-in to redo.
|
|
71
|
+
static async refreshHostAuth(host: string, refresh: (refreshToken: string) => Promise<AccessToken>) {
|
|
72
|
+
try {
|
|
73
|
+
return await ConfigLock.run(GlobalConfig.#lockPath, async () => {
|
|
74
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
75
|
+
const current = GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? getDefaultHostConfig(host));
|
|
76
|
+
const accessToken = current.auth?.accessToken;
|
|
77
|
+
const refreshToken = accessToken?.refreshToken;
|
|
78
|
+
if (!accessToken || !refreshToken || !GlobalConfig.needRefreshToken(accessToken)) return current;
|
|
79
|
+
const claimed: HostConfig = {
|
|
80
|
+
...current,
|
|
81
|
+
auth: { ...current.auth, accessToken: { ...accessToken, refreshToken: null } },
|
|
82
|
+
};
|
|
83
|
+
await GlobalConfig.#setAkanGlobalConfig(GlobalConfig.#withHost(akanConfig, claimed));
|
|
84
|
+
const next = await refresh(refreshToken).catch(() => null);
|
|
85
|
+
if (!next?.jwt) return claimed;
|
|
86
|
+
const refreshed: HostConfig = { ...current, auth: { ...current.auth, accessToken: next } };
|
|
87
|
+
await GlobalConfig.#setAkanGlobalConfig(
|
|
88
|
+
GlobalConfig.#withHost(await GlobalConfig.#getAkanGlobalConfig(), refreshed),
|
|
89
|
+
);
|
|
90
|
+
return refreshed;
|
|
91
|
+
});
|
|
92
|
+
} catch {
|
|
93
|
+
// No lock, or a write that failed: what is stored is used as it is, and nothing is refreshed outside the lock.
|
|
94
|
+
return await GlobalConfig.getHostConfig(host);
|
|
95
|
+
}
|
|
59
96
|
}
|
|
60
97
|
static async getRemoteEnvServers(): Promise<AkanGlobalConfig["remoteEnvServers"]> {
|
|
61
|
-
|
|
62
|
-
return akanConfig.remoteEnvServers;
|
|
98
|
+
return (await GlobalConfig.#getAkanGlobalConfig()).remoteEnvServers;
|
|
63
99
|
}
|
|
64
100
|
static async setRemoteEnvServer(name: string, config: RemoteEnvServerConfig) {
|
|
65
|
-
|
|
66
|
-
await GlobalConfig.#setAkanGlobalConfig({
|
|
101
|
+
await GlobalConfig.#update((akanConfig) => ({
|
|
67
102
|
...akanConfig,
|
|
68
|
-
remoteEnvServers: {
|
|
69
|
-
|
|
70
|
-
[name]: config,
|
|
71
|
-
},
|
|
72
|
-
});
|
|
103
|
+
remoteEnvServers: { ...akanConfig.remoteEnvServers, [name]: config },
|
|
104
|
+
}));
|
|
73
105
|
}
|
|
74
106
|
static async removeRemoteEnvServer(name: string) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
...akanConfig,
|
|
79
|
-
remoteEnvServers,
|
|
107
|
+
await GlobalConfig.#update((akanConfig) => {
|
|
108
|
+
const { [name]: _, ...remoteEnvServers } = akanConfig.remoteEnvServers;
|
|
109
|
+
return { ...akanConfig, remoteEnvServers };
|
|
80
110
|
});
|
|
81
111
|
}
|
|
112
|
+
static async getTestTargets(): Promise<TestTargetsConfig> {
|
|
113
|
+
return (await GlobalConfig.#getAkanGlobalConfig()).testTargets;
|
|
114
|
+
}
|
|
115
|
+
static async setTestTargets(testTargets: TestTargetsConfig) {
|
|
116
|
+
await GlobalConfig.#update((akanConfig) => ({
|
|
117
|
+
...akanConfig,
|
|
118
|
+
testTargets: { ...akanConfig.testTargets, ...testTargets },
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
82
121
|
static needRefreshToken(accessToken: AccessToken): boolean {
|
|
83
122
|
return !!accessToken?.expiresAt?.isBefore(dayjs().add(1, "hour"));
|
|
84
123
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { type InlineExtension, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import type { CodeAgentProfile } from "akanjs/common";
|
|
6
|
+
import { AkanEnvKeys } from "../tools/AkanEnvKeys";
|
|
7
|
+
import { type AkanContextFile, AkanContextFiles } from "./AkanContextFiles";
|
|
8
|
+
import { akanCodePaths } from "./akanCodePaths";
|
|
9
|
+
import { akanSystemPrompt } from "./akanSystemPrompt";
|
|
10
|
+
import type { CodeAgentProxy } from "./CodeAgentProxy";
|
|
11
|
+
import { CodeSessionIndex } from "./CodeSessionIndex";
|
|
12
|
+
|
|
13
|
+
export interface AkanCodeServicesOptions {
|
|
14
|
+
workspaceRoot: string;
|
|
15
|
+
cwd: string;
|
|
16
|
+
profile: CodeAgentProfile;
|
|
17
|
+
extensions: InlineExtension[];
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// All four engine services are injected explicitly: any one left out makes the engine fall back to `~/.pi/`.
|
|
22
|
+
export class AkanCodeServices {
|
|
23
|
+
static settings() {
|
|
24
|
+
// In memory on purpose: file settings would be a second configuration surface beside the profile.
|
|
25
|
+
// `enableInstallTelemetry` defaults to true and adds pi-branded headers to OpenRouter/NVIDIA/Cloudflare calls.
|
|
26
|
+
return SettingsManager.inMemory({
|
|
27
|
+
enableInstallTelemetry: false,
|
|
28
|
+
enableAnalytics: false,
|
|
29
|
+
quietStartup: true,
|
|
30
|
+
// Twice the engine's 16,384: at 16k a reasoning model stops mid-thought at the compaction threshold,
|
|
31
|
+
// and the recovery re-sends a near-full window to retry the turn.
|
|
32
|
+
compaction: { reserveTokens: 32_768 },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The engine locks and rewrites `authPath` itself, so two `akan code` refreshing a token at once cannot clobber it.
|
|
37
|
+
static async runtime(workspaceRoot: string, proxy: CodeAgentProxy | null = null) {
|
|
38
|
+
mkdirSync(akanCodePaths.globalDir(), { recursive: true, mode: 0o700 });
|
|
39
|
+
const runtime = await ModelRuntime.create({
|
|
40
|
+
//* Behind a proxy no real key may reach a request, so credentials live in a throwaway file.
|
|
41
|
+
authPath: proxy
|
|
42
|
+
? path.join(mkdtempSync(path.join(tmpdir(), "akan-code-proxy-")), "auth.json")
|
|
43
|
+
: akanCodePaths.authFile(),
|
|
44
|
+
modelsPath: akanCodePaths.modelsFile(),
|
|
45
|
+
});
|
|
46
|
+
if (proxy) await proxy.apply(runtime);
|
|
47
|
+
else await AkanEnvKeys.apply(runtime, workspaceRoot);
|
|
48
|
+
return runtime;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static sessions(workspaceRoot: string, cwd: string, profile: CodeAgentProfile, resume?: string) {
|
|
52
|
+
//* `remote` has no store of its own yet, so it keeps a file session: a pod worker's suspended asks need it.
|
|
53
|
+
if (profile.session.store === "memory") return SessionManager.inMemory(cwd);
|
|
54
|
+
const dir = akanCodePaths.sessionsDir(workspaceRoot);
|
|
55
|
+
mkdirSync(dir, { recursive: true });
|
|
56
|
+
const manager = SessionManager.create(cwd, dir);
|
|
57
|
+
if (!resume) return manager;
|
|
58
|
+
const file = CodeSessionIndex.fileOf(dir, resume);
|
|
59
|
+
// A mistyped id that quietly opened a new session would look like a session that lost its history.
|
|
60
|
+
if (!file) throw new Error(`No stored session ${resume} in ${dir}`);
|
|
61
|
+
manager.setSessionFile(file);
|
|
62
|
+
return manager;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Skills are text, never code that runs, so unlike extensions they may come from the home directory too.
|
|
66
|
+
static #skillPaths(profile: CodeAgentProfile, workspaceRoot: string) {
|
|
67
|
+
if (!profile.context.skills) return [];
|
|
68
|
+
const dirs = [
|
|
69
|
+
akanCodePaths.builtinSkillsDir(),
|
|
70
|
+
akanCodePaths.globalSkillsDir(),
|
|
71
|
+
akanCodePaths.skillsDir(workspaceRoot),
|
|
72
|
+
];
|
|
73
|
+
return dirs.filter((dir): dir is string => !!dir && existsSync(dir));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static resourceOptions(options: AkanCodeServicesOptions) {
|
|
77
|
+
// A path that does not exist is reported as a load error rather than skipped, and these two are optional.
|
|
78
|
+
const present = (dir: string) => (existsSync(dir) ? [dir] : []);
|
|
79
|
+
return {
|
|
80
|
+
noExtensions: true,
|
|
81
|
+
// Disables only the engine's default skill locations (`~/.pi/skills`, `.pi/`); `additionalSkillPaths` merge.
|
|
82
|
+
noSkills: true,
|
|
83
|
+
noPromptTemplates: true,
|
|
84
|
+
noThemes: true,
|
|
85
|
+
noContextFiles: !options.profile.context.projectFiles,
|
|
86
|
+
agentsFilesOverride: ({ agentsFiles }: { agentsFiles: AkanContextFile[] }) => ({
|
|
87
|
+
agentsFiles: options.profile.context.projectFiles
|
|
88
|
+
? AkanContextFiles.extend(agentsFiles, { workspaceRoot: options.workspaceRoot, cwd: options.cwd })
|
|
89
|
+
: agentsFiles,
|
|
90
|
+
}),
|
|
91
|
+
appendSystemPrompt: [akanSystemPrompt(options.profile)],
|
|
92
|
+
additionalExtensionPaths: present(akanCodePaths.extensionsDir(options.workspaceRoot)),
|
|
93
|
+
additionalSkillPaths: AkanCodeServices.#skillPaths(options.profile, options.workspaceRoot),
|
|
94
|
+
extensionFactories: options.extensions,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface AkanContextFile {
|
|
5
|
+
path: string;
|
|
6
|
+
content: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AkanContextFilesOptions {
|
|
10
|
+
workspaceRoot: string;
|
|
11
|
+
cwd: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// The engine takes only the first of `AGENTS.override.md · AGENTS.md · CLAUDE.md` per directory and never reads
|
|
15
|
+
// `.cursor/rules`; this appends what it drops, after the engine's own list.
|
|
16
|
+
export class AkanContextFiles {
|
|
17
|
+
/** Cursor's frontmatter block, which is metadata for its own rule picker and not instruction for a model. */
|
|
18
|
+
static readonly #frontmatter = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
19
|
+
|
|
20
|
+
static extend(base: AkanContextFile[], options: AkanContextFilesOptions): AkanContextFile[] {
|
|
21
|
+
const seen = new Set(base.map((file) => file.path));
|
|
22
|
+
// Keyed by content too: the usual way to carry both names is a symlink or a one-line pointer.
|
|
23
|
+
const contents = new Set(base.map((file) => file.content.trim()));
|
|
24
|
+
const added: AkanContextFile[] = [];
|
|
25
|
+
for (const file of [...AkanContextFiles.#claude(base), ...AkanContextFiles.#cursor(options)]) {
|
|
26
|
+
if (seen.has(file.path) || contents.has(file.content.trim()) || !file.content.trim()) continue;
|
|
27
|
+
seen.add(file.path);
|
|
28
|
+
contents.add(file.content.trim());
|
|
29
|
+
added.push(file);
|
|
30
|
+
}
|
|
31
|
+
return [...base, ...added];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Not beside an `AGENTS.override.md`: that file exists to displace the others in its directory.
|
|
35
|
+
static #claude(base: AkanContextFile[]) {
|
|
36
|
+
const files: AkanContextFile[] = [];
|
|
37
|
+
for (const file of base) {
|
|
38
|
+
if (!/^AGENTS\.(md|MD)$/.test(path.basename(file.path))) continue;
|
|
39
|
+
for (const name of ["CLAUDE.md", "CLAUDE.MD"]) {
|
|
40
|
+
const candidate = path.join(path.dirname(file.path), name);
|
|
41
|
+
const content = AkanContextFiles.#read(candidate);
|
|
42
|
+
if (content !== undefined) files.push({ path: candidate, content });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return files;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Only `alwaysApply: true` or frontmatter-less rules: Cursor's `globs`/`description`/manual kinds are scoped.
|
|
49
|
+
static #cursor(options: AkanContextFilesOptions) {
|
|
50
|
+
const files: AkanContextFile[] = [];
|
|
51
|
+
const roots = [...new Set([options.workspaceRoot, options.cwd])];
|
|
52
|
+
for (const root of roots) {
|
|
53
|
+
const legacy = AkanContextFiles.#read(path.join(root, ".cursorrules"));
|
|
54
|
+
if (legacy !== undefined) files.push({ path: path.join(root, ".cursorrules"), content: legacy });
|
|
55
|
+
for (const file of AkanContextFiles.#mdcFiles(path.join(root, ".cursor", "rules"))) {
|
|
56
|
+
const raw = AkanContextFiles.#read(file);
|
|
57
|
+
if (raw === undefined) continue;
|
|
58
|
+
const body = AkanContextFiles.#applied(raw);
|
|
59
|
+
if (body !== undefined) files.push({ path: file, content: body });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return files;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static #applied(raw: string) {
|
|
66
|
+
const match = AkanContextFiles.#frontmatter.exec(raw);
|
|
67
|
+
if (!match) return raw;
|
|
68
|
+
if (!/^\s*alwaysApply\s*:\s*true\s*$/m.test(match[1] ?? "")) return undefined;
|
|
69
|
+
return raw.slice(match[0].length);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Sorted, because the order rules reach the model in is otherwise the order a directory happens to list. */
|
|
73
|
+
static #mdcFiles(dir: string) {
|
|
74
|
+
if (!existsSync(dir)) return [];
|
|
75
|
+
try {
|
|
76
|
+
return readdirSync(dir, { recursive: true, withFileTypes: true })
|
|
77
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".mdc"))
|
|
78
|
+
.map((entry) => path.join(entry.parentPath ?? dir, entry.name))
|
|
79
|
+
.sort();
|
|
80
|
+
} catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static #read(file: string) {
|
|
86
|
+
try {
|
|
87
|
+
if (!existsSync(file) || !statSync(file).isFile()) return undefined;
|
|
88
|
+
return readFileSync(file, "utf8").replace(/^/, "");
|
|
89
|
+
} catch {
|
|
90
|
+
// A rule file that cannot be read costs its own rules. It must not cost the session its start.
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|