adde-acp 0.1.3

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 (136) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +88 -0
  3. package/README.md +88 -0
  4. package/dist/backend/acp/client.d.ts +149 -0
  5. package/dist/backend/acp/client.js +538 -0
  6. package/dist/backend/acp/client.js.map +1 -0
  7. package/dist/backend/acp/index.d.ts +8 -0
  8. package/dist/backend/acp/index.js +7 -0
  9. package/dist/backend/acp/index.js.map +1 -0
  10. package/dist/backend/acp/lifecycle.d.ts +15 -0
  11. package/dist/backend/acp/lifecycle.js +56 -0
  12. package/dist/backend/acp/lifecycle.js.map +1 -0
  13. package/dist/backend/acp/perm-diff.d.ts +37 -0
  14. package/dist/backend/acp/perm-diff.js +58 -0
  15. package/dist/backend/acp/perm-diff.js.map +1 -0
  16. package/dist/backend/acp/spawn.d.ts +20 -0
  17. package/dist/backend/acp/spawn.js +70 -0
  18. package/dist/backend/acp/spawn.js.map +1 -0
  19. package/dist/cli/adde.d.ts +2 -0
  20. package/dist/cli/adde.js +11 -0
  21. package/dist/cli/adde.js.map +1 -0
  22. package/dist/cli/alias.d.ts +45 -0
  23. package/dist/cli/alias.js +94 -0
  24. package/dist/cli/alias.js.map +1 -0
  25. package/dist/cli/completion.d.ts +4 -0
  26. package/dist/cli/completion.js +209 -0
  27. package/dist/cli/completion.js.map +1 -0
  28. package/dist/cli/init.d.ts +3 -0
  29. package/dist/cli/init.js +114 -0
  30. package/dist/cli/init.js.map +1 -0
  31. package/dist/cli/lane.d.ts +20 -0
  32. package/dist/cli/lane.js +350 -0
  33. package/dist/cli/lane.js.map +1 -0
  34. package/dist/cli/ops.d.ts +5 -0
  35. package/dist/cli/ops.js +230 -0
  36. package/dist/cli/ops.js.map +1 -0
  37. package/dist/cli/prompt.d.ts +15 -0
  38. package/dist/cli/prompt.js +41 -0
  39. package/dist/cli/prompt.js.map +1 -0
  40. package/dist/cli/run.d.ts +5 -0
  41. package/dist/cli/run.js +216 -0
  42. package/dist/cli/run.js.map +1 -0
  43. package/dist/cli/spec.d.ts +48 -0
  44. package/dist/cli/spec.js +98 -0
  45. package/dist/cli/spec.js.map +1 -0
  46. package/dist/core/diagnostics.d.ts +73 -0
  47. package/dist/core/diagnostics.js +333 -0
  48. package/dist/core/diagnostics.js.map +1 -0
  49. package/dist/core/index.d.ts +11 -0
  50. package/dist/core/index.js +9 -0
  51. package/dist/core/index.js.map +1 -0
  52. package/dist/core/injector.d.ts +27 -0
  53. package/dist/core/injector.js +297 -0
  54. package/dist/core/injector.js.map +1 -0
  55. package/dist/core/lane-config.d.ts +80 -0
  56. package/dist/core/lane-config.js +303 -0
  57. package/dist/core/lane-config.js.map +1 -0
  58. package/dist/core/launchd.d.ts +81 -0
  59. package/dist/core/launchd.js +216 -0
  60. package/dist/core/launchd.js.map +1 -0
  61. package/dist/core/messages.d.ts +31 -0
  62. package/dist/core/messages.js +71 -0
  63. package/dist/core/messages.js.map +1 -0
  64. package/dist/core/queue.d.ts +74 -0
  65. package/dist/core/queue.js +227 -0
  66. package/dist/core/queue.js.map +1 -0
  67. package/dist/core/runtime-state.d.ts +52 -0
  68. package/dist/core/runtime-state.js +90 -0
  69. package/dist/core/runtime-state.js.map +1 -0
  70. package/dist/core/session-ledger.d.ts +25 -0
  71. package/dist/core/session-ledger.js +89 -0
  72. package/dist/core/session-ledger.js.map +1 -0
  73. package/dist/core/supervisor.d.ts +41 -0
  74. package/dist/core/supervisor.js +315 -0
  75. package/dist/core/supervisor.js.map +1 -0
  76. package/dist/core/transcript.d.ts +22 -0
  77. package/dist/core/transcript.js +93 -0
  78. package/dist/core/transcript.js.map +1 -0
  79. package/dist/core/update-check.d.ts +25 -0
  80. package/dist/core/update-check.js +142 -0
  81. package/dist/core/update-check.js.map +1 -0
  82. package/dist/core/version.d.ts +7 -0
  83. package/dist/core/version.js +32 -0
  84. package/dist/core/version.js.map +1 -0
  85. package/dist/gate/gate.d.ts +41 -0
  86. package/dist/gate/gate.js +28 -0
  87. package/dist/gate/gate.js.map +1 -0
  88. package/dist/gate/index.d.ts +6 -0
  89. package/dist/gate/index.js +6 -0
  90. package/dist/gate/index.js.map +1 -0
  91. package/dist/shared/conf.d.ts +54 -0
  92. package/dist/shared/conf.js +85 -0
  93. package/dist/shared/conf.js.map +1 -0
  94. package/dist/shared/deny-match.d.ts +19 -0
  95. package/dist/shared/deny-match.js +122 -0
  96. package/dist/shared/deny-match.js.map +1 -0
  97. package/dist/shared/envelope.d.ts +37 -0
  98. package/dist/shared/envelope.js +91 -0
  99. package/dist/shared/envelope.js.map +1 -0
  100. package/dist/shared/errors.d.ts +8 -0
  101. package/dist/shared/errors.js +23 -0
  102. package/dist/shared/errors.js.map +1 -0
  103. package/dist/shared/fs-atomic.d.ts +17 -0
  104. package/dist/shared/fs-atomic.js +31 -0
  105. package/dist/shared/fs-atomic.js.map +1 -0
  106. package/dist/shared/i18n.d.ts +23 -0
  107. package/dist/shared/i18n.js +53 -0
  108. package/dist/shared/i18n.js.map +1 -0
  109. package/dist/shared/locales/en.d.ts +393 -0
  110. package/dist/shared/locales/en.js +447 -0
  111. package/dist/shared/locales/en.js.map +1 -0
  112. package/dist/shared/locales/ko.d.ts +389 -0
  113. package/dist/shared/locales/ko.js +443 -0
  114. package/dist/shared/locales/ko.js.map +1 -0
  115. package/dist/shared/mask.d.ts +6 -0
  116. package/dist/shared/mask.js +28 -0
  117. package/dist/shared/mask.js.map +1 -0
  118. package/dist/shared/notify.d.ts +15 -0
  119. package/dist/shared/notify.js +20 -0
  120. package/dist/shared/notify.js.map +1 -0
  121. package/dist/shared/paths.d.ts +42 -0
  122. package/dist/shared/paths.js +83 -0
  123. package/dist/shared/paths.js.map +1 -0
  124. package/dist/src-adapters/index.d.ts +8 -0
  125. package/dist/src-adapters/index.js +6 -0
  126. package/dist/src-adapters/index.js.map +1 -0
  127. package/dist/src-adapters/markdown.d.ts +80 -0
  128. package/dist/src-adapters/markdown.js +794 -0
  129. package/dist/src-adapters/markdown.js.map +1 -0
  130. package/dist/src-adapters/source.d.ts +33 -0
  131. package/dist/src-adapters/source.js +3 -0
  132. package/dist/src-adapters/source.js.map +1 -0
  133. package/dist/src-adapters/telegram.d.ts +48 -0
  134. package/dist/src-adapters/telegram.js +412 -0
  135. package/dist/src-adapters/telegram.js.map +1 -0
  136. package/package.json +62 -0
