ainecto 0.13.1 → 0.14.0-next.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.
@@ -25,9 +25,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  mod
26
26
  ));
27
27
 
28
- // node_modules/@xterm/addon-serialize/lib/addon-serialize.js
28
+ // ../ainecto-cli/node_modules/@xterm/addon-serialize/lib/addon-serialize.js
29
29
  var require_addon_serialize = __commonJS({
30
- "node_modules/@xterm/addon-serialize/lib/addon-serialize.js"(exports, module) {
30
+ "../ainecto-cli/node_modules/@xterm/addon-serialize/lib/addon-serialize.js"(exports, module) {
31
31
  "use strict";
32
32
  !(function(t, e) {
33
33
  "object" == typeof exports && "object" == typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define([], e) : "object" == typeof exports ? exports.SerializeAddon = e() : t.SerializeAddon = e();
@@ -391,9 +391,9 @@ var require_addon_serialize = __commonJS({
391
391
  }
392
392
  });
393
393
 
394
- // node_modules/@xterm/headless/lib-headless/xterm-headless.js
394
+ // ../ainecto-cli/node_modules/@xterm/headless/lib-headless/xterm-headless.js
395
395
  var require_xterm_headless = __commonJS({
396
- "node_modules/@xterm/headless/lib-headless/xterm-headless.js"(exports) {
396
+ "../ainecto-cli/node_modules/@xterm/headless/lib-headless/xterm-headless.js"(exports) {
397
397
  "use strict";
398
398
  (() => {
399
399
  "use strict";
@@ -5585,8 +5585,8 @@ function assertLoopbackRedirectUrl(value) {
5585
5585
  }
5586
5586
  throw new Error("OAuth redirect URI must be an http localhost loopback URL.");
5587
5587
  }
5588
- function isLoopbackHostname(hostname) {
5589
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5588
+ function isLoopbackHostname(hostname2) {
5589
+ return hostname2 === "localhost" || hostname2 === "127.0.0.1" || hostname2 === "::1" || hostname2 === "[::1]";
5590
5590
  }
5591
5591
  function assertEnv(value) {
5592
5592
  if (value === void 0) {
@@ -5678,7 +5678,7 @@ function legacyRoleInput(flag, envVars = process.env) {
5678
5678
 
5679
5679
  // src/core/version.ts
5680
5680
  var UNBUNDLED_VERSION = "dev";
5681
- var CLI_VERSION = true ? "0.13.1" : UNBUNDLED_VERSION;
5681
+ var CLI_VERSION = true ? "0.14.0-next.1" : UNBUNDLED_VERSION;
5682
5682
  var PACKAGE_NAME = "@ainecto/mcp";
5683
5683
  var CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;
5684
5684
  function bridgePackageSpec(version = CLI_VERSION) {
@@ -12562,8 +12562,735 @@ function restoreSummary(data) {
12562
12562
  return parts.length ? parts.join("\n") : "Nothing to restore.";
12563
12563
  }
12564
12564
 
12565
- // src/adapters/cli/ainectoCli.ts
12565
+ // src/adapters/cli/agentCommand.ts
12566
+ import { spawnSync } from "child_process";
12567
+ import { hostname } from "os";
12568
+
12569
+ // src/core/agent/claudeArgs.ts
12570
+ function agentReplySchema(textMaxCharacters) {
12571
+ return {
12572
+ type: "object",
12573
+ additionalProperties: false,
12574
+ required: ["reply"],
12575
+ properties: { reply: { type: "string", minLength: 1, maxLength: textMaxCharacters } }
12576
+ };
12577
+ }
12578
+ function agentMcpConfig(nodePath, cliScript, endpoint) {
12579
+ return { mcpServers: { ainecto: { command: nodePath, args: [cliScript, "--endpoint", endpoint, "mcp"] } } };
12580
+ }
12581
+ function claudeAgentArgs(input) {
12582
+ const args = [
12583
+ "-p",
12584
+ "--input-format",
12585
+ "stream-json",
12586
+ "--output-format",
12587
+ "stream-json",
12588
+ "--verbose",
12589
+ "--json-schema",
12590
+ JSON.stringify(input.schema),
12591
+ "--tools",
12592
+ "",
12593
+ // 내장 도구(Bash·Read·Write·Edit·Agent·Skill·WebFetch…) 전부 끔
12594
+ "--strict-mcp-config",
12595
+ "--mcp-config",
12596
+ JSON.stringify(input.mcpConfig),
12597
+ "--allowedTools",
12598
+ "mcp__ainecto",
12599
+ "--permission-mode",
12600
+ "dontAsk",
12601
+ // 허용 밖은 묻지 않고 바로 거절(헤드리스에서 묻기 = 2분 대기)
12602
+ "--setting-sources",
12603
+ ""
12604
+ // 사용자·프로젝트 설정(플러그인·스킬·훅·권한) 끔
12605
+ ];
12606
+ if (input.resumeSessionId) {
12607
+ args.push("--resume", input.resumeSessionId);
12608
+ }
12609
+ return args;
12610
+ }
12611
+ function claudeAgentEnv(env) {
12612
+ const out = { ...env };
12613
+ delete out.AINECTO_TOKEN;
12614
+ return out;
12615
+ }
12616
+ var AINECTO_TOOL_PREFIX = "mcp__ainecto__";
12617
+
12618
+ // src/core/agent/claudeProcess.ts
12619
+ import { spawn as spawn4 } from "child_process";
12620
+ import { createInterface as createInterface3 } from "readline";
12621
+
12622
+ // src/core/agent/documents.ts
12623
+ var DOCUMENT_LINE = /📄[^\n]*?\/app\/docs\/([0-9a-f]{32})(?![0-9a-f])/gu;
12624
+ function documentUuidsIn(content) {
12625
+ const out = [];
12626
+ for (const text of texts(content)) {
12627
+ for (const match of text.matchAll(DOCUMENT_LINE)) {
12628
+ const uuid = match[1];
12629
+ if (uuid && !out.includes(uuid)) out.push(uuid);
12630
+ }
12631
+ }
12632
+ return out;
12633
+ }
12634
+ function texts(content) {
12635
+ if (typeof content === "string") return [content];
12636
+ if (!Array.isArray(content)) return [];
12637
+ return content.flatMap((part) => part && typeof part === "object" && typeof part.text === "string" ? [part.text] : []);
12638
+ }
12639
+ function writeToolNames(tools) {
12640
+ return new Set(tools.filter((tool) => tool.annotations?.readOnlyHint !== true).map((tool) => tool.name));
12641
+ }
12642
+
12643
+ // src/core/agent/limits.ts
12644
+ var AGENT_CALL_TIMEOUT_MS = 1e4;
12645
+ var AGENT_RETRY_MIN_MS = 500;
12646
+ var AGENT_RETRY_MAX_MS = 1e4;
12647
+ var AGENT_TURN_TIMEOUT_MS = 15 * 6e4;
12648
+ var AGENT_STOP_WAIT_MS = 5e3;
12649
+ function nextRetryDelay(previousMs) {
12650
+ if (previousMs === void 0) return AGENT_RETRY_MIN_MS;
12651
+ return Math.min(previousMs * 2, AGENT_RETRY_MAX_MS);
12652
+ }
12653
+
12654
+ // src/core/agent/claudeProcess.ts
12655
+ var spawnClaude = (args, options) => spawn4("claude", args, { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "pipe"] });
12656
+ var ClaudeAgentProcess = class {
12657
+ constructor(conversationUuid, resumed, args, options) {
12658
+ this.conversationUuid = conversationUuid;
12659
+ this.resumed = resumed;
12660
+ this.sessionId = void 0;
12661
+ this.child = options.spawnImpl(args, { cwd: options.cwd, env: options.env });
12662
+ const pid = this.child.pid ?? -1;
12663
+ options.log.write({ event: "process_start", pid, conversationUuid, resume: resumed });
12664
+ this.child.stderr.setEncoding("utf8");
12665
+ this.child.stderr.on("data", (chunk) => {
12666
+ this.stderrTail = (this.stderrTail + chunk).slice(-2e3);
12667
+ });
12668
+ this.exited = new Promise((resolve4) => {
12669
+ const done = (code, signal) => {
12670
+ if (this.exitInfo) return;
12671
+ this.exitInfo = { code, signal };
12672
+ options.log.write({ event: "process_exit", pid, conversationUuid, code, signal });
12673
+ this.finish({ kind: "exited", code, signal, stderrTail: this.stderrTail.trim().slice(-500) });
12674
+ resolve4();
12675
+ };
12676
+ this.child.on("exit", done);
12677
+ this.child.on("error", (error) => {
12678
+ this.stderrTail += `
12679
+ ${error.message}`;
12680
+ done(null, null);
12681
+ });
12682
+ });
12683
+ this.child.stdin.on("error", () => {
12684
+ });
12685
+ createInterface3({ input: this.child.stdout }).on("line", (line) => {
12686
+ let event;
12687
+ try {
12688
+ event = JSON.parse(line);
12689
+ } catch {
12690
+ options.log.write({ event: "bad_line", pid, length: line.length });
12691
+ return;
12692
+ }
12693
+ this.onEvent(event, options.writeTools);
12694
+ });
12695
+ }
12696
+ conversationUuid;
12697
+ resumed;
12698
+ sessionId;
12699
+ child;
12700
+ toolNames = /* @__PURE__ */ new Map();
12701
+ pending;
12702
+ stderrTail = "";
12703
+ exitInfo;
12704
+ exited;
12705
+ get pid() {
12706
+ return this.child.pid ?? -1;
12707
+ }
12708
+ get alive() {
12709
+ return this.exitInfo === void 0;
12710
+ }
12711
+ /** 한 차례 — 기한 안에 result 가 오지 않으면 «timeout»(프로세스는 부른 쪽이 내린다). */
12712
+ async runTurn(text, timeoutMs) {
12713
+ if (!this.alive) {
12714
+ return { kind: "exited", code: this.exitInfo?.code ?? null, signal: this.exitInfo?.signal ?? null, stderrTail: this.stderrTail.trim().slice(-500) };
12715
+ }
12716
+ const outcome = new Promise((resolve4) => {
12717
+ this.pending = { resolve: resolve4, documents: [] };
12718
+ });
12719
+ this.child.stdin.write(`${JSON.stringify({ type: "user", message: { role: "user", content: text } })}
12720
+ `);
12721
+ let timer;
12722
+ const timeout = new Promise((resolve4) => {
12723
+ timer = setTimeout(() => resolve4({ kind: "timeout" }), timeoutMs);
12724
+ });
12725
+ try {
12726
+ return await Promise.race([outcome, timeout]);
12727
+ } finally {
12728
+ clearTimeout(timer);
12729
+ this.pending = void 0;
12730
+ }
12731
+ }
12732
+ /** stdin 닫기 → B6 → SIGTERM → B6 → SIGKILL. */
12733
+ async stop() {
12734
+ if (!this.alive) return;
12735
+ this.child.stdin.end();
12736
+ if (await this.waitExit(AGENT_STOP_WAIT_MS)) return;
12737
+ this.child.kill("SIGTERM");
12738
+ if (await this.waitExit(AGENT_STOP_WAIT_MS)) return;
12739
+ this.child.kill("SIGKILL");
12740
+ await this.waitExit(AGENT_STOP_WAIT_MS);
12741
+ }
12742
+ async waitExit(ms) {
12743
+ let timer;
12744
+ const timedOut = new Promise((resolve4) => {
12745
+ timer = setTimeout(() => resolve4(false), ms);
12746
+ });
12747
+ try {
12748
+ return await Promise.race([this.exited.then(() => true), timedOut]);
12749
+ } finally {
12750
+ clearTimeout(timer);
12751
+ }
12752
+ }
12753
+ finish(outcome) {
12754
+ const pending = this.pending;
12755
+ this.pending = void 0;
12756
+ pending?.resolve(outcome);
12757
+ }
12758
+ onEvent(event, writeTools) {
12759
+ if (event.type === "system" && event.subtype === "init" && typeof event.session_id === "string") {
12760
+ this.sessionId = event.session_id;
12761
+ return;
12762
+ }
12763
+ const content = event.message?.content;
12764
+ if (event.type === "assistant" && Array.isArray(content)) {
12765
+ for (const part of content) {
12766
+ if (part.type === "tool_use" && typeof part.id === "string" && typeof part.name === "string") {
12767
+ this.toolNames.set(part.id, part.name);
12768
+ }
12769
+ }
12770
+ return;
12771
+ }
12772
+ if (event.type === "user" && Array.isArray(content) && this.pending) {
12773
+ for (const part of content) {
12774
+ if (part.type !== "tool_result" || typeof part.tool_use_id !== "string") continue;
12775
+ const name = this.toolNames.get(part.tool_use_id);
12776
+ if (!name?.startsWith(AINECTO_TOOL_PREFIX) || !writeTools.has(name.slice(AINECTO_TOOL_PREFIX.length))) continue;
12777
+ for (const uuid of documentUuidsIn(part.content)) {
12778
+ if (!this.pending.documents.includes(uuid)) this.pending.documents.push(uuid);
12779
+ }
12780
+ }
12781
+ return;
12782
+ }
12783
+ if (event.type === "result") {
12784
+ if (typeof event.session_id === "string") this.sessionId = event.session_id;
12785
+ const documents = this.pending?.documents ?? [];
12786
+ this.finish({
12787
+ kind: "result",
12788
+ documents,
12789
+ result: {
12790
+ subtype: typeof event.subtype === "string" ? event.subtype : void 0,
12791
+ isError: event.is_error === true,
12792
+ structuredOutput: event.structured_output,
12793
+ resultText: typeof event.result === "string" ? event.result : void 0,
12794
+ sessionId: this.sessionId,
12795
+ numTurns: typeof event.num_turns === "number" ? event.num_turns : void 0,
12796
+ costUsd: typeof event.total_cost_usd === "number" ? event.total_cost_usd : void 0
12797
+ }
12798
+ });
12799
+ }
12800
+ }
12801
+ };
12802
+
12803
+ // src/core/agent/http.ts
12804
+ import { randomUUID as randomUUID2 } from "crypto";
12805
+ var AgentCallError = class extends Error {
12806
+ constructor(message, op, status, code) {
12807
+ super(message);
12808
+ this.op = op;
12809
+ this.status = status;
12810
+ this.code = code;
12811
+ this.name = "AgentCallError";
12812
+ }
12813
+ op;
12814
+ status;
12815
+ code;
12816
+ /** 다시 해 볼 만한가 — 네트워크·기한·5xx·429. */
12817
+ get retryable() {
12818
+ return this.status === void 0 || this.status >= 500 || this.status === 429;
12819
+ }
12820
+ };
12821
+ function createAgentHttp(input) {
12822
+ const fetchImpl = input.fetchImpl ?? fetch;
12823
+ const base = `${input.endpoint.replace(/\/+$/, "")}/agent/pc/`;
12824
+ let linkToken;
12825
+ async function send(op, payload, token, options) {
12826
+ const controller = new AbortController();
12827
+ const timer = setTimeout(
12828
+ () => controller.abort(new DOMException("timed out", "TimeoutError")),
12829
+ (options.holdMs ?? 0) + AGENT_CALL_TIMEOUT_MS
12830
+ );
12831
+ const onStop = () => controller.abort(new DOMException("stopped", "AbortError"));
12832
+ options.signal?.addEventListener("abort", onStop, { once: true });
12833
+ const headers = { "Content-Type": "application/json", Accept: "application/json" };
12834
+ if (token) headers.Authorization = `Bearer ${token}`;
12835
+ if (linkToken && op !== "open") headers["X-Link-Token"] = linkToken;
12836
+ try {
12837
+ const response = await fetchImpl(base + op, { method: "POST", headers, body: payload, signal: controller.signal });
12838
+ await response.clone().arrayBuffer();
12839
+ return response;
12840
+ } catch (error) {
12841
+ throw controller.signal.aborted && controller.signal.reason instanceof Error ? controller.signal.reason : error;
12842
+ } finally {
12843
+ clearTimeout(timer);
12844
+ options.signal?.removeEventListener("abort", onStop);
12845
+ }
12846
+ }
12847
+ return {
12848
+ useLinkToken(token) {
12849
+ linkToken = token;
12850
+ },
12851
+ async call(op, body, options = {}) {
12852
+ const callId = randomUUID2();
12853
+ const payload = JSON.stringify(body);
12854
+ const started = Date.now();
12855
+ input.log.write({ event: "agent_call", callId, op, turnUuid: options.turnUuid, bytes: Buffer.byteLength(payload, "utf8") });
12856
+ let outcome = {
12857
+ ok: false,
12858
+ error: "no response"
12859
+ };
12860
+ try {
12861
+ let response = await send(op, payload, await input.tokenProvider.getAccessToken(), options);
12862
+ if (response.status === 401) {
12863
+ response = await send(op, payload, await input.tokenProvider.refreshAfterUnauthorized(), options);
12864
+ }
12865
+ const text = await response.text();
12866
+ const parsed = parseJson2(text);
12867
+ if (!response.ok) {
12868
+ const code = typeof parsed?.code === "string" ? parsed.code : void 0;
12869
+ outcome = { ok: false, status: response.status, code };
12870
+ throw new AgentCallError(
12871
+ typeof parsed?.detail === "string" ? parsed.detail : `HTTP ${response.status}`,
12872
+ op,
12873
+ response.status,
12874
+ code
12875
+ );
12876
+ }
12877
+ const offers = Array.isArray(parsed?.offers) ? parsed.offers.length : void 0;
12878
+ outcome = { ok: true, status: response.status, offers };
12879
+ return parsed ?? {};
12880
+ } catch (error) {
12881
+ if (error instanceof AgentCallError) throw error;
12882
+ const timedOut = error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
12883
+ outcome = { ok: false, error: timedOut ? error.name : error instanceof Error ? error.message : String(error) };
12884
+ throw new AgentCallError(timedOut ? `${op} timed out` : `${op} failed: ${outcome.error}`, op);
12885
+ } finally {
12886
+ input.log.write({ event: "agent_call_response", callId, op, ...outcome, ms: Date.now() - started });
12887
+ }
12888
+ }
12889
+ };
12890
+ }
12891
+ function parseJson2(text) {
12892
+ if (!text) return void 0;
12893
+ try {
12894
+ const value = JSON.parse(text);
12895
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
12896
+ } catch {
12897
+ return void 0;
12898
+ }
12899
+ }
12900
+
12901
+ // src/core/agent/log.ts
12902
+ function openAgentLog(paths) {
12903
+ const log = new DaemonLog(paths.log, paths.previousLog);
12904
+ log.enableRotation();
12905
+ return log;
12906
+ }
12907
+
12908
+ // src/core/agent/paths.ts
12566
12909
  import { homedir as homedir3 } from "os";
12910
+ import { join as join7 } from "path";
12911
+ function agentPaths(home = homedir3()) {
12912
+ const dir = join7(home, ".ainecto", "agent");
12913
+ return {
12914
+ dir,
12915
+ work: join7(dir, "work"),
12916
+ sessions: join7(dir, "sessions.json"),
12917
+ log: join7(dir, "agent.log"),
12918
+ previousLog: join7(dir, "agent.log.1")
12919
+ };
12920
+ }
12921
+
12922
+ // src/core/agent/prompt.ts
12923
+ function agentPromptText(text, context) {
12924
+ const parts = [];
12925
+ const p = context?.project;
12926
+ if (p && typeof p.uuid === "string") {
12927
+ parts.push(`project \xAB${str(p.name)}\xBB (projectUuid ${p.uuid}${typeof p.workspaceUuid === "string" ? `, workspace \xAB${str(p.workspaceName)}\xBB ${p.workspaceUuid}` : ""})`);
12928
+ }
12929
+ const d = context?.openDocument;
12930
+ if (d && typeof d.uuid === "string") {
12931
+ parts.push(`open document \xAB${str(d.title)}\xBB (documentUuid ${d.uuid}${typeof d.type === "string" ? `, ${d.type}` : ""})`);
12932
+ }
12933
+ if (parts.length === 0) return text;
12934
+ return `[AiNecto context \u2014 "this project" / "this document" mean these] ${parts.join(" \xB7 ")}
12935
+
12936
+ ${text}`;
12937
+ }
12938
+ function str(value) {
12939
+ return typeof value === "string" ? value : "";
12940
+ }
12941
+
12942
+ // src/core/agent/reply.ts
12943
+ function replyForOutcome(outcome, limits) {
12944
+ const documentUuids = outcome.kind === "result" ? outcome.documents.slice(0, limits.documentsMax) : [];
12945
+ const failed = (errorCode2, text) => ({ status: "FAILED", errorCode: errorCode2, reply: clip(text, limits.textMaxCharacters), documentUuids });
12946
+ switch (outcome.kind) {
12947
+ case "timeout":
12948
+ return failed("TURN_TIMEOUT", "\uC81C\uD55C \uC2DC\uAC04 \uC548\uC5D0 \uB05D\uB098\uC9C0 \uC54A\uC544 \uBA48\uCDC4\uC5B4\uC694. \uB2E4\uC2DC \uBCF4\uB0B4 \uC8FC\uC138\uC694.");
12949
+ case "exited":
12950
+ return failed("AGENT_EXITED", `Claude Code \uAC00 \uB05D\uB0AC\uC5B4\uC694(code ${outcome.code ?? "-"}${outcome.signal ? `, ${outcome.signal}` : ""}).` + (outcome.stderrTail ? `
12951
+ ${outcome.stderrTail}` : ""));
12952
+ case "result": {
12953
+ const { result } = outcome;
12954
+ if (result.isError) {
12955
+ return failed("AGENT_ERROR", `Claude Code \uAC00 \uC2E4\uD328\uD588\uC5B4\uC694(${result.subtype ?? "error"}).` + (result.resultText ? `
12956
+ ${result.resultText}` : ""));
12957
+ }
12958
+ const reply = result.structuredOutput?.reply;
12959
+ const keys = result.structuredOutput && typeof result.structuredOutput === "object" ? Object.keys(result.structuredOutput) : [];
12960
+ if (typeof reply === "string" && reply.trim().length > 0 && keys.length === 1 && codePoints(reply) <= limits.textMaxCharacters) {
12961
+ return { status: "DONE", reply, documentUuids };
12962
+ }
12963
+ const raw = result.structuredOutput !== void 0 ? JSON.stringify(result.structuredOutput) : result.resultText ?? "";
12964
+ return failed("SCHEMA_VIOLATION", `\uB2F5 \uD615\uC2DD\uC774 \uC5B4\uAE0B\uB0AC\uC5B4\uC694.
12965
+ ${raw}`);
12966
+ }
12967
+ }
12968
+ }
12969
+ function codePoints(text) {
12970
+ return Array.from(text).length;
12971
+ }
12972
+ function clip(text, max) {
12973
+ const chars = Array.from(text.trim() || "-");
12974
+ return chars.length <= max ? chars.join("") : `${chars.slice(0, max - 1).join("")}\u2026`;
12975
+ }
12976
+
12977
+ // src/core/agent/serve.ts
12978
+ async function runAgentServe(deps) {
12979
+ const opened = await deps.http.call("open", { hostName: deps.hostName, cliVersion: deps.cliVersion });
12980
+ const linkId = String(opened.linkId);
12981
+ deps.http.useLinkToken(String(opened.linkToken));
12982
+ const limits = serverLimits(opened.limits);
12983
+ deps.print(`Connected to AiNecto as ${deps.hostName}. Requests to \xABMy agent\xBB in AiNecto Chat now run in Claude Code on this machine. Press Ctrl+C to stop.`);
12984
+ const stopController = new AbortController();
12985
+ let stopReason;
12986
+ let exitCode2 = 0;
12987
+ let wakeWorker;
12988
+ let current;
12989
+ const received = /* @__PURE__ */ new Set();
12990
+ const queue = [];
12991
+ const stop = (reason, code, message) => {
12992
+ if (stopReason) return;
12993
+ stopReason = reason;
12994
+ exitCode2 = code;
12995
+ if (message) deps.print(message);
12996
+ stopController.abort();
12997
+ wakeWorker?.();
12998
+ void current?.stop();
12999
+ };
13000
+ const onUserStop = () => stop("signal", 0, "Stopping\u2026");
13001
+ if (deps.signal.aborted) onUserStop();
13002
+ deps.signal.addEventListener("abort", onUserStop, { once: true });
13003
+ const stopOnFatal = (error) => {
13004
+ if (error.code === "LINK_NOT_FOUND") {
13005
+ stop("link_not_found", 1, "This connection ended (the server restarted, or the link was closed). Run `ainecto agent serve` again.");
13006
+ } else if (error.status === 401 || error.status === 403) {
13007
+ stop("auth", 1, `AiNecto refused the CLI sign-in (${error.code ?? error.status}). Run \`ainecto auth login\` and start again.`);
13008
+ }
13009
+ };
13010
+ const pollLoop = (async () => {
13011
+ let delay;
13012
+ while (!stopReason) {
13013
+ try {
13014
+ const res = await deps.http.call("poll", { linkId }, { holdMs: limits.pollHoldSeconds * 1e3, signal: stopController.signal });
13015
+ delay = void 0;
13016
+ if (res.state === "CLOSED") {
13017
+ stop("replaced", 1, "Another PC connected as your agent, so this one stopped.");
13018
+ return;
13019
+ }
13020
+ for (const offer of offersIn(res)) {
13021
+ const duplicate = received.has(offer.turnUuid);
13022
+ deps.log.write({ event: "offer", turnUuid: offer.turnUuid, conversationUuid: offer.conversationUuid, duplicate });
13023
+ if (duplicate) continue;
13024
+ received.add(offer.turnUuid);
13025
+ queue.push(offer);
13026
+ wakeWorker?.();
13027
+ }
13028
+ } catch (error) {
13029
+ if (stopReason) return;
13030
+ if (error instanceof AgentCallError && !error.retryable) {
13031
+ stopOnFatal(error);
13032
+ if (stopReason) return;
13033
+ }
13034
+ delay = nextRetryDelay(delay);
13035
+ await sleep(delay, stopController.signal);
13036
+ }
13037
+ }
13038
+ })();
13039
+ const runTurn = async (offer) => {
13040
+ if (current && (current.conversationUuid !== offer.conversationUuid || !current.alive)) {
13041
+ await current.stop();
13042
+ current = void 0;
13043
+ }
13044
+ if (!current) {
13045
+ const resumeSessionId = deps.sessions.get(offer.conversationUuid);
13046
+ current = new ClaudeAgentProcess(
13047
+ offer.conversationUuid,
13048
+ resumeSessionId !== void 0,
13049
+ deps.claudeArgs({ textMaxCharacters: limits.textMaxCharacters, resumeSessionId }),
13050
+ { cwd: deps.cwd, env: deps.env, spawnImpl: deps.spawnImpl, writeTools: deps.writeTools, log: deps.log }
13051
+ );
13052
+ }
13053
+ const proc = current;
13054
+ const started = Date.now();
13055
+ deps.log.write({ event: "agent_request", turnUuid: offer.turnUuid, conversationUuid: offer.conversationUuid, pid: proc.pid, textLength: offer.text.length });
13056
+ let outcome;
13057
+ let reply;
13058
+ let failure2;
13059
+ try {
13060
+ outcome = await proc.runTurn(agentPromptText(offer.text, offer.context), deps.turnTimeoutMs ?? AGENT_TURN_TIMEOUT_MS);
13061
+ if (outcome.kind === "timeout") {
13062
+ await proc.stop();
13063
+ }
13064
+ if (outcome.kind !== "result") {
13065
+ current = current === proc ? void 0 : current;
13066
+ if (proc.resumed) await deps.sessions.forget(offer.conversationUuid);
13067
+ } else if (proc.sessionId) {
13068
+ await deps.sessions.set(offer.conversationUuid, proc.sessionId);
13069
+ }
13070
+ reply = replyForOutcome(outcome, limits);
13071
+ return reply;
13072
+ } catch (error) {
13073
+ failure2 = error instanceof Error ? error.message : String(error);
13074
+ reply = { status: "FAILED", errorCode: "AGENT_INTERNAL", reply: `The PC agent failed: ${failure2}`.slice(0, limits.textMaxCharacters), documentUuids: [] };
13075
+ return reply;
13076
+ } finally {
13077
+ const result = outcome?.kind === "result" ? outcome.result : void 0;
13078
+ deps.log.write({
13079
+ event: "agent_response",
13080
+ turnUuid: offer.turnUuid,
13081
+ ms: Date.now() - started,
13082
+ outcome: outcome?.kind ?? `error: ${failure2 ?? "unknown"}`,
13083
+ status: reply?.status ?? "FAILED",
13084
+ errorCode: reply?.errorCode,
13085
+ subtype: result?.subtype,
13086
+ sessionId: proc.sessionId,
13087
+ documents: reply?.documentUuids.length ?? 0,
13088
+ numTurns: result?.numTurns,
13089
+ costUsd: result?.costUsd
13090
+ });
13091
+ }
13092
+ };
13093
+ const sendReply = async (offer, reply) => {
13094
+ let delay;
13095
+ for (; ; ) {
13096
+ try {
13097
+ await deps.http.call("reply", {
13098
+ linkId,
13099
+ turnUuid: offer.turnUuid,
13100
+ conversationUuid: offer.conversationUuid,
13101
+ status: reply.status,
13102
+ reply: reply.reply,
13103
+ documentUuids: reply.documentUuids,
13104
+ ...reply.errorCode ? { errorCode: reply.errorCode } : {}
13105
+ }, { turnUuid: offer.turnUuid, signal: stopController.signal });
13106
+ return;
13107
+ } catch (error) {
13108
+ if (stopReason) return;
13109
+ if (error instanceof AgentCallError && !error.retryable) {
13110
+ stopOnFatal(error);
13111
+ return;
13112
+ }
13113
+ delay = nextRetryDelay(delay);
13114
+ await sleep(delay, stopController.signal);
13115
+ }
13116
+ }
13117
+ };
13118
+ const worker = (async () => {
13119
+ while (!stopReason) {
13120
+ const offer = queue.shift();
13121
+ if (!offer) {
13122
+ await new Promise((resolve4) => {
13123
+ wakeWorker = resolve4;
13124
+ });
13125
+ wakeWorker = void 0;
13126
+ continue;
13127
+ }
13128
+ const reply = await runTurn(offer);
13129
+ if (!stopReason) await sendReply(offer, reply);
13130
+ }
13131
+ })();
13132
+ await Promise.allSettled([pollLoop, worker]);
13133
+ deps.signal.removeEventListener("abort", onUserStop);
13134
+ await current?.stop();
13135
+ if (stopReason === "signal") {
13136
+ try {
13137
+ await deps.http.call("close", { linkId });
13138
+ } catch {
13139
+ }
13140
+ }
13141
+ return { exitCode: exitCode2, reason: stopReason ?? "unknown" };
13142
+ }
13143
+ function serverLimits(value) {
13144
+ const v = value ?? {};
13145
+ const num = (key) => {
13146
+ const n = v[key];
13147
+ if (typeof n !== "number" || !Number.isFinite(n) || n <= 0) {
13148
+ throw new Error(`The server did not send limits.${key}; update the CLI or the server.`);
13149
+ }
13150
+ return n;
13151
+ };
13152
+ return { pollHoldSeconds: num("pollHoldSeconds"), textMaxCharacters: num("textMaxCharacters"), documentsMax: num("documentsMax") };
13153
+ }
13154
+ function offersIn(res) {
13155
+ if (!Array.isArray(res.offers)) return [];
13156
+ return res.offers.flatMap((o) => {
13157
+ const offer = o;
13158
+ return typeof offer.turnUuid === "string" && typeof offer.conversationUuid === "string" && typeof offer.text === "string" ? [{
13159
+ turnUuid: offer.turnUuid,
13160
+ conversationUuid: offer.conversationUuid,
13161
+ text: offer.text,
13162
+ context: offer.context && typeof offer.context === "object" ? offer.context : void 0
13163
+ }] : [];
13164
+ });
13165
+ }
13166
+ function sleep(ms, signal) {
13167
+ return new Promise((resolve4) => {
13168
+ if (signal.aborted) {
13169
+ resolve4();
13170
+ return;
13171
+ }
13172
+ const timer = setTimeout(done, ms);
13173
+ signal.addEventListener("abort", done, { once: true });
13174
+ function done() {
13175
+ clearTimeout(timer);
13176
+ signal.removeEventListener("abort", done);
13177
+ resolve4();
13178
+ }
13179
+ });
13180
+ }
13181
+
13182
+ // src/core/agent/sessions.ts
13183
+ import { readFile as readFile9, rename as rename6, writeFile as writeFile6 } from "fs/promises";
13184
+ var AgentSessionMap = class {
13185
+ constructor(path) {
13186
+ this.path = path;
13187
+ }
13188
+ path;
13189
+ map = {};
13190
+ async load() {
13191
+ try {
13192
+ const parsed = JSON.parse(await readFile9(this.path, "utf8"));
13193
+ if (parsed.conversations && typeof parsed.conversations === "object") {
13194
+ this.map = parsed.conversations;
13195
+ }
13196
+ } catch (error) {
13197
+ if (isNodeError(error, "ENOENT")) return;
13198
+ this.map = {};
13199
+ }
13200
+ }
13201
+ get(conversationUuid) {
13202
+ return this.map[conversationUuid]?.sessionId;
13203
+ }
13204
+ async set(conversationUuid, sessionId) {
13205
+ this.map[conversationUuid] = { sessionId, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
13206
+ await this.save();
13207
+ }
13208
+ async forget(conversationUuid) {
13209
+ delete this.map[conversationUuid];
13210
+ await this.save();
13211
+ }
13212
+ async save() {
13213
+ const tmp = `${this.path}.tmp`;
13214
+ await writeFile6(tmp, `${JSON.stringify({ version: 1, conversations: this.map }, null, 2)}
13215
+ `, { mode: PRIVATE_FILE_MODE });
13216
+ await rename6(tmp, this.path);
13217
+ }
13218
+ };
13219
+
13220
+ // src/adapters/cli/agentCommand.ts
13221
+ var AGENT_HELP_LINES = [
13222
+ " ainecto agent serve [--env prod|dev] answer \xABMy agent\xBB requests from AiNecto Chat with Claude Code on this machine (Ctrl+C to stop; log: ~/.ainecto/agent/agent.log)"
13223
+ ];
13224
+ function isAgentCommand(domain, action) {
13225
+ return domain === "agent" && action === "serve";
13226
+ }
13227
+ async function executeAgentServe(input) {
13228
+ const claude = spawnSync("claude", ["--version"], { encoding: "utf8" });
13229
+ if (claude.error || claude.status !== 0) {
13230
+ throw new Error("Claude Code (`claude`) is not on PATH. Install Claude Code and sign in, then run `ainecto agent serve` again.");
13231
+ }
13232
+ const paths = agentPaths();
13233
+ await ensurePrivateDir(paths.dir);
13234
+ await ensurePrivateDir(paths.work);
13235
+ const log = openAgentLog(paths);
13236
+ const host = hostname();
13237
+ log.write({ event: "serve_start", pid: process.pid, version: CLI_VERSION, endpoint: input.endpoint, host, claude: claude.stdout.trim() });
13238
+ const rpc = new McpRpcClient({ endpoint: input.endpoint, tokenProvider: input.auth, channel: "cli" });
13239
+ const started = Date.now();
13240
+ log.write({ event: "agent_call", callId: `tools-list-${started}`, op: "tools_list", bytes: 0 });
13241
+ let writeTools;
13242
+ try {
13243
+ writeTools = writeToolNames(await rpc.toolsList());
13244
+ log.write({ event: "agent_call_response", callId: `tools-list-${started}`, op: "tools_list", ok: true, ms: Date.now() - started });
13245
+ } catch (error) {
13246
+ log.write({
13247
+ event: "agent_call_response",
13248
+ callId: `tools-list-${started}`,
13249
+ op: "tools_list",
13250
+ ok: false,
13251
+ error: error instanceof Error ? error.message : String(error),
13252
+ ms: Date.now() - started
13253
+ });
13254
+ throw error;
13255
+ }
13256
+ const sessions = new AgentSessionMap(paths.sessions);
13257
+ await sessions.load();
13258
+ const controller = new AbortController();
13259
+ const onSignal = () => controller.abort();
13260
+ process.once("SIGINT", onSignal);
13261
+ process.once("SIGTERM", onSignal);
13262
+ const cliScript = process.argv[1];
13263
+ if (!cliScript) throw new Error("Cannot find the path of this CLI to start the AiNecto bridge for Claude Code.");
13264
+ const mcpConfig = agentMcpConfig(process.execPath, cliScript, input.endpoint);
13265
+ try {
13266
+ const result = await runAgentServe({
13267
+ http: createAgentHttp({ endpoint: input.endpoint, tokenProvider: input.auth, log }),
13268
+ log,
13269
+ sessions,
13270
+ writeTools,
13271
+ claudeArgs: ({ textMaxCharacters, resumeSessionId }) => claudeAgentArgs({ schema: agentReplySchema(textMaxCharacters), mcpConfig, resumeSessionId }),
13272
+ spawnImpl: spawnClaude,
13273
+ cwd: paths.work,
13274
+ env: claudeAgentEnv(process.env),
13275
+ hostName: host,
13276
+ cliVersion: CLI_VERSION,
13277
+ print: (line) => input.io.stderr.write(`${line}
13278
+ `),
13279
+ signal: controller.signal
13280
+ });
13281
+ log.write({ event: "serve_stop", pid: process.pid, reason: result.reason });
13282
+ return result.exitCode;
13283
+ } catch (error) {
13284
+ log.write({ event: "serve_stop", pid: process.pid, reason: `error: ${error instanceof Error ? error.message : String(error)}` });
13285
+ throw error;
13286
+ } finally {
13287
+ process.off("SIGINT", onSignal);
13288
+ process.off("SIGTERM", onSignal);
13289
+ }
13290
+ }
13291
+
13292
+ // src/adapters/cli/ainectoCli.ts
13293
+ import { homedir as homedir4 } from "os";
12567
13294
  async function runAinectoCli(argv, io) {
12568
13295
  let parsed = { json: argv.includes("--json"), help: false, version: false, positionals: [] };
12569
13296
  try {
@@ -12650,8 +13377,8 @@ async function runAinectoCli(argv, io) {
12650
13377
  auth,
12651
13378
  client: client2,
12652
13379
  // ★외부 CLI 는 홈 폴더에서 돈다 — 지금 저장소의 local·project 범위 항목이 사용자 범위 판정에 끼지 않게.
12653
- runner: systemCommandRunner({ cwd: homedir3() }),
12654
- home: homedir3(),
13380
+ runner: systemCommandRunner({ cwd: homedir4() }),
13381
+ home: homedir4(),
12655
13382
  json: parsed.json,
12656
13383
  io
12657
13384
  });
@@ -12668,9 +13395,9 @@ async function runAinectoCli(argv, io) {
12668
13395
  tokenStore,
12669
13396
  auth,
12670
13397
  client: client2,
12671
- runner: systemCommandRunner({ cwd: homedir3() }),
13398
+ runner: systemCommandRunner({ cwd: homedir4() }),
12672
13399
  hereRunner: systemCommandRunner({ cwd: process.cwd() }),
12673
- home: homedir3(),
13400
+ home: homedir4(),
12674
13401
  cwd: process.cwd(),
12675
13402
  envVars: process.env
12676
13403
  },
@@ -12678,6 +13405,9 @@ async function runAinectoCli(argv, io) {
12678
13405
  io
12679
13406
  });
12680
13407
  }
13408
+ if (isAgentCommand(domain, action)) {
13409
+ return await executeAgentServe({ endpoint: resolved.endpoint, auth: connect2().auth, io });
13410
+ }
12681
13411
  if (domain === "auth") {
12682
13412
  return await handleAuth(action, connect2().auth, resolved.endpoint, parsed.json, io);
12683
13413
  }
@@ -12882,6 +13612,9 @@ function helpText() {
12882
13612
  " ainecto init --agent NAME name the agent working in this folder: writes .ainecto/agent.local (do not commit it) and puts the name in .mcp.json, .cursor/mcp.json and .codex/config.toml",
12883
13613
  " ainecto init --undo",
12884
13614
  "",
13615
+ "Your PC agent (requests from AiNecto Chat, answered by Claude Code on this machine):",
13616
+ ...AGENT_HELP_LINES,
13617
+ "",
12885
13618
  "Terminal sessions on this machine (no tmux or cmux needed; macOS and Linux):",
12886
13619
  ...TERM_HELP_LINES,
12887
13620
  ""