@suwujs/king-ai 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +96 -0
  2. package/dist/src/agent-config-validation.d.ts +9 -0
  3. package/dist/src/agent-config-validation.js +30 -0
  4. package/dist/src/api.d.ts +4 -0
  5. package/dist/src/api.js +48 -0
  6. package/dist/src/attachments.d.ts +45 -0
  7. package/dist/src/attachments.js +322 -0
  8. package/dist/src/cli.d.ts +20 -0
  9. package/dist/src/cli.js +1697 -0
  10. package/dist/src/config.d.ts +3 -0
  11. package/dist/src/config.js +20 -0
  12. package/dist/src/cron.d.ts +11 -0
  13. package/dist/src/cron.js +65 -0
  14. package/dist/src/daemon.d.ts +36 -0
  15. package/dist/src/daemon.js +373 -0
  16. package/dist/src/engine.d.ts +32 -0
  17. package/dist/src/engine.js +1014 -0
  18. package/dist/src/heartbeat.d.ts +18 -0
  19. package/dist/src/heartbeat.js +28 -0
  20. package/dist/src/host-api.d.ts +40 -0
  21. package/dist/src/host-api.js +59 -0
  22. package/dist/src/host-control.d.ts +48 -0
  23. package/dist/src/host-control.js +1279 -0
  24. package/dist/src/host-export.d.ts +50 -0
  25. package/dist/src/host-export.js +187 -0
  26. package/dist/src/host-feedback.d.ts +78 -0
  27. package/dist/src/host-feedback.js +178 -0
  28. package/dist/src/host-home.d.ts +13 -0
  29. package/dist/src/host-home.js +54 -0
  30. package/dist/src/host-ledger.d.ts +261 -0
  31. package/dist/src/host-ledger.js +554 -0
  32. package/dist/src/host-loop-events.d.ts +69 -0
  33. package/dist/src/host-loop-events.js +288 -0
  34. package/dist/src/host-permission.d.ts +36 -0
  35. package/dist/src/host-permission.js +180 -0
  36. package/dist/src/host-policy.d.ts +15 -0
  37. package/dist/src/host-policy.js +36 -0
  38. package/dist/src/host-run-executor.d.ts +13 -0
  39. package/dist/src/host-run-executor.js +221 -0
  40. package/dist/src/host-run-heartbeat.d.ts +40 -0
  41. package/dist/src/host-run-heartbeat.js +103 -0
  42. package/dist/src/host-run-layout.d.ts +17 -0
  43. package/dist/src/host-run-layout.js +387 -0
  44. package/dist/src/host-run-meta.d.ts +41 -0
  45. package/dist/src/host-run-meta.js +115 -0
  46. package/dist/src/host-run-spec.d.ts +149 -0
  47. package/dist/src/host-run-spec.js +465 -0
  48. package/dist/src/host-runs.d.ts +77 -0
  49. package/dist/src/host-runs.js +195 -0
  50. package/dist/src/host-sdk.d.ts +412 -0
  51. package/dist/src/host-sdk.js +628 -0
  52. package/dist/src/host-server.d.ts +26 -0
  53. package/dist/src/host-server.js +921 -0
  54. package/dist/src/host-timeline.d.ts +24 -0
  55. package/dist/src/host-timeline.js +161 -0
  56. package/dist/src/jsonl.d.ts +13 -0
  57. package/dist/src/jsonl.js +47 -0
  58. package/dist/src/lifecycle.d.ts +5 -0
  59. package/dist/src/lifecycle.js +18 -0
  60. package/dist/src/message-routing.d.ts +32 -0
  61. package/dist/src/message-routing.js +119 -0
  62. package/dist/src/paths.d.ts +19 -0
  63. package/dist/src/paths.js +26 -0
  64. package/dist/src/project-profile.d.ts +49 -0
  65. package/dist/src/project-profile.js +356 -0
  66. package/dist/src/remediation.d.ts +14 -0
  67. package/dist/src/remediation.js +114 -0
  68. package/dist/src/remote-devices.d.ts +41 -0
  69. package/dist/src/remote-devices.js +156 -0
  70. package/dist/src/remote-diagnostics.d.ts +39 -0
  71. package/dist/src/remote-diagnostics.js +199 -0
  72. package/dist/src/remote-ssh.d.ts +39 -0
  73. package/dist/src/remote-ssh.js +129 -0
  74. package/dist/src/run-stream.d.ts +57 -0
  75. package/dist/src/run-stream.js +119 -0
  76. package/dist/src/runner.d.ts +131 -0
  77. package/dist/src/runner.js +1161 -0
  78. package/dist/src/runtime-data.d.ts +68 -0
  79. package/dist/src/runtime-data.js +172 -0
  80. package/dist/src/service.d.ts +114 -0
  81. package/dist/src/service.js +631 -0
  82. package/dist/src/shared-skills.d.ts +26 -0
  83. package/dist/src/shared-skills.js +85 -0
  84. package/dist/src/shim.d.ts +1 -0
  85. package/dist/src/shim.js +64 -0
  86. package/dist/src/skill-check.d.ts +17 -0
  87. package/dist/src/skill-check.js +158 -0
  88. package/dist/src/sse.d.ts +9 -0
  89. package/dist/src/sse.js +36 -0
  90. package/dist/src/team-routing.d.ts +55 -0
  91. package/dist/src/team-routing.js +131 -0
  92. package/dist/src/team-workflow.d.ts +78 -0
  93. package/dist/src/team-workflow.js +253 -0
  94. package/dist/src/text.d.ts +7 -0
  95. package/dist/src/text.js +27 -0
  96. package/dist/src/types.d.ts +98 -0
  97. package/dist/src/types.js +1 -0
  98. package/dist/src/usage.d.ts +116 -0
  99. package/dist/src/usage.js +350 -0
  100. package/dist/src/workspace.d.ts +9 -0
  101. package/dist/src/workspace.js +56 -0
  102. package/dist/src/worktree.d.ts +47 -0
  103. package/dist/src/worktree.js +201 -0
  104. package/package.json +63 -0
