@moondeck/station 0.0.1 → 1.0.1
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/LICENSE +21 -0
- package/README.md +149 -2
- package/bin/uai-host.mjs +130 -0
- package/db/migrations/0000_host_tasks.sql +12 -0
- package/db/migrations/0001_host_ui.sql +11 -0
- package/db/migrations/0002_host_github_tokens.sql +8 -0
- package/db/migrations/0003_host_ssh_keys.sql +8 -0
- package/db/migrations/0004_host_owner_name.sql +1 -0
- package/db/migrations/0005_host_env_vars.sql +5 -0
- package/db/migrations/0006_host_project_env.sql +9 -0
- package/db/migrations/0007_host_github_token_kind.sql +7 -0
- package/db/migrations/0008_host_mcp_connections.sql +18 -0
- package/db/migrations/0009_host_agent_sessions.sql +15 -0
- package/db/migrations/0010_host_engine_accounts.sql +13 -0
- package/db/migrations/0011_codex_thread_id.sql +5 -0
- package/db/migrations/0012_agent_session_attach_key.sql +4 -0
- package/db/migrations/0013_github_credential_generations.sql +11 -0
- package/db/migrations/0014_host_inventory_event_index.sql +1 -0
- package/db/migrations/0015_host_settings.sql +9 -0
- package/db/migrations/0016_task_environment.sql +2 -0
- package/db/migrations/0017_engine_account_scope.sql +7 -0
- package/db/migrations/0018_engine_account_position.sql +3 -0
- package/db/migrations/0019_engine_account_store_ref.sql +3 -0
- package/db/migrations/0020_user_credential_store_ref.sql +6 -0
- package/db/migrations/0021_project_env_store_ref.sql +3 -0
- package/db/migrations/0022_task_project_ids.sql +5 -0
- package/db/migrations/0023_project_env_org.sql +4 -0
- package/db/migrations/0024_task_env_mode.sql +9 -0
- package/db/migrations/0025_project_env_authority.sql +147 -0
- package/db/migrations/0026_project_env_replica.sql +150 -0
- package/db/migrations/0027_cloud_credential_foundation.sql +51 -0
- package/db/migrations/0028_leased_engine_runtimes.sql +11 -0
- package/db/migrations/0029_task_shared_files_mode.sql +5 -0
- package/db/migrations/0030_credential_cleanup_restops.sql +12 -0
- package/db/migrations/0031_first_turn_receipts.sql +9 -0
- package/db/migrations/meta/_journal.json +230 -0
- package/db/schema.ts +871 -0
- package/images/standard/Dockerfile +347 -0
- package/images/standard/README.md +167 -0
- package/images/standard/container/code-server-settings.json +63 -0
- package/images/standard/container/corepack-version +1 -0
- package/images/standard/container/uai-init +704 -0
- package/images/standard/container/uai-materialize-runtimes +1527 -0
- package/images/standard/container/uai-task-forwarder.mjs +303 -0
- package/images/standard/tool-versions +2 -0
- package/lib/__fixtures__/skill-index/SKILL.md +10 -0
- package/lib/__fixtures__/skill-index/skills/accessibility-checklist.md +7 -0
- package/lib/__fixtures__/skill-index/skills/brand-voice.md +9 -0
- package/lib/__fixtures__/skill-index/skills/design-tokens.md +9 -0
- package/lib/agent-cli.ts +1507 -0
- package/lib/agent-errors.ts +22 -0
- package/lib/agent.ts +672 -0
- package/lib/agents/attach-compatibility.ts +51 -0
- package/lib/agents/claude.ts +918 -0
- package/lib/agents/codex-token-usage.fixture.json +130 -0
- package/lib/agents/codex.ts +1392 -0
- package/lib/agents/cursor-period-usage.ts +253 -0
- package/lib/agents/cursor.ts +562 -0
- package/lib/agents/dispatch.ts +78 -0
- package/lib/agents/durable-proc.ts +334 -0
- package/lib/agents/factory.ts +57 -0
- package/lib/agents/grok.ts +381 -0
- package/lib/agents/kimi.ts +329 -0
- package/lib/agents/machine-durable.ts +604 -0
- package/lib/agents/mock.ts +133 -0
- package/lib/agents/mode.ts +64 -0
- package/lib/agents/opencode.ts +508 -0
- package/lib/agents/output-source.ts +3 -0
- package/lib/agents/proc.ts +221 -0
- package/lib/agents/rate-limit.ts +337 -0
- package/lib/agents/registry.ts +182 -0
- package/lib/agents/session-state.ts +197 -0
- package/lib/agents/skill-index-preamble.fixture.ts +26 -0
- package/lib/agents/transport.ts +741 -0
- package/lib/agents/types.ts +254 -0
- package/lib/agents/uai.ts +106 -0
- package/lib/agents/usage.ts +44 -0
- package/lib/apple-one-off.ts +277 -0
- package/lib/apple-runtime-recycle.ts +237 -0
- package/lib/apple-uninstall-teardown.ts +224 -0
- package/lib/assignment-evidence.ts +94 -0
- package/lib/attachments.ts +48 -0
- package/lib/browser-testing.ts +1537 -0
- package/lib/capture-flow.ts +332 -0
- package/lib/cloud-credential-cleanup-adapter.ts +123 -0
- package/lib/cloud-credential-host.ts +512 -0
- package/lib/cloud-credential-leases.ts +2587 -0
- package/lib/cloud-credential-runtime.ts +1357 -0
- package/lib/cloud-state.ts +56 -0
- package/lib/codex-auth.ts +456 -0
- package/lib/command-db.ts +310 -0
- package/lib/compose-network-recovery.ts +283 -0
- package/lib/connection-secret-redaction.ts +25 -0
- package/lib/container-runtime.ts +1470 -0
- package/lib/credential-cleanup-inventory.ts +490 -0
- package/lib/credential-consumers/engine-anchor.ts +1880 -0
- package/lib/credential-consumers/engines.ts +797 -0
- package/lib/credential-consumers/files.ts +934 -0
- package/lib/credential-consumers/github.ts +702 -0
- package/lib/credential-consumers/mcp.ts +389 -0
- package/lib/credential-consumers/task-files.ts +127 -0
- package/lib/credential-consumers/task-secrets.ts +10 -0
- package/lib/credential-store-pickup.ts +149 -0
- package/lib/credential-store-scope.ts +112 -0
- package/lib/credential-store.ts +932 -0
- package/lib/credentials-activation-authority.ts +709 -0
- package/lib/credentials-purge.ts +1006 -0
- package/lib/custom-connection-capture.ts +395 -0
- package/lib/custom-connection-launch.ts +384 -0
- package/lib/custom-connection-runtime.ts +141 -0
- package/lib/db.ts +80 -0
- package/lib/disk-pressure.ts +42 -0
- package/lib/docker-exec.ts +156 -0
- package/lib/engine-accounts.ts +1970 -0
- package/lib/engine-connect.ts +321 -0
- package/lib/engine-credential-env.ts +52 -0
- package/lib/engine-login.ts +2933 -0
- package/lib/engines.ts +1342 -0
- package/lib/enrollment-state.ts +251 -0
- package/lib/env-file.ts +246 -0
- package/lib/env-schema.ts +937 -0
- package/lib/env.ts +195 -0
- package/lib/first-turn-receipts.ts +62 -0
- package/lib/fs-aliases.ts +49 -0
- package/lib/git-clone-credentials.ts +334 -0
- package/lib/git-diff.ts +530 -0
- package/lib/git-identity.ts +715 -0
- package/lib/github-git-auth.ts +207 -0
- package/lib/github-origin.ts +28 -0
- package/lib/github-tokens.ts +2439 -0
- package/lib/host-cloud-url.ts +62 -0
- package/lib/host-config.ts +279 -0
- package/lib/host-env.ts +424 -0
- package/lib/host-logs.ts +965 -0
- package/lib/internal-connections.ts +5 -0
- package/lib/keyed-promise-tail.ts +23 -0
- package/lib/launch-mode.ts +25 -0
- package/lib/lease.ts +239 -0
- package/lib/legacy-host.ts +769 -0
- package/lib/legacy-runtime-retirement.ts +424 -0
- package/lib/legacy-runtime-v1.fixture.ts +627 -0
- package/lib/machine-exec.ts +238 -0
- package/lib/machine-gateway-tunnel.ts +344 -0
- package/lib/machine-keys.ts +65 -0
- package/lib/machine-project-env-loader.ts +152 -0
- package/lib/machine-provider-aws.ts +923 -0
- package/lib/machine-provider-local.ts +338 -0
- package/lib/machine-provider.ts +90 -0
- package/lib/managed-activation-watcher.ts +72 -0
- package/lib/managed-install-owner-watcher.ts +55 -0
- package/lib/managed-operation-drain.ts +57 -0
- package/lib/managed-runtime.ts +4296 -0
- package/lib/managed-update-scheduler.ts +125 -0
- package/lib/mcp-capture.ts +865 -0
- package/lib/mcp-config-lock.ts +2 -0
- package/lib/mcp-connections.ts +959 -0
- package/lib/mcp-gateway.ts +2594 -0
- package/lib/obs.ts +512 -0
- package/lib/openrouter-budget.ts +247 -0
- package/lib/orchestrator.ts +11409 -0
- package/lib/person-capture.ts +327 -0
- package/lib/preview-ports.ts +85 -0
- package/lib/preview-sidecar.ts +331 -0
- package/lib/project-env-authority.ts +1319 -0
- package/lib/project-env-feature.ts +74 -0
- package/lib/project-env-legacy-tags.ts +130 -0
- package/lib/project-env-loader.ts +242 -0
- package/lib/project-env-replica.ts +2094 -0
- package/lib/project-env-schema-capture.ts +342 -0
- package/lib/project-env-staging.ts +148 -0
- package/lib/project-env-task-policy.ts +176 -0
- package/lib/project-env-task-replica.ts +1752 -0
- package/lib/release-feed.ts +61 -0
- package/lib/release-manifest.ts +710 -0
- package/lib/release-trust.ts +46 -0
- package/lib/repo-clone.ts +38 -0
- package/lib/routing/shadow.ts +261 -0
- package/lib/runner-boot.ts +10 -0
- package/lib/runtime-activation-tail.ts +279 -0
- package/lib/runtime-archive.ts +1216 -0
- package/lib/runtime-authority.ts +166 -0
- package/lib/runtime-guard.ts +36 -0
- package/lib/runtime-provider-state.ts +169 -0
- package/lib/runtime-reconnect.ts +86 -0
- package/lib/runtime-state.ts +1033 -0
- package/lib/safe-http.ts +364 -0
- package/lib/secrets-proxy/address.ts +95 -0
- package/lib/secrets-proxy/audit.ts +30 -0
- package/lib/secrets-proxy/ca-store.ts +200 -0
- package/lib/secrets-proxy/ca.ts +470 -0
- package/lib/secrets-proxy/container-steps.ts +138 -0
- package/lib/secrets-proxy/der.ts +273 -0
- package/lib/secrets-proxy/engine.ts +1633 -0
- package/lib/secrets-proxy/host.ts +93 -0
- package/lib/secrets-proxy/hostname.ts +57 -0
- package/lib/secrets-proxy/placeholders.ts +196 -0
- package/lib/secrets-proxy/policy.ts +150 -0
- package/lib/secrets-proxy/readiness.ts +148 -0
- package/lib/secrets-proxy/runtime.ts +582 -0
- package/lib/secrets-proxy/scrub.ts +559 -0
- package/lib/secrets-proxy/sidecar-view.ts +139 -0
- package/lib/secrets-proxy/task-scripts.ts +262 -0
- package/lib/secrets-proxy/task-trust.ts +605 -0
- package/lib/secrets-proxy/trust-watch.ts +80 -0
- package/lib/secrets.ts +71 -0
- package/lib/shared-files-s3.ts +976 -0
- package/lib/shared-files.ts +156 -0
- package/lib/sidecar-assign.ts +49 -0
- package/lib/sidecar-guard.ts +58 -0
- package/lib/sidecar-resume.ts +178 -0
- package/lib/sidecar.ts +69 -0
- package/lib/signin-box-credentials.ts +465 -0
- package/lib/signin-box.ts +114 -0
- package/lib/skill-index-example.fixture.ts +56 -0
- package/lib/skill-layout.ts +417 -0
- package/lib/skills.ts +489 -0
- package/lib/ssh.ts +401 -0
- package/lib/standard-image-cleanup.ts +117 -0
- package/lib/standard-image-pin.ts +207 -0
- package/lib/standard-image.ts +1697 -0
- package/lib/stopped-task-status-queue.ts +120 -0
- package/lib/task-container-cli.ts +269 -0
- package/lib/task-credential-cleanup.ts +752 -0
- package/lib/task-diff-sandbox.ts +464 -0
- package/lib/task-diff.ts +91 -0
- package/lib/task-env-forwarder.ts +228 -0
- package/lib/task-env-source.ts +249 -0
- package/lib/task-environment/anchor.ts +31 -0
- package/lib/task-environment/apple-container.ts +860 -0
- package/lib/task-environment/docker.ts +1344 -0
- package/lib/task-environment/index.ts +466 -0
- package/lib/task-environment/legacy-adoption.ts +467 -0
- package/lib/task-environment/machine-environment.ts +61 -0
- package/lib/task-environment/machine-task-up.ts +1227 -0
- package/lib/task-environment/machine.ts +974 -0
- package/lib/task-environment/registry.ts +59 -0
- package/lib/task-environment/self-identity.ts +20 -0
- package/lib/task-environment/self.ts +566 -0
- package/lib/task-environment/types.ts +642 -0
- package/lib/task-environment/workspace-files.ts +685 -0
- package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
- package/lib/task-files-machine-retirement-outbox.ts +592 -0
- package/lib/task-files-machine-retirement.ts +116 -0
- package/lib/task-files-readiness.ts +127 -0
- package/lib/task-gateway-port.ts +22 -0
- package/lib/task-gateway-protocol.ts +190 -0
- package/lib/task-gateway-recovery.ts +47 -0
- package/lib/task-gateway-state.ts +512 -0
- package/lib/task-identity.ts +19 -0
- package/lib/task-inventory.ts +682 -0
- package/lib/task-machine-credentials-activation.ts +95 -0
- package/lib/task-machine-swap-safety.ts +729 -0
- package/lib/task-person-credentials.ts +76 -0
- package/lib/task-project-ops-host.ts +147 -0
- package/lib/task-project-ops.ts +1062 -0
- package/lib/task-secret-staging.ts +16 -0
- package/lib/task-status.ts +46 -0
- package/lib/task-token.ts +46 -0
- package/lib/token-broker-adopt.ts +344 -0
- package/lib/token-broker-lambda.ts +105 -0
- package/lib/token-broker-local.ts +392 -0
- package/lib/token-broker-revision.ts +310 -0
- package/lib/token-broker-ssm.ts +525 -0
- package/lib/token-broker.ts +218 -0
- package/lib/token-lanes/openai-oauth.ts +123 -0
- package/lib/token-lanes/registry.ts +45 -0
- package/lib/token-lanes/types.ts +163 -0
- package/lib/token-lanes/xai-oauth.ts +165 -0
- package/lib/transcript.ts +128 -0
- package/lib/tunnel-registry.ts +220 -0
- package/lib/tunnel-runtime.ts +235 -0
- package/lib/ulid.ts +16 -0
- package/lib/user-bus-env.ts +116 -0
- package/lib/user-credentials-store.ts +435 -0
- package/lib/vnode-pressure.ts +142 -0
- package/package.json +115 -8
- package/runner/runner.mjs +442 -0
- package/scripts/agent/_common.sh +633 -0
- package/scripts/agent/env-hook.sh +626 -0
- package/scripts/agent/moondeck-cli.sh +5 -0
- package/scripts/agent/task-down.sh +291 -0
- package/scripts/agent/task-status.sh +70 -0
- package/scripts/agent/task-up.sh +3761 -0
- package/scripts/agent/uai-env-exec.sh +26 -0
- package/scripts/install/darwin.ts +1048 -0
- package/scripts/install/linux.ts +958 -0
- package/scripts/install/types.ts +78 -0
- package/scripts/install/util.ts +254 -0
- package/scripts/install/win.ts +143 -0
- package/src/activation-respawn.ts +155 -0
- package/src/apple-tunnel-route.ts +104 -0
- package/src/bridge-diagnostics.ts +39 -0
- package/src/capture-wire.ts +102 -0
- package/src/cli.ts +2585 -0
- package/src/cloud-credentials.ts +2144 -0
- package/src/cloud-origins.ts +23 -0
- package/src/credential-location.ts +248 -0
- package/src/credential-wire-validation.ts +25 -0
- package/src/custom-connections.ts +237 -0
- package/src/engine-account-paths.ts +11 -0
- package/src/engine-models.ts +39 -0
- package/src/engine-usage.ts +104 -0
- package/src/enrollment-next-steps.ts +24 -0
- package/src/event-outbox.ts +185 -0
- package/src/index.ts +2907 -0
- package/src/load-env.ts +97 -0
- package/src/machine-tunnel-route.ts +156 -0
- package/src/main.ts +6363 -0
- package/src/never-proxied-domains.ts +31 -0
- package/src/outbound-frame.ts +31 -0
- package/src/paths.ts +81 -0
- package/src/project-env-latest.ts +130 -0
- package/src/project-env-policy.ts +396 -0
- package/src/project-env-wording.ts +53 -0
- package/src/protocol.ts +5550 -0
- package/src/runtime-bootstrap.ts +165 -0
- package/src/station-identity.ts +96 -0
- package/src/task-env-names.ts +28 -0
- package/src/task-secrets.ts +75 -0
- package/src/tunnel-dial.ts +102 -0
- package/src/ui/server.ts +852 -0
- package/src/ui/types.ts +173 -0
- package/src/workspace-path-policy.ts +41 -0
- package/ui/app.js +1102 -0
- package/ui/index.html +90 -0
- package/ui/style.css +685 -0
- package/ui/uai-favicon.svg +21 -0
- package/ui/uai-logo-black.svg +9 -0
- package/ui/uai-wheel.svg +9 -0
- package/bin/moondeck-station.mjs +0 -3
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KimiSession — a real AgentSession backed by Moonshot's **Kimi Code** CLI,
|
|
3
|
+
* run inside the task environment:
|
|
4
|
+
*
|
|
5
|
+
* ~/.kimi-code/bin/kimi \
|
|
6
|
+
* -p "<prompt>" -m kimi-code/k3 --output-format stream-json [-r <sessionId>]
|
|
7
|
+
*
|
|
8
|
+
* Unlike Claude (persistent stdin session) and Codex (`app-server` JSON-RPC),
|
|
9
|
+
* Kimi Code is **one-shot per turn**: `-p` runs a single prompt to completion,
|
|
10
|
+
* streams newline-delimited JSON to stdout, and exits. Multi-turn continuity is
|
|
11
|
+
* `-r <sessionId>` (the session lives in Kimi's cloud + the container's
|
|
12
|
+
* ~/.kimi-code). So each `send()` spawns a fresh provider process; sends are
|
|
13
|
+
* serialized so a second message can't race a running turn.
|
|
14
|
+
*
|
|
15
|
+
* Stream-json vocabulary (reverse-engineered — OpenAI-chat-style role lines):
|
|
16
|
+
* {"role":"assistant","content":"…"} → message_complete
|
|
17
|
+
* {"role":"assistant","tool_calls":[{function:{name,arguments}}]} → tool_call
|
|
18
|
+
* {"role":"tool","tool_call_id":…,"content":…} → (no event; folded)
|
|
19
|
+
* {"role":"meta","type":"session.resume_hint","session_id"} → captured for -r
|
|
20
|
+
*
|
|
21
|
+
* AUTH is the operator's Kimi Code **subscription**: `~/.kimi-code` (config +
|
|
22
|
+
* credentials/kimi-code.json) is docker-cp'd into the container at task-up
|
|
23
|
+
* (task-up.sh), same pattern as Codex's ~/.codex — never the cloud (ADR-015).
|
|
24
|
+
* `-p` runs non-interactively and auto-runs tools (the container is the
|
|
25
|
+
* isolation boundary), so there are no permission prompts to resolve.
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync } from "node:fs";
|
|
28
|
+
import { homedir } from "node:os";
|
|
29
|
+
import { join } from "node:path";
|
|
30
|
+
|
|
31
|
+
import { requireContainerRuntimeOperational } from "../runtime-guard";
|
|
32
|
+
import type {
|
|
33
|
+
TaskEnvironmentAgentSessionSurface,
|
|
34
|
+
TaskEnvironmentSessionRequest,
|
|
35
|
+
} from "../task-environment/types";
|
|
36
|
+
import { newId } from "../ulid";
|
|
37
|
+
import {
|
|
38
|
+
AGENT_SESSION_OUTPUT_BUFFER_BYTES,
|
|
39
|
+
spawnEnvironmentProcess,
|
|
40
|
+
type AgentEnvironmentProcess,
|
|
41
|
+
} from "./proc";
|
|
42
|
+
import { register } from "./registry";
|
|
43
|
+
import type {
|
|
44
|
+
AgentEvent,
|
|
45
|
+
AgentEventHandler,
|
|
46
|
+
AgentKind,
|
|
47
|
+
AgentSession,
|
|
48
|
+
RosterAgent,
|
|
49
|
+
} from "./types";
|
|
50
|
+
|
|
51
|
+
/** Installed by the image's install.sh (code.kimi.com); absolute so a
|
|
52
|
+
* non-login provider process doesn't depend on PATH. */
|
|
53
|
+
const KIMI_BIN = "/home/node/.kimi-code/bin/kimi";
|
|
54
|
+
|
|
55
|
+
// Model ids from `~/.kimi-code/config.toml` (`kimi -m <id>`). k3 is the
|
|
56
|
+
// frontier model; the K2.7 coding pair are cheaper/faster. UPDATE WHEN KIMI
|
|
57
|
+
// CHANGES its lineup. Order = display order in the cloud picker.
|
|
58
|
+
const KIMI_MODELS = [
|
|
59
|
+
"kimi-code/k3",
|
|
60
|
+
"kimi-code/kimi-for-coding",
|
|
61
|
+
"kimi-code/kimi-for-coding-highspeed",
|
|
62
|
+
];
|
|
63
|
+
const KIMI_DEFAULT_MODEL = "kimi-code/k3";
|
|
64
|
+
|
|
65
|
+
// Kimi Code has no per-invocation reasoning-effort flag (effort is a
|
|
66
|
+
// config.toml model default), so nothing to offer in the picker.
|
|
67
|
+
const KIMI_EFFORTS: string[] = [];
|
|
68
|
+
|
|
69
|
+
/** Host path to the subscription credential — its presence gates the engine
|
|
70
|
+
* (mirrors Codex's ~/.codex/auth.json check). */
|
|
71
|
+
function kimiCredPath(): string {
|
|
72
|
+
return join(
|
|
73
|
+
process.env.UAI_OWNER_HOME?.trim() || homedir(),
|
|
74
|
+
".kimi-code",
|
|
75
|
+
"credentials",
|
|
76
|
+
"kimi-code.json",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Pure protocol mapping — one stream-json line → AgentEvent[] (+ session id).
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
/** Best-effort one-line detail for a tool card from the tool's JSON args. */
|
|
85
|
+
function toolDetail(rawArgs: unknown): string {
|
|
86
|
+
if (typeof rawArgs !== "string") return "";
|
|
87
|
+
try {
|
|
88
|
+
const a = JSON.parse(rawArgs) as Record<string, unknown>;
|
|
89
|
+
const pick = a.command ?? a.path ?? a.file_path ?? a.pattern ?? a.query;
|
|
90
|
+
return typeof pick === "string" ? pick : rawArgs;
|
|
91
|
+
} catch {
|
|
92
|
+
return rawArgs;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface MappedLine {
|
|
97
|
+
events: AgentEvent[];
|
|
98
|
+
/** A session.resume_hint id, when this line carried one. */
|
|
99
|
+
sessionId?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Map a single Kimi stream-json line. Non-JSON lines (a tool's raw stdout that
|
|
104
|
+
* Kimi echoes) and lines with no user-facing meaning (`role:"tool"` results,
|
|
105
|
+
* `role:"user"` echoes) map to no events.
|
|
106
|
+
*/
|
|
107
|
+
export function mapKimiLine(line: string): MappedLine {
|
|
108
|
+
const trimmed = line.trim();
|
|
109
|
+
if (!trimmed.startsWith("{")) return { events: [] };
|
|
110
|
+
let msg: Record<string, unknown>;
|
|
111
|
+
try {
|
|
112
|
+
msg = JSON.parse(trimmed) as Record<string, unknown>;
|
|
113
|
+
} catch {
|
|
114
|
+
return { events: [] };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (msg.role === "assistant") {
|
|
118
|
+
if (typeof msg.content === "string" && msg.content.length > 0) {
|
|
119
|
+
return { events: [{ type: "message_complete", text: msg.content }] };
|
|
120
|
+
}
|
|
121
|
+
if (Array.isArray(msg.tool_calls)) {
|
|
122
|
+
const events: AgentEvent[] = [];
|
|
123
|
+
for (const raw of msg.tool_calls) {
|
|
124
|
+
const tc = (raw ?? {}) as Record<string, unknown>;
|
|
125
|
+
const fn = (tc.function ?? {}) as Record<string, unknown>;
|
|
126
|
+
const name = typeof fn.name === "string" ? fn.name : "tool";
|
|
127
|
+
events.push({
|
|
128
|
+
type: "tool_call",
|
|
129
|
+
id: typeof tc.id === "string" ? tc.id : newId(),
|
|
130
|
+
title: name,
|
|
131
|
+
detail: toolDetail(fn.arguments),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return { events };
|
|
135
|
+
}
|
|
136
|
+
return { events: [] };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (
|
|
140
|
+
msg.role === "meta" &&
|
|
141
|
+
msg.type === "session.resume_hint" &&
|
|
142
|
+
typeof msg.session_id === "string"
|
|
143
|
+
) {
|
|
144
|
+
return { events: [], sessionId: msg.session_id };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { events: [] };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Session
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
/** Injectable spawn seam (tests provide a fake). */
|
|
155
|
+
export type Spawner = (
|
|
156
|
+
request: TaskEnvironmentSessionRequest,
|
|
157
|
+
) => AgentEnvironmentProcess;
|
|
158
|
+
|
|
159
|
+
export class KimiSession implements AgentSession {
|
|
160
|
+
readonly agentId: string;
|
|
161
|
+
readonly kind: AgentKind = "kimi";
|
|
162
|
+
|
|
163
|
+
private readonly environment: TaskEnvironmentAgentSessionSurface;
|
|
164
|
+
private readonly model?: string;
|
|
165
|
+
private readonly systemPreamble: string;
|
|
166
|
+
private readonly agentEnv: Record<string, string>;
|
|
167
|
+
private readonly spawner: Spawner;
|
|
168
|
+
|
|
169
|
+
private readonly handlers = new Set<AgentEventHandler>();
|
|
170
|
+
private sessionId: string | null = null;
|
|
171
|
+
private current: AgentEnvironmentProcess | null = null;
|
|
172
|
+
private queue: Promise<void> = Promise.resolve();
|
|
173
|
+
private sentPreamble = false;
|
|
174
|
+
private closed = false;
|
|
175
|
+
|
|
176
|
+
constructor(args: {
|
|
177
|
+
taskId: string;
|
|
178
|
+
agent: RosterAgent;
|
|
179
|
+
environment: TaskEnvironmentAgentSessionSurface;
|
|
180
|
+
systemPreamble: string;
|
|
181
|
+
agentEnv?: Record<string, string>;
|
|
182
|
+
spawner?: Spawner;
|
|
183
|
+
}) {
|
|
184
|
+
this.agentId = args.agent.id;
|
|
185
|
+
this.environment = args.environment;
|
|
186
|
+
this.model = args.agent.model;
|
|
187
|
+
this.systemPreamble = args.systemPreamble;
|
|
188
|
+
this.agentEnv = args.agentEnv ?? {};
|
|
189
|
+
this.spawner =
|
|
190
|
+
args.spawner ?? ((request) => spawnEnvironmentProcess(this.environment, request));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
onEvent(handler: AgentEventHandler): () => void {
|
|
194
|
+
this.handlers.add(handler);
|
|
195
|
+
return () => this.handlers.delete(handler);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private emit(event: AgentEvent): void {
|
|
199
|
+
if (this.closed && event.type !== "exit") return;
|
|
200
|
+
for (const h of this.handlers) h(event);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Kimi is one-shot; serialize turns so a second message queues behind the
|
|
204
|
+
* running one instead of spawning a racing `kimi -p`. */
|
|
205
|
+
async send(text: string): Promise<void> {
|
|
206
|
+
if (this.closed) return;
|
|
207
|
+
const turn = this.queue.catch(() => {}).then(() => this.runTurn(text));
|
|
208
|
+
this.queue = turn;
|
|
209
|
+
return turn;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private buildRequest(prompt: string): TaskEnvironmentSessionRequest {
|
|
213
|
+
const argv = [
|
|
214
|
+
KIMI_BIN,
|
|
215
|
+
"-p",
|
|
216
|
+
prompt,
|
|
217
|
+
"--output-format",
|
|
218
|
+
"stream-json",
|
|
219
|
+
];
|
|
220
|
+
if (this.model) argv.push("-m", this.model);
|
|
221
|
+
// Continue the same Kimi session across turns (captured from the first
|
|
222
|
+
// turn's resume_hint). First turn has none → a fresh session.
|
|
223
|
+
if (this.sessionId) argv.push("-r", this.sessionId);
|
|
224
|
+
return {
|
|
225
|
+
argv: argv as [string, ...string[]],
|
|
226
|
+
projectEnvProfile: "task",
|
|
227
|
+
user: "node",
|
|
228
|
+
env: this.agentEnv,
|
|
229
|
+
maxOutputBytes: AGENT_SESSION_OUTPUT_BUFFER_BYTES,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private async runTurn(text: string): Promise<void> {
|
|
234
|
+
if (this.closed) return;
|
|
235
|
+
// A turn may have spent minutes queued behind its predecessor. Admission
|
|
236
|
+
// at send() time is stale; this synchronous check immediately precedes the
|
|
237
|
+
// provider process launch and leaves the first-turn preamble unconsumed on fail.
|
|
238
|
+
requireContainerRuntimeOperational();
|
|
239
|
+
// Kimi's `-p` has no system-prompt flag, so fold the channel briefing into
|
|
240
|
+
// the FIRST turn's prompt (later turns carry it via the resumed session).
|
|
241
|
+
const prompt =
|
|
242
|
+
!this.sentPreamble && this.systemPreamble.trim().length > 0
|
|
243
|
+
? `${this.systemPreamble}\n\n---\n\n${text}`
|
|
244
|
+
: text;
|
|
245
|
+
this.sentPreamble = true;
|
|
246
|
+
|
|
247
|
+
const child = this.spawner(this.buildRequest(prompt));
|
|
248
|
+
this.current = child;
|
|
249
|
+
|
|
250
|
+
let buf = "";
|
|
251
|
+
const consume = (chunk: string): void => {
|
|
252
|
+
buf += chunk;
|
|
253
|
+
let nl: number;
|
|
254
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
255
|
+
const line = buf.slice(0, nl);
|
|
256
|
+
buf = buf.slice(nl + 1);
|
|
257
|
+
const { events, sessionId } = mapKimiLine(line);
|
|
258
|
+
if (sessionId) this.sessionId = sessionId;
|
|
259
|
+
for (const e of events) this.emit(e);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
child.stdout?.on("data", (b: Buffer) => consume(b.toString("utf8")));
|
|
263
|
+
let stderr = "";
|
|
264
|
+
child.stderr?.on("data", (b: Buffer) => {
|
|
265
|
+
stderr += b.toString("utf8");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await new Promise<void>((resolve) => {
|
|
269
|
+
child.on("exit", (code) => {
|
|
270
|
+
this.current = null;
|
|
271
|
+
if (buf.trim().length > 0) consume("\n"); // flush a trailing line
|
|
272
|
+
if (!this.closed) {
|
|
273
|
+
if (code !== 0) {
|
|
274
|
+
const tail = stderr.trim().slice(-500);
|
|
275
|
+
this.emit({
|
|
276
|
+
type: "error",
|
|
277
|
+
message: `kimi exited ${code ?? "null"}${tail ? `: ${tail}` : ""}`,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
this.emit({ type: "turn_complete" });
|
|
281
|
+
}
|
|
282
|
+
resolve();
|
|
283
|
+
});
|
|
284
|
+
child.on("error", (err) => {
|
|
285
|
+
this.current = null;
|
|
286
|
+
if (!this.closed) {
|
|
287
|
+
this.emit({ type: "error", message: `kimi spawn failed: ${err.message}` });
|
|
288
|
+
this.emit({ type: "turn_complete" });
|
|
289
|
+
}
|
|
290
|
+
resolve();
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async interrupt(): Promise<void> {
|
|
296
|
+
// Kill the running turn's exec. Best-effort — the in-container `kimi` may
|
|
297
|
+
// finish the current tool, but no further output is emitted.
|
|
298
|
+
this.current?.kill("SIGKILL");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// `-p` mode auto-approves tools (the container is the sandbox), so no
|
|
302
|
+
// permission requests are ever emitted — nothing to resolve.
|
|
303
|
+
async resolvePermission(): Promise<void> {
|
|
304
|
+
/* no-op */
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async close(): Promise<void> {
|
|
308
|
+
if (this.closed) return;
|
|
309
|
+
this.closed = true;
|
|
310
|
+
this.current?.kill("SIGKILL");
|
|
311
|
+
this.current = null;
|
|
312
|
+
this.emit({ type: "exit", code: 0 });
|
|
313
|
+
this.handlers.clear();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Register the Kimi Code adapter at module load (ADR-021).
|
|
318
|
+
register({
|
|
319
|
+
kind: "kimi",
|
|
320
|
+
label: "Kimi Code",
|
|
321
|
+
supportedModels: () => [...KIMI_MODELS],
|
|
322
|
+
defaultModel: KIMI_DEFAULT_MODEL,
|
|
323
|
+
supportedEfforts: () => [...KIMI_EFFORTS],
|
|
324
|
+
// Gated on the operator's Kimi Code subscription credential (copied into
|
|
325
|
+
// containers at task-up). No login → the engine isn't advertised.
|
|
326
|
+
available: () => existsSync(kimiCredPath()),
|
|
327
|
+
create: async ({ taskId, agent, environment, systemPreamble, agentEnv }) =>
|
|
328
|
+
new KimiSession({ taskId, agent, environment, systemPreamble, agentEnv }),
|
|
329
|
+
});
|