@@ -0,0 +1,31 @@
1
+ /** CLI 명령 표면. 최소 표면 원칙. */
2
+ export declare const COMMANDS: {
3
+ /** 주 진입점. */
4
+ readonly primary: "adde";
5
+ /** 단축 별칭. */
6
+ readonly short: "add";
7
+ };
8
+ /** 최상위 도움말(인자 없음·미지원 명령 시). */
9
+ export declare function buildUsage(): string;
10
+ /** 명령별 사용법 한 줄(인자 누락 시 안내). 끝에 \n 없음 — 호출부가 개행 부여. getter 로 현재 로케일 반영. */
11
+ export declare const USAGE: {
12
+ readonly up: string;
13
+ readonly down: string;
14
+ readonly restart: string;
15
+ readonly status: string;
16
+ readonly logs: string;
17
+ readonly sessions: string;
18
+ readonly laneAdd: string;
19
+ readonly laneLs: string;
20
+ readonly laneShow: string;
21
+ readonly laneRm: string;
22
+ readonly completion: string;
23
+ };
24
+ /** `adde lane` 그룹 도움말. */
25
+ export declare function buildLaneUsage(): string;
26
+ /** 최상위 명령 오류 — `[adde <cmd>] 오류: <detail>`. */
27
+ export declare function cmdError(cmd: string, detail: string): string;
28
+ /** `adde lane` 하위 오류 — `[adde lane] <detail>`. */
29
+ export declare function laneError(detail: string): string;
30
+ /** 알 수 없는 lane 서브커맨드 안내(+ 사용법). */
31
+ export declare function unknownLaneSub(sub: string): string;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * CLI 사용자 노출 문자열의 단일 표면 — 사용법·명령 오류 안내·도움말.
3
+ * 문구 본문은 i18n 카탈로그(`shared/locales/`)가 소유하고, 본 모듈은 CLI API 를 유지한다.
4
+ * presentation 계층(cli/run·lane·ops) 전용. 내부 라이브러리 throw Error(개발자 대상)는 여기서 다루지 않는다.
5
+ * 런타임 차단·예외 포맷은 `shared/notify.ts`(formatBlock/formatException) 담당 — 역할 분리.
6
+ */
7
+ import { t } from "../shared/i18n.js";
8
+ /** CLI 명령 표면. 최소 표면 원칙. */
9
+ export const COMMANDS = {
10
+ /** 주 진입점. */
11
+ primary: "adde",
12
+ /** 단축 별칭. */
13
+ short: "add",
14
+ };
15
+ /** 최상위 도움말(인자 없음·미지원 명령 시). */
16
+ export function buildUsage() {
17
+ return t("usage.main", { primary: COMMANDS.primary, short: COMMANDS.short });
18
+ }
19
+ /** 명령별 사용법 한 줄(인자 누락 시 안내). 끝에 \n 없음 — 호출부가 개행 부여. getter 로 현재 로케일 반영. */
20
+ export const USAGE = {
21
+ get up() {
22
+ return t("usage.up");
23
+ },
24
+ get down() {
25
+ return t("usage.down");
26
+ },
27
+ get restart() {
28
+ return t("usage.restart");
29
+ },
30
+ get status() {
31
+ return t("usage.status");
32
+ },
33
+ get logs() {
34
+ return t("usage.logs");
35
+ },
36
+ get sessions() {
37
+ return t("usage.sessions");
38
+ },
39
+ get laneAdd() {
40
+ return t("usage.laneAdd");
41
+ },
42
+ get laneLs() {
43
+ return t("usage.laneLs");
44
+ },
45
+ get laneShow() {
46
+ return t("usage.laneShow");
47
+ },
48
+ get laneRm() {
49
+ return t("usage.laneRm");
50
+ },
51
+ get completion() {
52
+ return t("usage.completion");
53
+ },
54
+ };
55
+ /** `adde lane` 그룹 도움말. */
56
+ export function buildLaneUsage() {
57
+ return t("usage.lane");
58
+ }
59
+ /** 최상위 명령 오류 — `[adde <cmd>] 오류: <detail>`. */
60
+ export function cmdError(cmd, detail) {
61
+ return t("cli.cmdError", { cmd, detail });
62
+ }
63
+ /** `adde lane` 하위 오류 — `[adde lane] <detail>`. */
64
+ export function laneError(detail) {
65
+ return t("cli.laneError", { detail });
66
+ }
67
+ /** 알 수 없는 lane 서브커맨드 안내(+ 사용법). */
68
+ export function unknownLaneSub(sub) {
69
+ return `${t("cli.unknownSub", { sub })}\n\n${buildLaneUsage()}`;
70
+ }
71
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/core/messages.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,mBAAmB,CAAC;AAEtC,2BAA2B;AAC3B,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,aAAa;IACb,OAAO,EAAE,MAAM;IACf,aAAa;IACb,KAAK,EAAE,KAAK;CACJ,CAAC;AAEX,+BAA+B;AAC/B,MAAM,UAAU,UAAU;IACxB,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;AAC/E,CAAC;AAED,0EAA0E;AAC1E,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,IAAI,EAAE;QACJ,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;IACvB,CAAC;IACD,IAAI,IAAI;QACN,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;IACzB,CAAC;IACD,IAAI,OAAO;QACT,OAAO,CAAC,CAAC,eAAe,CAAC,CAAC;IAC5B,CAAC;IACD,IAAI,MAAM;QACR,OAAO,CAAC,CAAC,cAAc,CAAC,CAAC;IAC3B,CAAC;IACD,IAAI,IAAI;QACN,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;IACzB,CAAC;IACD,IAAI,QAAQ;QACV,OAAO,CAAC,CAAC,gBAAgB,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,OAAO;QACT,OAAO,CAAC,CAAC,eAAe,CAAC,CAAC;IAC5B,CAAC;IACD,IAAI,MAAM;QACR,OAAO,CAAC,CAAC,cAAc,CAAC,CAAC;IAC3B,CAAC;IACD,IAAI,QAAQ;QACV,OAAO,CAAC,CAAC,gBAAgB,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,MAAM;QACR,OAAO,CAAC,CAAC,cAAc,CAAC,CAAC;IAC3B,CAAC;IACD,IAAI,UAAU;QACZ,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAC/B,CAAC;CACF,CAAC;AAEF,0BAA0B;AAC1B,MAAM,UAAU,cAAc;IAC5B,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;AACzB,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,MAAc;IAClD,OAAO,CAAC,CAAC,cAAc,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,OAAO,CAAC,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,GAAG,CAAC,CAAC,gBAAgB,EAAE,EAAE,GAAG,EAAE,CAAC,OAAO,cAAc,EAAE,EAAE,CAAC;AAClE,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { basename } from "node:path";
2
+ import type { LanePaths } from "../shared/paths.js";
3
+ import type { Envelope } from "../shared/envelope.js";
4
+ /**
5
+ * envelope 을 queue 디렉토리에 atomic rename 으로 저장.
6
+ * tmp 작성 완료 후 rename → 부분 쓰기가 queue 에 노출되지 않는다.
7
+ */
8
+ export declare function enqueue(paths: LanePaths, envelope: Envelope): Promise<void>;
9
+ /**
10
+ * queue 에서 다음 envelope 을 꺼내 processing 으로 이동.
11
+ * 큐가 비어 있으면 null 반환.
12
+ */
13
+ export declare function claimNext(paths: LanePaths): Promise<{
14
+ id: string;
15
+ envelope: Envelope;
16
+ } | null>;
17
+ /**
18
+ * 손상된 processing 메시지를 격리한다(poison message 차단).
19
+ * processing/<id>.msg → processing/<id>.msg.corrupt (scanProcessing 의 `.msg` 필터에서 제외돼
20
+ * 재기동 시 재처리되지 않는다) + out/<id>.failed 가시성 기록.
21
+ */
22
+ export declare function quarantineCorrupt(paths: LanePaths, id: string, reason: unknown): Promise<void>;
23
+ /**
24
+ * processing 디렉토리 스캔 — 크래시 재개 대상 id 목록.
25
+ */
26
+ export declare function scanProcessing(paths: LanePaths): Promise<string[]>;
27
+ /**
28
+ * out/<id>.out 존재 여부 검사 — dedup 판정.
29
+ */
30
+ export declare function isDone(paths: LanePaths, id: string): Promise<boolean>;
31
+ /**
32
+ * 해당 id 가 큐/처리/출력 어디든 이미 존재하는지 검사 — 크래시 재개 시 중복 enqueue 방지.
33
+ * queue/<ts>-<id>.msg · processing/<id>.msg · out/<id>.out 중 하나라도 있으면 true.
34
+ */
35
+ export declare function hasId(paths: LanePaths, id: string): Promise<boolean>;
36
+ export interface OutSidecar {
37
+ reply_ref?: {
38
+ channel_msg_id: string;
39
+ thread?: string;
40
+ };
41
+ /** turn 완료 시각(ISO). */
42
+ ts?: string;
43
+ /** 원본 메시지 전송(enqueue) 시각(envelope.ts) — 채널 렌더의 스탬프 SoT. */
44
+ origin_ts?: string;
45
+ /** 원본 질문 발췌(첫 줄, 마스킹) — 채널 렌더 헤더의 맥락 표시용. */
46
+ question?: string;
47
+ }
48
+ /**
49
+ * 처리 결과를 out 디렉토리에 atomic rename 으로 기록.
50
+ * <id>.out (텍스트) + <id>.out.json (sidecar).
51
+ */
52
+ export declare function writeOut(paths: LanePaths, id: string, text: string, sidecar: OutSidecar): Promise<void>;
53
+ /**
54
+ * 채널 전송 성공 마커 out/<id>.sent 기록(atomic). `.out`(응답 영속·dedup)과 분리해
55
+ * "응답은 기록됐으나 채널 미전송" 상태를 표현 — render 실패 시 재전송 대상 판별에 쓰인다.
56
+ */
57
+ export declare function markSent(paths: LanePaths, id: string): Promise<void>;
58
+ /** out/<id>.sent 존재 여부 — 채널 전송 완료 판정. */
59
+ export declare function isSent(paths: LanePaths, id: string): Promise<boolean>;
60
+ /**
61
+ * 응답은 기록됐으나(out/<id>.out) 채널 전송이 안 된(out/<id>.sent 부재) id 목록.
62
+ * render 실패·크래시로 미전달된 응답의 재전송 대상.
63
+ */
64
+ export declare function findUnsent(paths: LanePaths): Promise<string[]>;
65
+ /**
66
+ * inject 실패 등 처리 실패를 out/<id>.failed 로 기록(E1, 가시성).
67
+ * dedup 마커(.out)가 아니므로 processing/<id>.msg 는 남아 재기동 시 재처리된다(at-least-once 유지).
68
+ */
69
+ export declare function writeFailed(paths: LanePaths, id: string, reason: string): Promise<void>;
70
+ /** processing/<id>.msg 경로를 직접 반환 (재처리 복원 등에 사용). */
71
+ export declare function processingFilePath(paths: LanePaths, id: string): string;
72
+ /** out/<id>.out.json sidecar 읽기. */
73
+ export declare function readSidecar(paths: LanePaths, id: string): Promise<OutSidecar | null>;
74
+ export { basename };
@@ -0,0 +1,227 @@
1
+ /**
2
+ * atomic 저장·상태 전이·dedup.
3
+ * tmp→rename 으로 부분 쓰기 미노출.
4
+ * queue→processing→out 상태 전이는 원자적 rename.
5
+ */
6
+ import { t } from "../shared/i18n.js";
7
+ import { mkdir, rename, readdir, access, readFile } from "node:fs/promises";
8
+ import { join, basename } from "node:path";
9
+ import { atomicWrite } from "../shared/fs-atomic.js";
10
+ import { errMsg, errCode } from "../shared/errors.js";
11
+ import { serializeEnvelope, parseEnvelope } from "../shared/envelope.js";
12
+ import { formatException } from "../shared/notify.js";
13
+ /** queue 파일명 형식: <ts_ms>-<id>.msg */
14
+ function queueFileName(envelope) {
15
+ const ts = Date.now();
16
+ return `${ts}-${envelope.id}.msg`;
17
+ }
18
+ /** processing 파일명: <id>.msg */
19
+ function processingFileName(id) {
20
+ return `${id}.msg`;
21
+ }
22
+ /** id 를 queue 파일명에서 추출. */
23
+ function idFromQueueFile(filename) {
24
+ const base = filename.replace(/\.msg$/, "");
25
+ const dashIdx = base.indexOf("-");
26
+ return dashIdx === -1 ? base : base.slice(dashIdx + 1);
27
+ }
28
+ /** id 를 processing 파일명에서 추출. */
29
+ function idFromProcessingFile(filename) {
30
+ return filename.replace(/\.msg$/, "");
31
+ }
32
+ /**
33
+ * envelope 을 queue 디렉토리에 atomic rename 으로 저장.
34
+ * tmp 작성 완료 후 rename → 부분 쓰기가 queue 에 노출되지 않는다.
35
+ */
36
+ export async function enqueue(paths, envelope) {
37
+ await atomicWrite(join(paths.queueDir, queueFileName(envelope)), serializeEnvelope(envelope));
38
+ }
39
+ /**
40
+ * queue 에서 다음 envelope 을 꺼내 processing 으로 이동.
41
+ * 큐가 비어 있으면 null 반환.
42
+ */
43
+ export async function claimNext(paths) {
44
+ await mkdir(paths.queueDir, { recursive: true });
45
+ await mkdir(paths.processingDir, { recursive: true });
46
+ let files;
47
+ try {
48
+ files = await readdir(paths.queueDir);
49
+ }
50
+ catch (err) {
51
+ // ENOENT(디렉터리 부재)는 빈 큐와 동치 — 정상. 그 외 FS 오류는 무음 흡수 금지(전파).
52
+ if (errCode(err) === "ENOENT")
53
+ return null;
54
+ throw err;
55
+ }
56
+ const msgFiles = files.filter((f) => f.endsWith(".msg")).sort();
57
+ // 정렬 순서대로 claim 시도 — 경합(ENOENT)·손상(parse 실패)은 건너뛰고 다음 메시지로.
58
+ for (const next of msgFiles) {
59
+ const id = idFromQueueFile(next);
60
+ const src = join(paths.queueDir, next);
61
+ const dst = join(paths.processingDir, processingFileName(id));
62
+ try {
63
+ await rename(src, dst);
64
+ }
65
+ catch (err) {
66
+ // ENOENT = 경합(다른 워커가 먼저 claim) 또는 파일 소멸 → 다음 후보로.
67
+ // 그 외(EBUSY/EACCES/EXDEV/ENOSPC/NFS 등)는 일시·구조적 FS 오류 → 액션형 로그 후 전파.
68
+ // 흡수해 null 을 돌리면 큐가 안 비었는데 idle 로 빠져 메시지가 무음 방치된다.
69
+ if (errCode(err) === "ENOENT")
70
+ continue;
71
+ console.error(formatException({
72
+ situation: t("queue.claimFail.situation", { code: errCode(err) ?? "unknown", path: src }),
73
+ action: t("queue.claimFail.action"),
74
+ }));
75
+ throw err;
76
+ }
77
+ let envelope;
78
+ try {
79
+ envelope = parseEnvelope(await readFile(dst, "utf8"));
80
+ }
81
+ catch (parseErr) {
82
+ // 손상 메시지(스키마/JSON 깨짐) — 격리 후 다음 후보로. 매 기동 동일 파싱오류 반복 차단.
83
+ await quarantineCorrupt(paths, id, parseErr);
84
+ continue;
85
+ }
86
+ return { id, envelope };
87
+ }
88
+ return null;
89
+ }
90
+ /**
91
+ * 손상된 processing 메시지를 격리한다(poison message 차단).
92
+ * processing/<id>.msg → processing/<id>.msg.corrupt (scanProcessing 의 `.msg` 필터에서 제외돼
93
+ * 재기동 시 재처리되지 않는다) + out/<id>.failed 가시성 기록.
94
+ */
95
+ export async function quarantineCorrupt(paths, id, reason) {
96
+ const detail = errMsg(reason);
97
+ const src = join(paths.processingDir, processingFileName(id));
98
+ const corrupt = `${src}.corrupt`;
99
+ try {
100
+ await rename(src, corrupt);
101
+ }
102
+ catch (err) {
103
+ // 이미 격리됐거나(ENOENT) 다른 워커가 처리 — 격리 자체 실패는 로그만(가시성 .failed 는 계속 기록).
104
+ if (errCode(err) !== "ENOENT") {
105
+ console.error(t("log.queue.quarantineFail", { id, code: errCode(err) ?? "unknown" }));
106
+ }
107
+ }
108
+ await writeFailed(paths, id, t("queue.quarantined", { ts: new Date().toISOString(), detail })).catch((e) => console.error(t("log.queue.failedWriteFail", { id, error: errMsg(e) })));
109
+ }
110
+ /**
111
+ * processing 디렉토리 스캔 — 크래시 재개 대상 id 목록.
112
+ */
113
+ export async function scanProcessing(paths) {
114
+ let files;
115
+ try {
116
+ files = await readdir(paths.processingDir);
117
+ }
118
+ catch {
119
+ return [];
120
+ }
121
+ return files.filter((f) => f.endsWith(".msg")).map(idFromProcessingFile);
122
+ }
123
+ /**
124
+ * out/<id>.out 존재 여부 검사 — dedup 판정.
125
+ */
126
+ export async function isDone(paths, id) {
127
+ const outPath = join(paths.outDir, `${id}.out`);
128
+ try {
129
+ await access(outPath);
130
+ return true;
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ }
136
+ /**
137
+ * 해당 id 가 큐/처리/출력 어디든 이미 존재하는지 검사 — 크래시 재개 시 중복 enqueue 방지.
138
+ * queue/<ts>-<id>.msg · processing/<id>.msg · out/<id>.out 중 하나라도 있으면 true.
139
+ */
140
+ export async function hasId(paths, id) {
141
+ if (await isDone(paths, id))
142
+ return true;
143
+ try {
144
+ await access(join(paths.processingDir, processingFileName(id)));
145
+ return true;
146
+ }
147
+ catch {
148
+ // processing 에 없음 — 큐 검사로 진행
149
+ }
150
+ try {
151
+ const files = await readdir(paths.queueDir);
152
+ return files.some((f) => f.endsWith(`-${id}.msg`));
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ }
158
+ /**
159
+ * 처리 결과를 out 디렉토리에 atomic rename 으로 기록.
160
+ * <id>.out (텍스트) + <id>.out.json (sidecar).
161
+ */
162
+ export async function writeOut(paths, id, text, sidecar) {
163
+ // sidecar 를 먼저 확정한다 — `.out` 이 dedup/done 마커(isDone)이므로 본문을 마지막에 rename 하면
164
+ // "`.out` 존재 ⇒ sidecar 존재" 가 성립한다. 두 rename 사이 크래시에도 done 메시지가 reply_ref 를
165
+ // 잃지 않고, reader 가 `.out` 만 보고 sidecar 부재 창을 만나지 않는다.
166
+ await atomicWrite(join(paths.outDir, `${id}.out.json`), JSON.stringify(sidecar));
167
+ await atomicWrite(join(paths.outDir, `${id}.out`), text);
168
+ }
169
+ /**
170
+ * 채널 전송 성공 마커 out/<id>.sent 기록(atomic). `.out`(응답 영속·dedup)과 분리해
171
+ * "응답은 기록됐으나 채널 미전송" 상태를 표현 — render 실패 시 재전송 대상 판별에 쓰인다.
172
+ */
173
+ export async function markSent(paths, id) {
174
+ await atomicWrite(join(paths.outDir, `${id}.sent`), new Date().toISOString());
175
+ }
176
+ /** out/<id>.sent 존재 여부 — 채널 전송 완료 판정. */
177
+ export async function isSent(paths, id) {
178
+ try {
179
+ await access(join(paths.outDir, `${id}.sent`));
180
+ return true;
181
+ }
182
+ catch {
183
+ return false;
184
+ }
185
+ }
186
+ /**
187
+ * 응답은 기록됐으나(out/<id>.out) 채널 전송이 안 된(out/<id>.sent 부재) id 목록.
188
+ * render 실패·크래시로 미전달된 응답의 재전송 대상.
189
+ */
190
+ export async function findUnsent(paths) {
191
+ let files;
192
+ try {
193
+ files = await readdir(paths.outDir);
194
+ }
195
+ catch {
196
+ return [];
197
+ }
198
+ const sent = new Set(files.filter((f) => f.endsWith(".sent")).map((f) => f.replace(/\.sent$/, "")));
199
+ return files
200
+ .filter((f) => f.endsWith(".out"))
201
+ .map((f) => f.replace(/\.out$/, ""))
202
+ .filter((id) => !sent.has(id));
203
+ }
204
+ /**
205
+ * inject 실패 등 처리 실패를 out/<id>.failed 로 기록(E1, 가시성).
206
+ * dedup 마커(.out)가 아니므로 processing/<id>.msg 는 남아 재기동 시 재처리된다(at-least-once 유지).
207
+ */
208
+ export async function writeFailed(paths, id, reason) {
209
+ await atomicWrite(join(paths.outDir, `${id}.failed`), reason);
210
+ }
211
+ /** processing/<id>.msg 경로를 직접 반환 (재처리 복원 등에 사용). */
212
+ export function processingFilePath(paths, id) {
213
+ return join(paths.processingDir, processingFileName(id));
214
+ }
215
+ /** out/<id>.out.json sidecar 읽기. */
216
+ export async function readSidecar(paths, id) {
217
+ const sidecarPath = join(paths.outDir, `${id}.out.json`);
218
+ try {
219
+ const json = await readFile(sidecarPath, "utf8");
220
+ return JSON.parse(json);
221
+ }
222
+ catch {
223
+ return null;
224
+ }
225
+ }
226
+ export { basename };
227
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/core/queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import type { LanePaths } from "../shared/paths.js";
2
+ /** 하트비트 touch 주기 — up 이 이 간격으로 runtime.json mtime 을 갱신(긴 인터벌, 보조 신호). */
3
+ export declare const HEARTBEAT_INTERVAL_MS = 60000;
4
+ /** stale 판정 임계 — mtime 이 이보다 오래되면 행(hung)으로 본다(인터벌의 3배 여유). */
5
+ export declare const HEARTBEAT_STALE_MS = 180000;
6
+ /** runtime.json 에 기록되는 레인 런타임 정보. */
7
+ export interface RuntimeInfo {
8
+ /** 스키마 버전 — 향후 형식 변경 시 forward-compat 판별. */
9
+ v: 1;
10
+ /** 레인을 기동한 up 프로세스의 pid. 생존 판정 대상. */
11
+ pid: number;
12
+ lane: string;
13
+ sessionId: string;
14
+ /** ISO8601 기동 시각. */
15
+ startedAt: string;
16
+ source: string;
17
+ backend: string;
18
+ engine: string;
19
+ }
20
+ /** 레인 라이브니스 — 파일/pid/하트비트 조합으로 판정. stale=pid 는 살아있으나 하트비트가 끊긴 행 상태. */
21
+ export type Liveness = "running" | "stale" | "dead" | "stopped";
22
+ /** runtime.json 을 원자적으로(tmp→rename) 기록. stateDir 부재 시 생성. */
23
+ export declare function writeRuntime(paths: LanePaths, info: RuntimeInfo): Promise<void>;
24
+ /**
25
+ * 하트비트 — runtime.json 의 mtime 만 현재시각으로 갱신(utimes, 내용 재작성 없음).
26
+ * 파일 부재(종료 레이스 등 ENOENT)는 무시 — 멱등. 그 외 오류는 호출부가 흡수(보조 신호).
27
+ */
28
+ export declare function touchRuntime(paths: LanePaths): Promise<void>;
29
+ /** runtime.json 제거(graceful 종료). 부재(ENOENT)는 무시 — 멱등. */
30
+ export declare function removeRuntime(paths: LanePaths): Promise<void>;
31
+ /** runtime.json 읽기. 부재·파싱불가·스키마 불일치 시 null(=stopped 로 취급). */
32
+ export declare function readRuntime(paths: LanePaths): Promise<RuntimeInfo | null>;
33
+ /**
34
+ * pid 생존 판정. `process.kill(pid, 0)` 은 신호를 보내지 않고 존재만 확인한다.
35
+ * - 성공: 프로세스 존재 → running.
36
+ * - EPERM: 존재하나 시그널 권한 없음(다른 소유자) → 존재하므로 running.
37
+ * - ESRCH(그 외): 프로세스 없음 → dead(크래시 잔존).
38
+ */
39
+ export declare function isPidAlive(pid: number): boolean;
40
+ /** livenessOf 판정 입력 — 하트비트(mtime) 신선도까지 보려면 mtimeMs 주입. */
41
+ export interface LivenessOptions {
42
+ /** runtime.json 의 mtime(ms). 주입 시 stale 판정에 사용. 미주입이면 pid-only(running/dead). */
43
+ mtimeMs?: number | undefined;
44
+ /** 현재시각(ms). 테스트 주입용. 기본 Date.now(). */
45
+ now?: number | undefined;
46
+ }
47
+ /**
48
+ * runtime.json + pid 생존 + 하트비트(mtime)로 라이브니스 판정.
49
+ * - 파일 없음 → stopped, pid 없음 → dead.
50
+ * - pid 생존 + mtime 임계 초과 → stale(행). mtime 미주입이면 running(pid-only).
51
+ */
52
+ export declare function livenessOf(info: RuntimeInfo | null, opts?: LivenessOptions): Liveness;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * 레인 라이브니스 상태 파일(runtime.json) 입출력 + pid 생존 판정.
3
+ * `adde up` 이 레인 기동 시 기록하고 graceful 종료(down·시그널)에서 제거한다.
4
+ * `adde status` 는 별도 프로세스라 up 의 in-memory 상태를 못 본다 → 이 파일이 유일한 교차 프로세스 신호.
5
+ */
6
+ import { unlink, readFile, utimes } from "node:fs/promises";
7
+ import { atomicWrite } from "../shared/fs-atomic.js";
8
+ /** 하트비트 touch 주기 — up 이 이 간격으로 runtime.json mtime 을 갱신(긴 인터벌, 보조 신호). */
9
+ export const HEARTBEAT_INTERVAL_MS = 60_000;
10
+ /** stale 판정 임계 — mtime 이 이보다 오래되면 행(hung)으로 본다(인터벌의 3배 여유). */
11
+ export const HEARTBEAT_STALE_MS = 180_000;
12
+ /** runtime.json 을 원자적으로(tmp→rename) 기록. stateDir 부재 시 생성. */
13
+ export async function writeRuntime(paths, info) {
14
+ await atomicWrite(paths.runtimeJson, JSON.stringify(info, null, 2) + "\n");
15
+ }
16
+ /**
17
+ * 하트비트 — runtime.json 의 mtime 만 현재시각으로 갱신(utimes, 내용 재작성 없음).
18
+ * 파일 부재(종료 레이스 등 ENOENT)는 무시 — 멱등. 그 외 오류는 호출부가 흡수(보조 신호).
19
+ */
20
+ export async function touchRuntime(paths) {
21
+ const now = new Date();
22
+ try {
23
+ await utimes(paths.runtimeJson, now, now);
24
+ }
25
+ catch (err) {
26
+ if (err.code !== "ENOENT")
27
+ throw err;
28
+ }
29
+ }
30
+ /** runtime.json 제거(graceful 종료). 부재(ENOENT)는 무시 — 멱등. */
31
+ export async function removeRuntime(paths) {
32
+ try {
33
+ await unlink(paths.runtimeJson);
34
+ }
35
+ catch (err) {
36
+ if (err.code !== "ENOENT")
37
+ throw err;
38
+ }
39
+ }
40
+ /** runtime.json 읽기. 부재·파싱불가·스키마 불일치 시 null(=stopped 로 취급). */
41
+ export async function readRuntime(paths) {
42
+ let text;
43
+ try {
44
+ text = await readFile(paths.runtimeJson, "utf8");
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ try {
50
+ const parsed = JSON.parse(text);
51
+ if (typeof parsed.pid === "number" && typeof parsed.sessionId === "string") {
52
+ return parsed;
53
+ }
54
+ }
55
+ catch {
56
+ // 손상된 파일 — null 로 취급(stopped). 진단은 status 가 별도 표면화하지 않는다.
57
+ }
58
+ return null;
59
+ }
60
+ /**
61
+ * pid 생존 판정. `process.kill(pid, 0)` 은 신호를 보내지 않고 존재만 확인한다.
62
+ * - 성공: 프로세스 존재 → running.
63
+ * - EPERM: 존재하나 시그널 권한 없음(다른 소유자) → 존재하므로 running.
64
+ * - ESRCH(그 외): 프로세스 없음 → dead(크래시 잔존).
65
+ */
66
+ export function isPidAlive(pid) {
67
+ try {
68
+ process.kill(pid, 0);
69
+ return true;
70
+ }
71
+ catch (err) {
72
+ return err.code === "EPERM";
73
+ }
74
+ }
75
+ /**
76
+ * runtime.json + pid 생존 + 하트비트(mtime)로 라이브니스 판정.
77
+ * - 파일 없음 → stopped, pid 없음 → dead.
78
+ * - pid 생존 + mtime 임계 초과 → stale(행). mtime 미주입이면 running(pid-only).
79
+ */
80
+ export function livenessOf(info, opts = {}) {
81
+ if (!info)
82
+ return "stopped";
83
+ if (!isPidAlive(info.pid))
84
+ return "dead";
85
+ const { mtimeMs, now = Date.now() } = opts;
86
+ if (mtimeMs !== undefined && now - mtimeMs > HEARTBEAT_STALE_MS)
87
+ return "stale";
88
+ return "running";
89
+ }
90
+ //# sourceMappingURL=runtime-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-state.js","sourceRoot":"","sources":["../../src/core/runtime-state.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAErD,yEAAyE;AACzE,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAC5C,+DAA+D;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAoB1C,6DAA6D;AAC7D,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAgB,EAAE,IAAiB;IACpE,MAAM,WAAW,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAC7E,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAgB;IACjD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;IAClE,CAAC;AACH,CAAC;AAED,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAgB;IAClD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;IAClE,CAAC;AACH,CAAC;AAED,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAgB;IAChD,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAyB,CAAC;QACxD,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;YAC3E,OAAO,MAAqB,CAAC;QAC/B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0DAA0D;IAC5D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAQ,GAA6B,CAAC,IAAI,KAAK,OAAO,CAAC;IACzD,CAAC;AACH,CAAC;AAUD;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAwB,EAAE,OAAwB,EAAE;IAC7E,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IACzC,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC;IAC3C,IAAI,OAAO,KAAK,SAAS,IAAI,GAAG,GAAG,OAAO,GAAG,kBAAkB;QAAE,OAAO,OAAO,CAAC;IAChF,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { LanePaths } from "../shared/paths.js";
2
+ import type { ControlRequest } from "../shared/envelope.js";
3
+ export interface SessionEntry {
4
+ id: string;
5
+ /** ISO — 세션 생성 시각. */
6
+ createdAt: string;
7
+ /** ISO — 마지막 대화(턴 종료) 시각. 목록 표기·회전 기준. */
8
+ lastActivityAt: string;
9
+ /** 첫 프롬프트 발췌(마스킹) — 목록에서 세션 식별용. 첫 턴에서 1회 채움. */
10
+ label?: string;
11
+ }
12
+ /** 장부 읽기 — 부재·파손은 빈 장부(보조 데이터, fail-open). */
13
+ export declare function readLedger(paths: LanePaths): Promise<SessionEntry[]>;
14
+ /** 세션 목록의 시각 표기 — 로컬 `MM-DD HH:mm`(목록 가독 우선, 정밀 시각은 장부 파일에 보존). */
15
+ export declare function formatWhen(iso: string): string;
16
+ /**
17
+ * resume 인자 → ControlRequest 해석(채널 공통). 무인자=목록(sessions), 숫자=장부 최신순
18
+ * 번호, 그 외=세션 id 직접 지정(문자셋 위반·번호 범위 밖은 sessionId 미지정 — 수신측이
19
+ * "재개할 세션 없음" 통지, fail-closed).
20
+ */
21
+ export declare function resolveResumeControl(arg: string | undefined, entries: SessionEntry[]): ControlRequest;
22
+ /** 세션 생성/복귀 시 upsert — 기존 항목이면 lastActivityAt 만 갱신. */
23
+ export declare function recordSession(paths: LanePaths, id: string): Promise<void>;
24
+ /** 턴 종료 시 마지막 대화 시각 갱신(+미기재 label 이면 발췌 기록). 항목 부재 시 생성. */
25
+ export declare function touchSession(paths: LanePaths, id: string, labelIfEmpty?: string): Promise<void>;