@aiti-ai/aiti-claude-code-agent 0.0.0-stage → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,30 @@
1
1
  {
2
2
  "name": "@aiti-ai/aiti-claude-code-agent",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.7",
4
+ "description": "Drive Claude Code CLI sessions — resume historical rows from All Sessions (agentKey: claude)",
5
+ "soul": "历史会话不是化石——点一下就活过来。",
6
+ "scripts": {
7
+ "build:client": "node -e \"const fs=require('fs');fs.mkdirSync('dist',{recursive:true});for(const f of fs.readdirSync('client/src'))if(f.endsWith('.mjs'))fs.copyFileSync('client/src/'+f,'dist/'+f)\""
8
+ },
9
+ "aitiConfigs": {
10
+ "configFiles": [
11
+ "zcode-api-key.json",
12
+ "controlled-login.json"
13
+ ]
14
+ },
15
+ "aitiExtProtocol": "1.0",
16
+ "dependencies": {
17
+ "@aiti-ai/ext-shared": "^0.1.3"
18
+ },
19
+ "license": "MIT",
20
+ "contentHash": "cdf0b8c7a747f509",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/aiti-ai/aiti-ai.git",
24
+ "directory": "extensions/aiti-claude-code-agent"
25
+ },
26
+ "homepage": "https://github.com/aiti-ai/aiti-ai#readme",
27
+ "bugs": {
28
+ "url": "https://github.com/aiti-ai/aiti-ai/issues"
29
+ }
30
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * _shared/reconcile-deps.mjs · 通知 pi-web 双实例对齐扩展共享依赖(各扩展 deploy.mjs 共用)
3
+ *
4
+ * 背景:扩展运行时依赖统一装进共享根 <webRoot>/node_modules(jiti 从扩展目录向上解析
5
+ * 自动命中;机制仿 pi 本体 npm: 包安装)。共享根的唯一写入方是 aiti-extman 的
6
+ * shared-deps reconcile——deploy.mjs 拷完文件后 POST 本通知即可,不在部署脚本里跑 npm。
7
+ *
8
+ * 用法(与 reload-instances 同款:允许失败,永不 throw、不影响 deploy 退出码):
9
+ * import { notifyReconcile } from "../../_shared/reconcile-deps.mjs";
10
+ * await notifyReconcile(); // 默认 "[deploy] " 前缀;建议在 reloadInstances() 之前调
11
+ *
12
+ * 失败无害:extman 在下一次 install/remove/reconcile-deps 时会自愈对齐;双实例各自
13
+ * reconcile 各自的 webRoot(~/.pi/web 与 ~/.pi/web-dev),互不相干。
14
+ */
15
+ import { buildInstances } from "./reload-instances.mjs";
16
+
17
+ export async function notifyReconcile(prefix = "[deploy] ") {
18
+ for (const inst of buildInstances()) {
19
+ if (inst.base === null) {
20
+ console.log(`${prefix}reconcile ${inst.name} 跳过:basePath 非法`);
21
+ continue;
22
+ }
23
+ try {
24
+ const r = await fetch(inst.base + "/api/extensions/aiti-extman/ops", {
25
+ method: "POST",
26
+ headers: { "content-type": "application/json" },
27
+ body: JSON.stringify({ action: "reconcile-deps", source: "deploy" }),
28
+ });
29
+ const body = await r.json().catch(() => null);
30
+ const err = body && body.deps && body.deps.error;
31
+ if (r.ok && body && body.ok) {
32
+ console.log(`${prefix}reconcile ${inst.name} → ✓(${Object.keys(body.deps?.deps || {}).length} 个共享依赖)`);
33
+ } else {
34
+ console.log(`${prefix}reconcile ${inst.name} → HTTP ${r.status} ⚠${err ? " " + err : "(extman 未就绪?下次安装时自动对齐)"}`);
35
+ }
36
+ } catch (e) {
37
+ const cause = e && e.cause ? String(e.cause.code || e.cause.message || e.cause) : String((e && e.message) || e);
38
+ console.log(`${prefix}reconcile ${inst.name} 不可达(${cause})— 下次安装时自动对齐`);
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * _shared/reload-instances.mjs · pi-web 双实例热重载(各扩展 deploy.mjs 共用)
3
+ *
4
+ * dev 3001 / 生产 3002;端口可经 AITI_DEV_PORT / AITI_PROD_PORT 覆盖;
5
+ * basePath 双 env(AITI_DEV_BASE_PATH / AITI_PROD_BASE_PATH)按机器设值,
6
+ * 推荐值不带斜杠(48:dev 与 空;155:系统级 wdev 与 win)——git-bash 启动
7
+ * 原生程序时会把 / 开头的 env 值做 MSYS 路径转换(/wdev → D:/apps/Git/wdev)
8
+ * 致 URL 非法;不带斜杠则不触发转换。此处自动补 /(旧 /xxx 格式仍兼容),
9
+ * 兼容不了时提示 MSYS_NO_PATHCONV=1。
10
+ * 避免「server 端代码改了、实例没 reload 导致跑旧逻辑」。
11
+ *
12
+ * 用法(静态 import 或顶层 await import 均可):
13
+ * import { reloadInstances } from "../../_shared/reload-instances.mjs";
14
+ * await reloadInstances(); // 默认 "[deploy] " 前缀
15
+ */
16
+ const BASE_PATH_RE = /^\/[A-Za-z0-9._-]*$/;
17
+
18
+ function buildInstance(name, portEnv, portDefault, pathEnv, pathDefault) {
19
+ const origin = `http://127.0.0.1:${process.env[portEnv] || portDefault}`;
20
+ const raw = process.env[pathEnv];
21
+ if (raw === undefined || raw === null || String(raw).trim() === "") return { name, base: origin + pathDefault };
22
+ // 自动补 /:wdev → /wdev(推荐无斜杠格式,MSYS 不转换);/wdev → /wdev(旧格式兼容)
23
+ const p = "/" + String(raw).trim().replace(/^\/+|\/+$/g, "");
24
+ if (BASE_PATH_RE.test(p)) return { name, base: origin + p };
25
+ return { name, base: null, raw };
26
+ }
27
+
28
+ /** 双实例清单(dev/prod 各 {name, base});reload-instances 与 reconcile-deps 等共用。 */
29
+ export function buildInstances() {
30
+ return [
31
+ buildInstance("dev", "AITI_DEV_PORT", 3001, "AITI_DEV_BASE_PATH", "/dev"),
32
+ buildInstance("prod", "AITI_PROD_PORT", 3002, "AITI_PROD_BASE_PATH", ""),
33
+ ];
34
+ }
35
+
36
+ export async function reloadInstances(prefix = "[deploy] ") {
37
+ // 批次合并(deploy-all 场景):子条目部署脚本被注入 PI_WEB_DEFER_RELOAD=1 时不各自 reload,
38
+ // 由 deploy-all 在全部条目完成后统一执行一轮(N 条目 × 2 实例 → 2 次 POST)。
39
+ // 避免批量部署时的 reload 风暴(2026-09-15 实测:65 条目逐个 reload 把 dev 实例打满)。
40
+ // 单独跑某个扩展 deploy.mjs 不带该 env → 行为不变(部署完立即 reload)。
41
+ if (process.env.PI_WEB_DEFER_RELOAD === "1") {
42
+ console.log(`${prefix}reload 已 defer(PI_WEB_DEFER_RELOAD=1,由 deploy-all 收尾统一执行)`);
43
+ return;
44
+ }
45
+ // 允许失败:网络异常/非 2xx/服务未启动/env 非法一律只打提示,永不 throw——不影响 deploy 退出码
46
+ for (const inst of buildInstances()) {
47
+ if (inst.base === null) {
48
+ const msysHint = /[\\:]/.test(inst.raw) ? "——疑似 git-bash MSYS 路径转换,用 MSYS_NO_PATHCONV=1 前缀重跑" : "";
49
+ console.log(`${prefix}reload ${inst.name} 跳过:AITI_${inst.name.toUpperCase()}_BASE_PATH="${inst.raw}" 非法(须形如 /xxx 或留空)${msysHint}`);
50
+ continue;
51
+ }
52
+ try {
53
+ const r = await fetch(inst.base + "/api/extensions/reload", { method: "POST" });
54
+ console.log(`${prefix}reload ${inst.name} → HTTP ${r.status} ${r.ok ? "✓" : "⚠ 失败(请手动 POST /api/extensions/reload)"}`);
55
+ } catch (e) {
56
+ const cause = e && e.cause ? String(e.cause.code || e.cause.message || e.cause) : String((e && e.message) || e);
57
+ console.log(`${prefix}reload ${inst.name} 不可达(${cause})— 需手动 POST /api/extensions/reload`);
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ /** 部署 aiti-claude-code-agent 到 ~/.pi/web{,-dev}/extensions/(默认双写;--prod 仅 prod、--dev 仅 dev;文件集须与部署树一致)。 */
3
+ import { cpSync, mkdirSync, existsSync } from "node:fs";
4
+ import { join, dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { notifyReconcile } from "../../_shared/reconcile-deps.mjs";
7
+
8
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
9
+ // 目标:默认双写 web + web-dev;--prod 仅 prod、--dev 仅 dev(向后兼容旧单边用法)
10
+ const prod = process.argv.includes("--prod");
11
+ const devOnly = process.argv.includes("--dev");
12
+ const home = process.env.HOME || process.env.USERPROFILE;
13
+ const bases = [
14
+ ...(prod ? [] : [join(home, ".pi", process.env.PI_WEB_DEV_DIR ? `web-${process.env.PI_WEB_DEV_DIR}` : "web-dev", "extensions")]),
15
+ ...(devOnly ? [] : [join(home, ".pi", "web", "extensions")]),
16
+ ];
17
+ for (const base of bases) {
18
+ const dest = join(base, "aiti-claude-code-agent");
19
+ mkdirSync(dest, { recursive: true });
20
+ for (const f of ["index.ts","package.json","external-governance.ts", "claude-quota.ts"]) {
21
+ if (existsSync(join(root, f))) cpSync(join(root, f), join(dest, f));
22
+ }
23
+ // 登录 UI 已统一到 aiti-agent-manager(2026-09-13):受控登录 client/弹窗不再部署
24
+ //(服务端 /login/* 与 .agent 描述符保留,manager 代理消费)。
25
+ console.log(`[deploy] aiti-claude-code-agent → ${dest}`);
26
+ }
27
+
28
+ // ── 共享依赖 reconcile 通知(依赖进 <webRoot>/node_modules;允许失败,永不 throw)──
29
+ await notifyReconcile();
30
+
31
+ // ── 双实例热重载(basePath 双 env 按机器设值)——允许失败:服务未启动/共享模块缺失仅提示,不影响 deploy 退出码 ──
32
+ try {
33
+ const { reloadInstances } = await import("../../_shared/reload-instances.mjs");
34
+ await reloadInstances();
35
+ } catch (e) {
36
+ console.log("[deploy] 自动 reload 未执行(" + String((e && e.message) || e) + ")— 需手动 POST /api/extensions/reload");
37
+ }
38
+
39
+ // ── dev-pack(方案 §3.2:源码+构建工具+AGENTS.md 直达部署目录,AI 可现场直改)——允许失败,不影响 deploy 退出码 ──
40
+ try {
41
+ const { packDevPack } = await import("../../_shared/dev-pack.mjs");
42
+ packDevPack({ extRoot: resolve(import.meta.dirname, ".."), log: (m) => console.log("[deploy] " + m) });
43
+ } catch (e) {
44
+ console.log("[deploy] dev-pack 未执行(" + String((e && e.message) || e) + ")");
45
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * claude-code turn-ack 探针(债务 D11)——stream-json 长连接「result 后 0ms 写入」是否被吞。
3
+ *
4
+ * 方法(对齐 codebuddy turn-ack 方案 §1.2 裸 CLI 复现链):
5
+ * spawn `claude -p --output-format=stream-json --input-format=stream-json
6
+ * --include-partial-messages --verbose --dangerously-skip-permissions`
7
+ * 轮 1: "Reply with exactly: ONE" → 等 result 帧
8
+ * 轮 2: result 后 **0ms** 立即写 "Reply with exactly: TWO" → 45s 等第二个 result
9
+ * 判定:第二个 result 到达且文本含 TWO → 健康(followUp 可开);
10
+ * 超时 / result 复读 ONE(无 assistant 帧) → 吞输入(同 cbc 病)。
11
+ *
12
+ * Run: node pi-web-ext/aiti-claude-code-agent/tools/turn-ack-probe.mjs [--delay <ms>]
13
+ * 输出:JSON 一行结论 { healthy, delayMs, turn2, evidence }
14
+ */
15
+
16
+ import { spawn } from "node:child_process";
17
+
18
+ const delayMs = Number(process.argv[process.argv.indexOf("--delay") + 1] ?? 0) || 0;
19
+ const bin = process.env.CLAUDE_CLI_PATH || "claude";
20
+
21
+ const child = spawn(bin, [
22
+ "-p",
23
+ "--output-format", "stream-json",
24
+ "--input-format", "stream-json",
25
+ "--include-partial-messages",
26
+ "--verbose",
27
+ "--dangerously-skip-permissions",
28
+ ], { cwd: process.cwd(), stdio: ["pipe", "pipe", "pipe"] });
29
+
30
+ const frames = [];
31
+ let buffer = "";
32
+ let turn = 0;
33
+ let resultCount = 0;
34
+ let assistantTexts = ["", ""];
35
+
36
+ child.stdout.on("data", (chunk) => {
37
+ buffer += chunk.toString("utf8");
38
+ for (;;) {
39
+ const idx = buffer.indexOf("\n");
40
+ if (idx === -1) break;
41
+ const line = buffer.slice(0, idx);
42
+ buffer = buffer.slice(idx + 1);
43
+ if (!line.trim()) continue;
44
+ let msg;
45
+ try { msg = JSON.parse(line); } catch { continue; }
46
+ const type = String(msg.type ?? "");
47
+ if (type === "assistant") {
48
+ const c = msg.message?.content ?? [];
49
+ for (const b of c) if (b.type === "text" && b.text) assistantTexts[turn] += b.text;
50
+ }
51
+ if (type === "result") {
52
+ resultCount++;
53
+ frames.push({ t: Date.now(), type, subtype: msg.subtype, result: String(msg.result ?? "").slice(0, 120) });
54
+ if (resultCount === 1) {
55
+ // result 后 0ms(可调 --delay)写入第二轮
56
+ setTimeout(() => {
57
+ child.stdin.write(JSON.stringify({
58
+ type: "user",
59
+ message: { role: "user", content: [{ type: "text", text: "Reply with exactly: TWO" }] },
60
+ }) + "\n");
61
+ turn = 1;
62
+ frames.push({ t: Date.now(), type: "PROBE_WRITE_TURN2", delayAfterResultMs: delayMs });
63
+ }, delayMs);
64
+ } else if (resultCount === 2) {
65
+ finish(true);
66
+ }
67
+ }
68
+ if (type === "system" && msg.subtype === "init") frames.push({ t: Date.now(), type: "init", session_id: msg.session_id });
69
+ }
70
+ });
71
+
72
+ child.stderr.on("data", (c) => process.stderr.write(`[stderr] ${c}`));
73
+ child.on("exit", (code) => {
74
+ if (resultCount < 2) finish(false, `process exited (code=${code}) before turn2 result`);
75
+ });
76
+
77
+ const timer = setTimeout(() => finish(false, "timeout 120s waiting for turn2 result"), 120_000);
78
+
79
+ function finish(turned, why) {
80
+ clearTimeout(timer);
81
+ try { child.kill("SIGKILL"); } catch { /* already dead */ }
82
+ const t2 = (assistantTexts[1] || "").trim();
83
+ const r2 = frames.filter((f) => f.type === "result")[1];
84
+ const replayed = r2 && !t2 && String(r2.result ?? "").includes("ONE");
85
+ const out = {
86
+ healthy: Boolean(turned && t2.includes("TWO")),
87
+ delayMs,
88
+ turn2AssistantText: t2.slice(0, 200) || null,
89
+ turn2Result: r2 ? String(r2.result ?? "").slice(0, 200) : null,
90
+ replaySuspect: replayed || undefined,
91
+ why: why || undefined,
92
+ frames: frames.map((f) => ({ ...f, t: undefined })),
93
+ };
94
+ console.log(JSON.stringify(out, null, 2));
95
+ process.exit(0);
96
+ }
97
+
98
+ // 启动:stream-json 输入模式下 CLI 懒初始化——init 帧在首条消息后才出现(扩展实测),
99
+ // 所以不等 init,spawn 后直接写第一轮。
100
+ setTimeout(() => {
101
+ child.stdin.write(JSON.stringify({
102
+ type: "user",
103
+ message: { role: "user", content: [{ type: "text", text: "Reply with exactly: ONE" }] },
104
+ }) + "\n");
105
+ turn = 0;
106
+ }, 500);
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.