@@ -0,0 +1,149 @@
1
+ import type { RuntimeAttachment } from "./attachments.js";
2
+ import type { EngineId } from "./types.js";
3
+ export type HostRunMode = "run" | "takeover";
4
+ export type HostRunLoopMode = "bounded" | "infinite";
5
+ export type HostRunRoleProfile = "small" | "engineering" | "product" | "full";
6
+ export type CodexReasoningEffort = "low" | "medium" | "high" | "xhigh";
7
+ export interface ThreadSyncSpec {
8
+ threadId: string;
9
+ syncUrl?: string;
10
+ syncSecret?: string;
11
+ }
12
+ export interface HostRunOptions {
13
+ loops: number;
14
+ pollIntervalSeconds: number;
15
+ loopMode: HostRunLoopMode;
16
+ engine?: EngineId;
17
+ model?: string;
18
+ fastModel?: string;
19
+ codexReasoningEffort?: CodexReasoningEffort;
20
+ configPath?: string;
21
+ workerUrl?: string;
22
+ workerModel?: string;
23
+ workerKey?: string;
24
+ noBrain: boolean;
25
+ keepArtifacts: boolean;
26
+ outputDir: string;
27
+ errorLimit: number;
28
+ }
29
+ export interface HostProjectRunSpec {
30
+ goal: string;
31
+ mode: HostRunMode;
32
+ projectDir?: string;
33
+ repoSourceDir?: string;
34
+ repoCloneUrl?: string;
35
+ workspaceRoot?: string;
36
+ gitRoot?: string;
37
+ sourceLabel: string;
38
+ bootstrapScript?: string;
39
+ githubToken?: string;
40
+ threadSync?: ThreadSyncSpec;
41
+ roleProfile: HostRunRoleProfile;
42
+ hooks?: unknown;
43
+ attachments: RuntimeAttachment[];
44
+ }
45
+ export type ProjectRunSpec<THooks = unknown> = Omit<Partial<HostProjectRunSpec>, "hooks"> & {
46
+ hooks?: THooks;
47
+ };
48
+ export interface HostRunPlan {
49
+ runId: string;
50
+ spec: HostProjectRunSpec;
51
+ options: HostRunOptions;
52
+ summary: string;
53
+ }
54
+ export interface HostLaunchIssue {
55
+ severity: "info" | "warning" | "error";
56
+ code: string;
57
+ message: string;
58
+ }
59
+ export type HostRunLlmModeLabel = "hybrid-worker" | "codex-cli" | "claude-cli" | "runtime-default";
60
+ export interface HostRunSessionPlan {
61
+ runId: string;
62
+ sourceLabel: string;
63
+ useHybrid: boolean;
64
+ runtimeOverride?: EngineId;
65
+ modelOverride?: string;
66
+ fastModelOverride?: string;
67
+ codexReasoningEffort?: CodexReasoningEffort;
68
+ runtimeLabel: string;
69
+ llmModeLabel: HostRunLlmModeLabel;
70
+ codexConfigLabel: string | null;
71
+ }
72
+ export interface HostRunEnvironmentPlan {
73
+ envFilePath?: string;
74
+ envFileExists: boolean;
75
+ envLoading: "not-loaded";
76
+ note: string;
77
+ }
78
+ export interface HostRunGitPlan {
79
+ gitRoot: string;
80
+ isGitRepo: boolean;
81
+ activeBranch?: string;
82
+ hasUpstream: boolean;
83
+ branchAhead: number;
84
+ branchBehind: number;
85
+ modifiedFiles: string[];
86
+ prUrl?: string;
87
+ }
88
+ export interface HostRunConfigPlan {
89
+ label: string;
90
+ source: "explicit" | "project" | "default";
91
+ path?: string;
92
+ exists: boolean;
93
+ }
94
+ export interface HostRunLocalLayoutPlan {
95
+ baseDir: string;
96
+ configPath: string;
97
+ workspaceRoot: string;
98
+ sharedSkillsDir: string;
99
+ gitRoot: string;
100
+ outputDir: string;
101
+ loopEventsPath: string;
102
+ resultsPath: string;
103
+ heartbeatPath: string;
104
+ metaPath: string;
105
+ collaborationPath: string;
106
+ tasksPath: string;
107
+ capsulesPath: string;
108
+ workflowPath: string;
109
+ feedbackPath: string;
110
+ sourceConfigPath?: string;
111
+ exists: boolean;
112
+ }
113
+ export interface HostLaunchPlan extends HostRunPlan {
114
+ availableEngines: EngineId[];
115
+ effectiveEngine?: EngineId;
116
+ session: HostRunSessionPlan;
117
+ environment: HostRunEnvironmentPlan;
118
+ git: HostRunGitPlan;
119
+ config: HostRunConfigPlan;
120
+ layout: HostRunLocalLayoutPlan;
121
+ ready: boolean;
122
+ issues: HostLaunchIssue[];
123
+ suggestedCommands: string[];
124
+ launchSummary: string;
125
+ }
126
+ export type JsonSafeHostLaunchPlan = Omit<HostLaunchPlan, "options"> & {
127
+ options: Omit<HostRunOptions, "loops"> & {
128
+ loops: number | "infinite";
129
+ };
130
+ };
131
+ export type HostRunSpecInput = Omit<Partial<HostProjectRunSpec>, "attachments"> & {
132
+ goal: string;
133
+ options?: Partial<HostRunOptions>;
134
+ runId?: string;
135
+ attachments?: unknown;
136
+ };
137
+ export declare function createDefaultHostRunOptions(overrides?: Partial<HostRunOptions>): HostRunOptions;
138
+ export declare function createHostRunPlan(input: HostRunSpecInput, env?: NodeJS.ProcessEnv): HostRunPlan;
139
+ export declare function createHostLaunchPlan(input: HostRunSpecInput, env?: NodeJS.ProcessEnv, availableEngines?: EngineId[]): HostLaunchPlan;
140
+ export declare function formatHostRunPlanSummary(plan: Pick<HostRunPlan, "spec" | "options">): string;
141
+ export declare function formatHostLaunchPlanSummary(plan: HostLaunchPlan): string;
142
+ export declare function createHostRunEnvironmentPlan(plan: HostRunPlan): HostRunEnvironmentPlan;
143
+ export declare function createHostRunGitPlan(plan: HostRunPlan): HostRunGitPlan;
144
+ export declare function parseHostGitStatus(raw: string): Omit<HostRunGitPlan, "gitRoot" | "isGitRepo" | "prUrl">;
145
+ export declare function toJsonSafeHostLaunchPlan(plan: HostLaunchPlan): JsonSafeHostLaunchPlan;
146
+ export declare function createHostRunConfigPlan(plan: HostRunPlan): HostRunConfigPlan;
147
+ export declare function createHostRunLocalLayoutPlan(plan: HostRunPlan, config?: HostRunConfigPlan): HostRunLocalLayoutPlan;
148
+ export declare function createHostRunSessionPlan(plan: HostRunPlan, effectiveEngine?: EngineId): HostRunSessionPlan;
149
+ export declare function detectAvailableHostEngines(env?: NodeJS.ProcessEnv): EngineId[];
@@ -0,0 +1,465 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { execFileSync } from "node:child_process";
3
+ import { basename, join, resolve } from "node:path";
4
+ import { hostRunHeartbeatPathForOutputDir } from "./host-run-heartbeat.js";
5
+ import { formatAttachmentPrompt, normalizeRuntimeAttachments, requiredAttachmentsRejected } from "./attachments.js";
6
+ export function createDefaultHostRunOptions(overrides = {}) {
7
+ const loopMode = overrides.loopMode ?? (overrides.loops === Infinity ? "infinite" : "bounded");
8
+ return {
9
+ loops: loopMode === "infinite" ? Infinity : Math.max(1, Math.floor(overrides.loops ?? 100)),
10
+ pollIntervalSeconds: Math.max(1, Math.floor(overrides.pollIntervalSeconds ?? 15)),
11
+ loopMode,
12
+ engine: overrides.engine,
13
+ model: cleanString(overrides.model),
14
+ fastModel: cleanString(overrides.fastModel),
15
+ codexReasoningEffort: normalizeReasoningEffort(overrides.codexReasoningEffort),
16
+ configPath: cleanString(overrides.configPath),
17
+ workerUrl: cleanString(overrides.workerUrl),
18
+ workerModel: cleanString(overrides.workerModel),
19
+ workerKey: cleanString(overrides.workerKey) ?? "lmstudio",
20
+ noBrain: overrides.noBrain ?? false,
21
+ keepArtifacts: overrides.keepArtifacts ?? false,
22
+ outputDir: overrides.outputDir ? resolve(overrides.outputDir) : resolve("deliverables"),
23
+ errorLimit: Math.max(1, Math.floor(overrides.errorLimit ?? 20))
24
+ };
25
+ }
26
+ export function createHostRunPlan(input, env = process.env) {
27
+ const goal = cleanString(input.goal);
28
+ if (!goal)
29
+ throw new Error("goal is required");
30
+ const mode = input.mode ?? "run";
31
+ const projectDir = resolveOptionalDir(input.projectDir, "projectDir");
32
+ const repoSourceDir = resolveOptionalDir(input.repoSourceDir ?? projectDir, "repoSourceDir");
33
+ const workspaceRoot = input.workspaceRoot ? resolve(input.workspaceRoot) : env.KING_AI_AGENT_WORKSPACE_ROOT;
34
+ const gitRoot = input.gitRoot ? resolve(input.gitRoot) : repoSourceDir ?? projectDir;
35
+ const options = createDefaultHostRunOptions(input.options);
36
+ const explicitRunId = cleanString(input.runId);
37
+ const spec = {
38
+ goal,
39
+ mode,
40
+ projectDir,
41
+ repoSourceDir,
42
+ repoCloneUrl: cleanString(input.repoCloneUrl),
43
+ workspaceRoot: workspaceRoot ? resolve(workspaceRoot) : undefined,
44
+ gitRoot,
45
+ sourceLabel: cleanString(input.sourceLabel) || sourceLabel({ projectDir, repoSourceDir, repoCloneUrl: input.repoCloneUrl }),
46
+ bootstrapScript: cleanString(input.bootstrapScript),
47
+ githubToken: cleanString(input.githubToken),
48
+ threadSync: normalizeThreadSync(input.threadSync),
49
+ roleProfile: normalizeRoleProfile(input.roleProfile),
50
+ hooks: input.hooks,
51
+ attachments: normalizeRuntimeAttachments(input.attachments)
52
+ };
53
+ const rejectedRequired = requiredAttachmentsRejected(spec.attachments);
54
+ if (rejectedRequired.length) {
55
+ throw new Error(`required attachment rejected: ${rejectedRequired.map((attachment) => `${attachment.name} (${attachment.rejectionReason ?? attachment.decision})`).join(", ")}`);
56
+ }
57
+ return {
58
+ runId: explicitRunId ? safeFilenameSegment(explicitRunId, "runId") : buildHostRunId(goal),
59
+ spec,
60
+ options,
61
+ summary: formatHostRunPlanSummary({ spec, options })
62
+ };
63
+ }
64
+ export function createHostLaunchPlan(input, env = process.env, availableEngines = detectAvailableHostEngines(env)) {
65
+ const plan = createHostRunPlan(input, env);
66
+ const issues = [];
67
+ const effectiveEngine = plan.options.engine && availableEngines.includes(plan.options.engine)
68
+ ? plan.options.engine
69
+ : availableEngines[0];
70
+ if (availableEngines.length === 0) {
71
+ issues.push({
72
+ severity: "error",
73
+ code: "no-engine",
74
+ message: "No supported local engine is available on PATH. Install and sign in to Claude Code or Codex."
75
+ });
76
+ }
77
+ else if (plan.options.engine && !availableEngines.includes(plan.options.engine)) {
78
+ issues.push({
79
+ severity: "warning",
80
+ code: "engine-unavailable",
81
+ message: `${plan.options.engine} was requested but is not available; ${effectiveEngine ?? "the runtime default"} would be used instead.`
82
+ });
83
+ }
84
+ if (plan.spec.mode === "takeover" && !plan.spec.projectDir && !plan.spec.repoSourceDir && !plan.spec.repoCloneUrl) {
85
+ issues.push({
86
+ severity: "error",
87
+ code: "takeover-source-required",
88
+ message: "Takeover mode requires projectDir, repoSourceDir, or repoCloneUrl."
89
+ });
90
+ }
91
+ if (plan.spec.projectDir && !isGitRepo(plan.spec.projectDir)) {
92
+ issues.push({
93
+ severity: "warning",
94
+ code: "project-not-git",
95
+ message: "The selected project directory is not a git repository; worktree and patch planning will be limited."
96
+ });
97
+ }
98
+ if (!plan.spec.workspaceRoot) {
99
+ issues.push({
100
+ severity: "info",
101
+ code: "default-agent-workspace",
102
+ message: "No explicit workspaceRoot was provided; agents will use their private workspace directories."
103
+ });
104
+ }
105
+ const config = createHostRunConfigPlan(plan);
106
+ const launchPlan = {
107
+ ...plan,
108
+ availableEngines,
109
+ effectiveEngine,
110
+ session: createHostRunSessionPlan(plan, effectiveEngine),
111
+ environment: createHostRunEnvironmentPlan(plan),
112
+ git: createHostRunGitPlan(plan),
113
+ config,
114
+ layout: createHostRunLocalLayoutPlan(plan, config),
115
+ ready: !issues.some((issue) => issue.severity === "error"),
116
+ issues,
117
+ suggestedCommands: suggestedCommands(plan),
118
+ launchSummary: ""
119
+ };
120
+ launchPlan.launchSummary = formatHostLaunchPlanSummary(launchPlan);
121
+ return launchPlan;
122
+ }
123
+ export function formatHostRunPlanSummary(plan) {
124
+ const lines = [
125
+ `host run: ${plan.spec.mode} ${plan.spec.sourceLabel}`,
126
+ `goal: ${plan.spec.goal}`,
127
+ `engine: ${plan.options.engine ?? "runtime default"} model=${plan.options.model ?? "default"} fast=${plan.options.fastModel ?? "default"}`,
128
+ `brain: ${plan.options.noBrain ? "disabled" : "enabled"} worker=${plan.options.workerUrl ? "configured" : "default"} workerModel=${plan.options.workerModel ?? "default"} config=${plan.options.configPath ? "configured" : "default"}`,
129
+ `loops: ${plan.options.loopMode === "infinite" ? "infinite" : plan.options.loops} poll=${plan.options.pollIntervalSeconds}s errors=${plan.options.errorLimit}`,
130
+ `output: ${plan.options.outputDir}`
131
+ ];
132
+ if (plan.spec.projectDir)
133
+ lines.push(`project: ${plan.spec.projectDir}`);
134
+ if (plan.spec.repoSourceDir && plan.spec.repoSourceDir !== plan.spec.projectDir)
135
+ lines.push(`repo source: ${plan.spec.repoSourceDir}`);
136
+ if (plan.spec.workspaceRoot)
137
+ lines.push(`workspace root: ${plan.spec.workspaceRoot}`);
138
+ if (plan.spec.gitRoot)
139
+ lines.push(`git root: ${plan.spec.gitRoot}`);
140
+ if (plan.spec.threadSync)
141
+ lines.push(`thread sync: ${plan.spec.threadSync.threadId}`);
142
+ const attachmentPrompt = formatAttachmentPrompt(plan.spec.attachments);
143
+ if (attachmentPrompt)
144
+ lines.push(attachmentPrompt);
145
+ lines.push(`role profile: ${plan.spec.roleProfile}`);
146
+ return lines.join("\n");
147
+ }
148
+ export function formatHostLaunchPlanSummary(plan) {
149
+ const lines = [
150
+ plan.summary,
151
+ `session: ${plan.session.llmModeLabel} runtime=${plan.session.runtimeLabel} codex=${plan.session.codexConfigLabel ?? "n/a"}`,
152
+ `environment: ${plan.environment.envFileExists ? ".env present" : ".env absent"} loading=${plan.environment.envLoading}`,
153
+ `git: ${formatHostRunGitPlan(plan.git)}`,
154
+ `config: ${plan.config.label} source=${plan.config.source} exists=${plan.config.exists ? "yes" : "no"}`,
155
+ `layout: ${plan.layout.baseDir} exists=${plan.layout.exists ? "yes" : "no"}`,
156
+ `workspace: ${plan.layout.workspaceRoot}`,
157
+ `ready: ${plan.ready ? "yes" : "no"}`,
158
+ `available engines: ${plan.availableEngines.join(", ") || "(none)"}`,
159
+ `effective engine: ${plan.effectiveEngine ?? "(none)"}`
160
+ ];
161
+ if (plan.issues.length) {
162
+ lines.push("preflight:");
163
+ for (const issue of plan.issues)
164
+ lines.push(` - ${issue.severity}/${issue.code}: ${issue.message}`);
165
+ }
166
+ if (plan.suggestedCommands.length) {
167
+ lines.push("suggested commands:");
168
+ for (const command of plan.suggestedCommands)
169
+ lines.push(` ${command}`);
170
+ }
171
+ return lines.join("\n");
172
+ }
173
+ export function createHostRunEnvironmentPlan(plan) {
174
+ const envFilePath = plan.spec.projectDir ? join(plan.spec.projectDir, ".env") : undefined;
175
+ return {
176
+ envFilePath,
177
+ envFileExists: !!envFilePath && existsSync(envFilePath),
178
+ envLoading: "not-loaded",
179
+ note: "Project .env files are detected for app visibility but are not loaded by host preflight or layout preparation."
180
+ };
181
+ }
182
+ export function createHostRunGitPlan(plan) {
183
+ const gitRoot = plan.spec.gitRoot ?? plan.spec.projectDir ?? process.cwd();
184
+ if (!isGitRepo(gitRoot))
185
+ return emptyHostRunGitPlan(gitRoot, false);
186
+ const parsed = readHostGitStatus(gitRoot);
187
+ return {
188
+ gitRoot,
189
+ isGitRepo: true,
190
+ ...parsed,
191
+ prUrl: detectPullRequestUrl(gitRoot, parsed.activeBranch)
192
+ };
193
+ }
194
+ export function parseHostGitStatus(raw) {
195
+ const state = {
196
+ activeBranch: undefined,
197
+ hasUpstream: false,
198
+ branchAhead: 0,
199
+ branchBehind: 0,
200
+ modifiedFiles: []
201
+ };
202
+ const seen = new Set();
203
+ for (const line of raw.split(/\r?\n/)) {
204
+ if (!line)
205
+ continue;
206
+ if (line.startsWith("# branch.head ")) {
207
+ const branch = line.slice("# branch.head ".length).trim();
208
+ state.activeBranch = branch && branch !== "(detached)" ? branch : undefined;
209
+ continue;
210
+ }
211
+ if (line.startsWith("# branch.upstream ")) {
212
+ state.hasUpstream = true;
213
+ continue;
214
+ }
215
+ if (line.startsWith("# branch.ab ")) {
216
+ const match = line.match(/^# branch\.ab \+(\d+) -(\d+)$/);
217
+ if (match) {
218
+ state.branchAhead = Number.parseInt(match[1] ?? "0", 10);
219
+ state.branchBehind = Number.parseInt(match[2] ?? "0", 10);
220
+ }
221
+ continue;
222
+ }
223
+ const file = parseHostGitStatusPath(line);
224
+ if (file && !seen.has(file)) {
225
+ seen.add(file);
226
+ state.modifiedFiles.push(file);
227
+ }
228
+ }
229
+ return state;
230
+ }
231
+ function readHostGitStatus(gitRoot) {
232
+ try {
233
+ return parseHostGitStatus(execFileSync("git", ["status", "--porcelain=v2", "--branch", "--untracked-files=all"], {
234
+ cwd: gitRoot,
235
+ encoding: "utf8",
236
+ stdio: ["ignore", "pipe", "pipe"],
237
+ timeout: 5000
238
+ }));
239
+ }
240
+ catch {
241
+ return emptyHostRunGitPlan(gitRoot, true);
242
+ }
243
+ }
244
+ function parseHostGitStatusPath(line) {
245
+ if (line.startsWith("? ") || line.startsWith("! "))
246
+ return line.slice(2).trim() || undefined;
247
+ const ordinary = line.match(/^1 [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ (.+)$/);
248
+ if (ordinary)
249
+ return ordinary[1]?.trim() || undefined;
250
+ const renamed = line.match(/^2 [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ (.+)$/);
251
+ if (renamed)
252
+ return renamed[1]?.split("\t")[0]?.trim() || undefined;
253
+ const unmerged = line.match(/^u [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ [^ ]+ (.+)$/);
254
+ if (unmerged)
255
+ return unmerged[1]?.trim() || undefined;
256
+ return undefined;
257
+ }
258
+ function detectPullRequestUrl(gitRoot, branch) {
259
+ if (!branch)
260
+ return undefined;
261
+ try {
262
+ const raw = execFileSync("gh", ["pr", "list", "--head", branch, "--json", "url", "--limit", "1"], {
263
+ cwd: gitRoot,
264
+ encoding: "utf8",
265
+ stdio: ["ignore", "pipe", "pipe"],
266
+ timeout: 5000
267
+ }).trim();
268
+ if (!raw)
269
+ return undefined;
270
+ const parsed = JSON.parse(raw);
271
+ return typeof parsed[0]?.url === "string" ? parsed[0].url : undefined;
272
+ }
273
+ catch {
274
+ return undefined;
275
+ }
276
+ }
277
+ function emptyHostRunGitPlan(gitRoot, isGitRepo) {
278
+ return {
279
+ gitRoot,
280
+ isGitRepo,
281
+ hasUpstream: false,
282
+ branchAhead: 0,
283
+ branchBehind: 0,
284
+ modifiedFiles: []
285
+ };
286
+ }
287
+ function formatHostRunGitPlan(plan) {
288
+ if (!plan.isGitRepo)
289
+ return `${plan.gitRoot} not-git`;
290
+ const branch = plan.activeBranch ?? "detached";
291
+ const upstream = plan.hasUpstream ? "upstream=yes" : "upstream=no";
292
+ const pr = plan.prUrl ? ` pr=${plan.prUrl}` : "";
293
+ return `${branch} ahead=${plan.branchAhead} behind=${plan.branchBehind} changed=${plan.modifiedFiles.length} ${upstream}${pr}`;
294
+ }
295
+ export function toJsonSafeHostLaunchPlan(plan) {
296
+ return {
297
+ ...plan,
298
+ options: {
299
+ ...plan.options,
300
+ loops: plan.options.loopMode === "infinite" ? "infinite" : plan.options.loops
301
+ }
302
+ };
303
+ }
304
+ export function createHostRunConfigPlan(plan) {
305
+ if (plan.options.configPath) {
306
+ const path = resolve(plan.options.configPath);
307
+ return {
308
+ label: basename(path),
309
+ source: "explicit",
310
+ path,
311
+ exists: existsSync(path)
312
+ };
313
+ }
314
+ if (plan.spec.projectDir) {
315
+ const path = join(plan.spec.projectDir, "agents.json");
316
+ return {
317
+ label: "agents.json (project)",
318
+ source: "project",
319
+ path,
320
+ exists: existsSync(path)
321
+ };
322
+ }
323
+ return {
324
+ label: "built-in-local-agents.json",
325
+ source: "default",
326
+ exists: true
327
+ };
328
+ }
329
+ export function createHostRunLocalLayoutPlan(plan, config = createHostRunConfigPlan(plan)) {
330
+ const outputDir = resolve(plan.options.outputDir);
331
+ const baseDir = join(outputDir, ".king-ai-local", plan.runId);
332
+ return {
333
+ baseDir,
334
+ configPath: join(baseDir, "agents.json"),
335
+ workspaceRoot: plan.spec.workspaceRoot ?? join(baseDir, "agents"),
336
+ sharedSkillsDir: join(baseDir, "shared-skills"),
337
+ gitRoot: plan.spec.gitRoot ?? plan.spec.projectDir ?? process.cwd(),
338
+ outputDir,
339
+ loopEventsPath: join(outputDir, "loop-events.ndjson"),
340
+ resultsPath: join(outputDir, "results.tsv"),
341
+ heartbeatPath: hostRunHeartbeatPathForOutputDir(outputDir),
342
+ metaPath: join(outputDir, "meta.json"),
343
+ collaborationPath: join(baseDir, "collaboration.json"),
344
+ tasksPath: join(outputDir, "tasks.jsonl"),
345
+ capsulesPath: join(outputDir, "capsules.jsonl"),
346
+ workflowPath: join(outputDir, "workflow.jsonl"),
347
+ feedbackPath: join(outputDir, "run-feedback.jsonl"),
348
+ sourceConfigPath: config.path,
349
+ exists: existsSync(baseDir)
350
+ };
351
+ }
352
+ export function createHostRunSessionPlan(plan, effectiveEngine) {
353
+ const runtimeOverride = plan.options.engine;
354
+ const runtimeLabel = runtimeOverride ?? effectiveEngine ?? "runtime default";
355
+ const useHybrid = !!plan.options.workerUrl;
356
+ const llmModeLabel = useHybrid
357
+ ? "hybrid-worker"
358
+ : runtimeLabel === "codex"
359
+ ? "codex-cli"
360
+ : runtimeLabel === "claude"
361
+ ? "claude-cli"
362
+ : "runtime-default";
363
+ const shouldShowCodexConfig = runtimeLabel === "codex" || runtimeOverride === "codex";
364
+ return {
365
+ runId: plan.runId,
366
+ sourceLabel: plan.spec.sourceLabel,
367
+ useHybrid,
368
+ runtimeOverride,
369
+ modelOverride: plan.options.model,
370
+ fastModelOverride: plan.options.fastModel,
371
+ codexReasoningEffort: plan.options.codexReasoningEffort,
372
+ runtimeLabel,
373
+ llmModeLabel,
374
+ codexConfigLabel: shouldShowCodexConfig
375
+ ? `${plan.options.model ?? "default"} / ${plan.options.codexReasoningEffort ?? "default"}`
376
+ : null
377
+ };
378
+ }
379
+ function cleanString(value) {
380
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
381
+ }
382
+ function normalizeRoleProfile(value) {
383
+ if (value === "small" || value === "engineering" || value === "product" || value === "full")
384
+ return value;
385
+ if (value === undefined || value === null || value === "")
386
+ return "full";
387
+ throw new Error(`invalid role profile: ${String(value)}`);
388
+ }
389
+ function safeFilenameSegment(value, label) {
390
+ if (value === "." || value === ".." || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value)) {
391
+ throw new Error(`${label} must be a safe filename segment`);
392
+ }
393
+ return value;
394
+ }
395
+ function normalizeReasoningEffort(value) {
396
+ return value === "low" || value === "medium" || value === "high" || value === "xhigh" ? value : undefined;
397
+ }
398
+ function normalizeThreadSync(value) {
399
+ if (!value?.threadId?.trim())
400
+ return undefined;
401
+ return {
402
+ threadId: value.threadId.trim(),
403
+ syncUrl: cleanString(value.syncUrl),
404
+ syncSecret: cleanString(value.syncSecret)
405
+ };
406
+ }
407
+ function resolveOptionalDir(value, label) {
408
+ const cleaned = cleanString(value);
409
+ if (!cleaned)
410
+ return undefined;
411
+ const resolved = resolve(cleaned);
412
+ if (!existsSync(resolved))
413
+ throw new Error(`${label} does not exist: ${resolved}`);
414
+ if (!statSync(resolved).isDirectory())
415
+ throw new Error(`${label} is not a directory: ${resolved}`);
416
+ return resolved;
417
+ }
418
+ export function detectAvailableHostEngines(env = process.env) {
419
+ const engines = [];
420
+ if (commandExists("claude", env))
421
+ engines.push("claude");
422
+ if (commandExists("codex", env))
423
+ engines.push("codex");
424
+ return engines;
425
+ }
426
+ function commandExists(command, env) {
427
+ try {
428
+ execFileSync(process.platform === "win32" ? "where" : "which", [command], {
429
+ env,
430
+ stdio: "ignore",
431
+ timeout: 3000
432
+ });
433
+ return true;
434
+ }
435
+ catch {
436
+ return false;
437
+ }
438
+ }
439
+ function isGitRepo(dir) {
440
+ return existsSync(resolve(dir, ".git"));
441
+ }
442
+ function suggestedCommands(plan) {
443
+ const commands = ["king-ai agent computer --doctor"];
444
+ if (plan.spec.projectDir)
445
+ commands.push(`king-ai project-profile ${shellQuote(plan.spec.projectDir)}`);
446
+ commands.push("king-ai host status --json");
447
+ return commands;
448
+ }
449
+ function shellQuote(value) {
450
+ return /^[A-Za-z0-9_./:-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
451
+ }
452
+ function sourceLabel(input) {
453
+ if (input.projectDir)
454
+ return `project:${input.projectDir.split(/[\\/]/).pop() ?? "local"}`;
455
+ if (input.repoSourceDir)
456
+ return `repo:${input.repoSourceDir.split(/[\\/]/).pop() ?? "local"}`;
457
+ if (input.repoCloneUrl)
458
+ return "remote-repo";
459
+ return "ad-hoc";
460
+ }
461
+ function buildHostRunId(goal) {
462
+ const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
463
+ const slug = goal.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 24).toLowerCase() || "goal";
464
+ return `host-run-${ts}-${slug}`;
465
+ }
@@ -0,0 +1,77 @@
1
+ import type { HostRunSpecInput, JsonSafeHostLaunchPlan } from "./host-run-spec.js";
2
+ export type HostRunRequestStatus = "pending" | "running" | "completed" | "failed" | "cancelled";
3
+ export interface HostRunExecutorSpec {
4
+ kind: "host-command";
5
+ command: string;
6
+ input?: unknown;
7
+ format?: "text" | "json";
8
+ actorRole?: string;
9
+ trusted?: boolean;
10
+ }
11
+ export interface HostRunExecutionResult {
12
+ command: string;
13
+ ok: boolean;
14
+ exitCode: number;
15
+ textPreview?: string;
16
+ error?: string;
17
+ }
18
+ export interface HostRunRequestUpdate {
19
+ id: string;
20
+ status: Exclude<HostRunRequestStatus, "pending">;
21
+ updatedAt: string;
22
+ detail?: string;
23
+ result?: HostRunExecutionResult;
24
+ }
25
+ export interface HostRunRequest {
26
+ id: string;
27
+ status: HostRunRequestStatus;
28
+ createdAt: string;
29
+ updatedAt?: string;
30
+ spec: HostRunSpecInput;
31
+ ready: boolean;
32
+ effectiveEngine?: "claude" | "codex";
33
+ summary: string;
34
+ detail?: string;
35
+ executor?: HostRunExecutorSpec;
36
+ result?: HostRunExecutionResult;
37
+ }
38
+ export interface HostRunSubmitInput extends HostRunSpecInput {
39
+ requestId?: string;
40
+ executor?: HostRunExecutorSpec;
41
+ }
42
+ export interface HostRunListInput {
43
+ limit?: number;
44
+ status?: HostRunRequestStatus;
45
+ }
46
+ export interface HostRunGetInput {
47
+ id: string;
48
+ }
49
+ export interface HostRunUpdateInput {
50
+ id: string;
51
+ status: HostRunRequestUpdate["status"];
52
+ detail?: string;
53
+ result?: HostRunExecutionResult;
54
+ }
55
+ export interface HostRunSubmitResult {
56
+ request: HostRunRequest;
57
+ launchPlan: JsonSafeHostLaunchPlan;
58
+ summary: string;
59
+ }
60
+ export interface HostRunUpdateResult {
61
+ request: HostRunRequest;
62
+ summary: string;
63
+ }
64
+ export declare function submitHostRunRequest(input: HostRunSubmitInput, options?: {
65
+ path?: string;
66
+ env?: NodeJS.ProcessEnv;
67
+ availableEngines?: Array<"claude" | "codex">;
68
+ now?: () => Date;
69
+ }): Promise<HostRunSubmitResult>;
70
+ export declare function listHostRunRequests(input?: HostRunListInput, path?: string): Promise<HostRunRequest[]>;
71
+ export declare function getHostRunRequest(input: HostRunGetInput, path?: string): Promise<HostRunRequest | null>;
72
+ export declare function updateHostRunRequest(input: HostRunUpdateInput, options?: {
73
+ path?: string;
74
+ now?: () => Date;
75
+ }): Promise<HostRunUpdateResult>;
76
+ export declare function formatHostRunRequests(requests: HostRunRequest[]): string;
77
+ export declare function formatHostRunRequestSummary(request: HostRunRequest): string;