@qlingzzy/qling 1.2.0 → 1.2.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## v1.2.2 (2026-07-15)
6
+
7
+ - Fix CRLF skill frontmatter parsing on Windows CI.
8
+ - Normalize Windows and POSIX target paths consistently in recovery fingerprints.
9
+ - Persist session task state atomically to prevent partial JSON reads.
10
+ - Install locked production dependencies in Windows portable builds and verify the staged launcher with `--version` and `doctor` before packaging.
11
+ - Provision Playwright Chromium for the required Linux dashboard smoke test.
12
+
13
+ ## v1.2.1 (2026-07-15)
14
+
15
+ ### Official distribution + strict layers + self-contained portable
16
+
17
+ - **分层**:清零反向依赖;`ci:check` 启用 `dep:layers --strict`;`installSlashPorts` + `DurableSessionSupervisor` 上提 `agent/`。
18
+ - **便携包**:`build:portable-win` 内嵌 Node 运行时(`runtime/node.exe`),无需本机安装 Node。
19
+ - **Scoop/winget**:manifest 指向 GitHub Release portable zip;多文件 winget 清单;官方 PR 按审阅修正 64bit 结构与 hash 格式。
20
+ - **公共 Scoop bucket**:https://github.com/Zzy-min/scoop-qling
21
+
5
22
  ## v1.2.0 (2026-07-14)
6
23
 
7
24
  ### Phase 8.0 — Sprint 5 分层治理 + 分发资产
package/README.en.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Node](https://img.shields.io/badge/Node-%E2%89%A518-339933?logo=node.js&logoColor=white)](#requirements)
4
4
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
- [![Version](https://img.shields.io/badge/version-1.2.0-orange.svg)](CHANGELOG.md)
5
+ [![Version](https://img.shields.io/badge/version-1.2.2-orange.svg)](CHANGELOG.md)
6
6
  [![Release](https://img.shields.io/github/v/release/Zzy-min/qling?display_name=tag&sort=semver)](https://github.com/Zzy-min/qling/releases)
7
7
 
8
8
  **Qling** is a **local-first** AI Agent CLI workbench. It keeps sessions, tools, skills, permissions, diagnostics, long-running missions, and recovery controls in one auditable terminal console.
@@ -178,14 +178,14 @@ QLING_EVAL_LLM=1 npm run eval:llm
178
178
  | Channel | Status |
179
179
  |---------|--------|
180
180
  | Source + bootstrap | Ready |
181
- | npm `@qlingzzy/qling` | **Published** `1.2.0` — install with `--registry https://registry.npmjs.org/` if your default is a mirror |
181
+ | npm `@qlingzzy/qling` | **Published** `1.2.2` — install with `--registry https://registry.npmjs.org/` if your default is a mirror |
182
182
  | Scoop | Draft + real tarball SHA256; not in official buckets |
183
183
  | winget | Draft YAML only (needs portable zip + SHA256) |
184
184
  | Portable zip / single binary | Not yet |
185
185
 
186
186
  ## Version
187
187
 
188
- - Package: `1.2.0` (see latest tag on GitHub for hotfixes)
188
+ - Package: `1.2.2` (see latest tag on GitHub for hotfixes)
189
189
  - Changelog: [CHANGELOG.md](CHANGELOG.md)
190
190
  - Specs / plans: `docs/superpowers/`
191
191
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Node](https://img.shields.io/badge/Node-%E2%89%A518-339933?logo=node.js&logoColor=white)](#环境要求)
4
4
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
- [![Version](https://img.shields.io/badge/version-1.2.0-orange.svg)](CHANGELOG.md)
5
+ [![Version](https://img.shields.io/badge/version-1.2.2-orange.svg)](CHANGELOG.md)
6
6
 
7
7
  [English](README.en.md) · [安装指南](docs/install.md) · [CHANGELOG](CHANGELOG.md)
8
8
 
@@ -63,7 +63,7 @@ qling
63
63
  # 或从 GitHub 直接全局安装
64
64
  npm install -g github:Zzy-min/qling
65
65
 
66
- # npm 官方包(已发布 1.2.0;作用域名,CLI 仍是 qling)
66
+ # npm 官方包(已发布 1.2.2;作用域名,CLI 仍是 qling)
67
67
  # 若默认 registry 是国内镜像,请显式指定官方源
68
68
  npm install -g @qlingzzy/qling --registry https://registry.npmjs.org/
69
69
  ```
@@ -402,7 +402,7 @@ npm audit --registry=https://registry.npmjs.org --audit-level=high
402
402
 
403
403
  ## 版本与变更
404
404
 
405
- - 当前版本:`1.2.0`
405
+ - 当前版本:`1.2.2`
406
406
 
407
407
  - 完整变更历史:见 [CHANGELOG.md](CHANGELOG.md)
408
408
  - 安装与分发:见 [docs/install.md](docs/install.md)、[packaging/](packaging/)
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-session-supervisor.d.ts","sourceRoot":"","sources":["../../src/agent/durable-session-supervisor.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA4B;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,+BAA+B;IAMpD,KAAK,IAAI,IAAI;IAQP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAOb,IAAI;YAyBJ,iBAAiB;YAcjB,iBAAiB;YAiBjB,sBAAsB;YAiCtB,cAAc;YAoCd,iBAAiB;YAyBjB,WAAW;CA8B1B"}
@@ -4,9 +4,9 @@ import * as path from "path";
4
4
  import { AgentLoop } from "../agent-loop.js";
5
5
  import { applyConfigToProcessEnv, loadQlingConfig } from "../config.js";
6
6
  import { buildToolRegistry } from "../tools/index.js";
7
- import { SessionGoalController } from "./goal-controller.js";
8
- import { SessionGoalManager } from "./session-goal-manager.js";
9
- import { SessionScheduler } from "./session-scheduler.js";
7
+ import { SessionGoalController } from "../session/goal-controller.js";
8
+ import { SessionGoalManager } from "../session/session-goal-manager.js";
9
+ import { SessionScheduler } from "../session/session-scheduler.js";
10
10
  export class DurableSessionSupervisor {
11
11
  stateDir;
12
12
  tickIntervalMs;
package/dist/daemon.js CHANGED
@@ -13,7 +13,7 @@ import dotenv from "dotenv";
13
13
  import { SessionScheduler } from "./session/session-scheduler.js";
14
14
  import { SessionGoalController } from "./session/goal-controller.js";
15
15
  import { SessionGoalManager } from "./session/session-goal-manager.js";
16
- import { DurableSessionSupervisor } from "./session/durable-session-supervisor.js";
16
+ import { DurableSessionSupervisor } from "./agent/durable-session-supervisor.js";
17
17
  import { formatDaemonVersion } from "./package-version.js";
18
18
  const HOME_DIR = os.homedir();
19
19
  const DEFAULT_STATE_DIR = path.join(HOME_DIR, ".qling");
@@ -1 +1 @@
1
- {"version":3,"file":"failure-classifier.d.ts","sourceRoot":"","sources":["../../src/execution/failure-classifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEzE,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,cAAmB,GAAG,qBAAqB,CAgCnG;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,aAAa,CAAC,GAAG,MAAM,CAUpG"}
1
+ {"version":3,"file":"failure-classifier.d.ts","sourceRoot":"","sources":["../../src/execution/failure-classifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEzE,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAqBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,cAAmB,GAAG,qBAAqB,CAgCnG;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,aAAa,CAAC,GAAG,MAAM,CAUpG"}
@@ -13,6 +13,9 @@ function normalizeMessage(value) {
13
13
  .replace(/\s+/g, " ")
14
14
  .trim();
15
15
  }
16
+ function portableBasename(value) {
17
+ return value.includes("\\") ? path.win32.basename(value) : path.posix.basename(value);
18
+ }
16
19
  export function classifyFailure(error, context = {}) {
17
20
  const message = errorMessage(error);
18
21
  const lower = message.toLowerCase();
@@ -54,7 +57,7 @@ export function classifyFailure(error, context = {}) {
54
57
  return { ...classification, fingerprint: createFailureFingerprint(classification) };
55
58
  }
56
59
  export function createFailureFingerprint(failure) {
57
- const target = failure.targetPath ? path.basename(failure.targetPath).toLowerCase() : "";
60
+ const target = failure.targetPath ? portableBasename(failure.targetPath).toLowerCase() : "";
58
61
  const stable = [
59
62
  failure.category,
60
63
  failure.tool ?? "",
package/dist/index.js CHANGED
@@ -15,6 +15,8 @@ import axios from "axios";
15
15
  import { AgentLoop } from "./agent-loop.js";
16
16
  import { Repl } from "./repl.js";
17
17
  import { StreamingREPL } from "./tui/streaming-repl.js";
18
+ import { installSlashPorts } from "./slash-ports.js";
19
+ import { handleSlashCommand, findSlashCompletion, formatSlashCommandPanel, formatGroupedSlashPanel, } from "./commands/index.js";
18
20
  import { buildHelpText, formatCliError, parseCliArgs } from "./cli/startup-contract.js";
19
21
  import { runBootstrap } from "./cli/bootstrap.js";
20
22
  import { applyConfigToProcessEnv, loadQlingConfig } from "./config.js";
@@ -1111,6 +1113,14 @@ async function main() {
1111
1113
  process.exit(1);
1112
1114
  }
1113
1115
  }
1116
+ installSlashPorts({
1117
+ handleSlashCommand,
1118
+ ui: {
1119
+ findSlashCompletion,
1120
+ formatSlashCommandPanel,
1121
+ formatGroupedSlashPanel,
1122
+ },
1123
+ });
1114
1124
  if (decision.mode === "chat") {
1115
1125
  const repl = new StreamingREPL(agent, {
1116
1126
  continueSession: decision.global.continueSession,
package/dist/repl.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AgentLoop } from "./agent-loop.js";
2
+ import { type SlashCommandHandler } from "./slash-ports.js";
2
3
  export type ReplInputResult = "continue" | "exit";
3
4
  export declare class Repl {
4
5
  private agent;
@@ -10,9 +11,11 @@ export declare class Repl {
10
11
  private controllersSessionId;
11
12
  private immediatePrompt;
12
13
  private runningScheduledTask;
14
+ private readonly handleSlashCommandOverride?;
13
15
  constructor(agent?: AgentLoop, options?: {
14
16
  resumeSession?: string;
15
17
  continueSession?: boolean;
18
+ handleSlashCommand?: SlashCommandHandler;
16
19
  });
17
20
  start(): Promise<void>;
18
21
  private loop;
@@ -1 +1 @@
1
- {"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAQ5C,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,MAAM,CAAC;AAMlD,qBAAa,IAAI;IACf,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,EAAE,CAAqB;IAC/B,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAC1C,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,oBAAoB,CAAS;gBAGnC,KAAK,CAAC,EAAE,SAAS,EACjB,OAAO,GAAE;QACP,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,CAAC,EAAE,OAAO,CAAC;KACtB;IAWF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAqBd,IAAI;IAeZ,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;YA+EhD,aAAa;YA8Bb,6BAA6B;YAqC7B,gCAAgC;IAoB9C,OAAO,CAAC,kBAAkB;YAkBZ,iCAAiC;YAwBjC,sBAAsB;IAepC,OAAO,CAAC,+BAA+B;YAezB,uBAAuB;IAWrC,OAAO,CAAC,oBAAoB;YASd,wBAAwB;IAatC,OAAO,CAAC,iBAAiB;YAKX,6BAA6B;YAwC7B,4BAA4B;YAS5B,aAAa;YAWb,uBAAuB;YAMvB,sBAAsB;IAcpC,OAAO,CAAC,0BAA0B;IAIlC,OAAO,CAAC,2BAA2B;IAInC,OAAO,CAAC,8BAA8B;YAYxB,6BAA6B;CAS5C"}
1
+ {"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,kBAAkB,CAAC;AAM1B,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,MAAM,CAAC;AAMlD,qBAAa,IAAI;IACf,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,EAAE,CAAqB;IAC/B,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAC1C,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAC,CAAsB;gBAGhE,KAAK,CAAC,EAAE,SAAS,EACjB,OAAO,GAAE;QACP,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,kBAAkB,CAAC,EAAE,mBAAmB,CAAC;KACrC;IAYF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAqBd,IAAI;IAeZ,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;YAgFhD,aAAa;YA8Bb,6BAA6B;YAqC7B,gCAAgC;IAoB9C,OAAO,CAAC,kBAAkB;YAkBZ,iCAAiC;YAwBjC,sBAAsB;IAepC,OAAO,CAAC,+BAA+B;YAezB,uBAAuB;IAWrC,OAAO,CAAC,oBAAoB;YASd,wBAAwB;IAatC,OAAO,CAAC,iBAAiB;YAKX,6BAA6B;YAwC7B,4BAA4B;YAS5B,aAAa;YAWb,uBAAuB;YAMvB,sBAAsB;IAcpC,OAAO,CAAC,0BAA0B;IAIlC,OAAO,CAAC,2BAA2B;IAInC,OAAO,CAAC,8BAA8B;YAYxB,6BAA6B;CAS5C"}
package/dist/repl.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // ============================================================
5
5
  import * as readline from "readline";
6
6
  import { AgentLoop } from "./agent-loop.js";
7
- import { handleSlashCommand } from "./commands/index.js";
7
+ import { resolveSlashHandler, } from "./slash-ports.js";
8
8
  import { SessionGoalController } from "./session/goal-controller.js";
9
9
  import { SessionGoalManager } from "./session/session-goal-manager.js";
10
10
  import { SessionScheduler } from "./session/session-scheduler.js";
@@ -18,10 +18,12 @@ export class Repl {
18
18
  controllersSessionId = null;
19
19
  immediatePrompt = null;
20
20
  runningScheduledTask = false;
21
+ handleSlashCommandOverride;
21
22
  constructor(agent, options = {}) {
22
23
  this.agent = agent ?? new AgentLoop();
23
24
  this.startupResumeTarget = options.resumeSession;
24
25
  this.startupContinue = options.continueSession ?? false;
26
+ this.handleSlashCommandOverride = options.handleSlashCommand;
25
27
  this.rl = readline.createInterface({
26
28
  input: process.stdin,
27
29
  output: process.stdout,
@@ -114,6 +116,7 @@ export class Repl {
114
116
  if (trimmedInput.startsWith("/")) {
115
117
  await this.ensureLocalSessionControllers();
116
118
  this.immediatePrompt = null;
119
+ const handleSlashCommand = await resolveSlashHandler(this.handleSlashCommandOverride);
117
120
  const handledSlashCommand = await handleSlashCommand(trimmedInput, this.createSlashContext());
118
121
  if (handledSlashCommand) {
119
122
  if (this.immediatePrompt) {
@@ -1 +1 @@
1
- {"version":3,"file":"session-scheduler.d.ts","sourceRoot":"","sources":["../../src/session/session-scheduler.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AACrC,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,CAAC;AAClD,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;AACvG,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IACxD,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAA+C;gBAEhD,OAAO,EAAE,uBAAuB;IAStC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,KAAK,IAAI,IAAI;IAOP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAIvB,cAAc,CAAC,OAAO,EAAE;QAC5B,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,eAAe,CAAC;QACtB,MAAM,CAAC,EAAE,iBAAiB,CAAC;KAC5B,GAAG,OAAO,CAAC,WAAW,CAAC;IAqBlB,SAAS,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAOnC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAU5C,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAiBjC,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC;IA4E9C,OAAO,CAAC,cAAc;YAQR,SAAS;YAaT,SAAS;CAMxB"}
1
+ {"version":3,"file":"session-scheduler.d.ts","sourceRoot":"","sources":["../../src/session/session-scheduler.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AACrC,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,CAAC;AAClD,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;AACvG,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IACxD,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAA+C;gBAEhD,OAAO,EAAE,uBAAuB;IAStC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,KAAK,IAAI,IAAI;IAOP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAIvB,cAAc,CAAC,OAAO,EAAE;QAC5B,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,eAAe,CAAC;QACtB,MAAM,CAAC,EAAE,iBAAiB,CAAC;KAC5B,GAAG,OAAO,CAAC,WAAW,CAAC;IAqBlB,SAAS,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAOnC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAU5C,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAiBjC,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC;IA4E9C,OAAO,CAAC,cAAc;YAQR,SAAS;YAaT,SAAS;CAYxB"}
@@ -1,6 +1,7 @@
1
1
  import * as fs from "fs/promises";
2
2
  import { existsSync } from "fs";
3
3
  import * as path from "path";
4
+ import { randomUUID } from "node:crypto";
4
5
  function cloneTask(task) {
5
6
  return { ...task };
6
7
  }
@@ -190,7 +191,14 @@ export class SessionScheduler {
190
191
  const payload = Array.from(this.tasks.values())
191
192
  .sort((a, b) => a.createdAt - b.createdAt)
192
193
  .map(cloneTask);
193
- await fs.writeFile(this.stateFile, JSON.stringify(payload, null, 2), "utf-8");
194
+ const tempFile = `${this.stateFile}.${process.pid}.${randomUUID()}.tmp`;
195
+ await fs.writeFile(tempFile, JSON.stringify(payload, null, 2), "utf-8");
196
+ try {
197
+ await fs.rename(tempFile, this.stateFile);
198
+ }
199
+ finally {
200
+ await fs.rm(tempFile, { force: true }).catch(() => undefined);
201
+ }
194
202
  }
195
203
  }
196
204
  function sanitizeError(error) {
@@ -92,7 +92,7 @@ export function parseFrontmatter(raw, filePath) {
92
92
  triggers: [],
93
93
  path: filePath,
94
94
  };
95
- const lines = raw.split("\n");
95
+ const lines = raw.replace(/\r\n?/g, "\n").split("\n");
96
96
  const firstIdx = lines.findIndex((l) => l.trim() === "---");
97
97
  if (firstIdx < 0)
98
98
  return fallback;
@@ -0,0 +1,24 @@
1
+ import type { SlashCommandContext } from "./slash-context.js";
2
+ export type SlashCommandHandler = (input: string, context: SlashCommandContext | Record<string, any>) => Promise<boolean>;
3
+ export interface SlashCatalogItem {
4
+ name: string;
5
+ description?: string;
6
+ aliases?: string[];
7
+ }
8
+ export type FindSlashCompletion = (prefix: string, limit?: number) => SlashCatalogItem[];
9
+ export type FormatSlashPanel = (prefix: string, selectedIndex?: number, width?: number, limit?: number) => string[];
10
+ export type FormatGroupedSlashPanel = (width?: number) => string[];
11
+ export interface SlashUiPorts {
12
+ findSlashCompletion: FindSlashCompletion;
13
+ formatSlashCommandPanel: FormatSlashPanel;
14
+ formatGroupedSlashPanel: FormatGroupedSlashPanel;
15
+ }
16
+ export interface SlashRuntimePorts {
17
+ handleSlashCommand: SlashCommandHandler;
18
+ ui?: SlashUiPorts;
19
+ }
20
+ export declare function installSlashPorts(ports: SlashRuntimePorts): void;
21
+ export declare function getInstalledSlashPorts(): SlashRuntimePorts | null;
22
+ export declare function resolveSlashHandler(override?: SlashCommandHandler): Promise<SlashCommandHandler>;
23
+ export declare function resolveSlashUiPorts(override?: SlashUiPorts): Promise<SlashUiPorts>;
24
+ //# sourceMappingURL=slash-ports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slash-ports.d.ts","sourceRoot":"","sources":["../src/slash-ports.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,MAAM,MAAM,mBAAmB,GAAG,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAC/C,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,gBAAgB,EAAE,CAAC;AACzF,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;AACpH,MAAM,MAAM,uBAAuB,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;AAEnE,MAAM,WAAW,YAAY;IAC3B,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,uBAAuB,EAAE,gBAAgB,CAAC;IAC1C,uBAAuB,EAAE,uBAAuB,CAAC;CAClD;AAED,MAAM,WAAW,iBAAiB;IAChC,kBAAkB,EAAE,mBAAmB,CAAC;IACxC,EAAE,CAAC,EAAE,YAAY,CAAC;CACnB;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAEhE;AAED,wBAAgB,sBAAsB,IAAI,iBAAiB,GAAG,IAAI,CAEjE;AAED,wBAAsB,mBAAmB,CACvC,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CAK9B;AAED,wBAAsB,mBAAmB,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CASxF"}
@@ -0,0 +1,31 @@
1
+ // ============================================================
2
+ // Slash ports — agent-runtime / presentation 依赖的抽象,避免直接 import cli/commands
3
+ // ============================================================
4
+ /** 由 cli 入口注入;未注入时 runtime 使用动态 import 回退(不形成静态分层边) */
5
+ let installedPorts = null;
6
+ export function installSlashPorts(ports) {
7
+ installedPorts = ports;
8
+ }
9
+ export function getInstalledSlashPorts() {
10
+ return installedPorts;
11
+ }
12
+ export async function resolveSlashHandler(override) {
13
+ if (override)
14
+ return override;
15
+ if (installedPorts?.handleSlashCommand)
16
+ return installedPorts.handleSlashCommand;
17
+ const mod = await import("./commands/index.js");
18
+ return mod.handleSlashCommand;
19
+ }
20
+ export async function resolveSlashUiPorts(override) {
21
+ if (override)
22
+ return override;
23
+ if (installedPorts?.ui)
24
+ return installedPorts.ui;
25
+ const mod = await import("./commands/index.js");
26
+ return {
27
+ findSlashCompletion: mod.findSlashCompletion,
28
+ formatSlashCommandPanel: mod.formatSlashCommandPanel,
29
+ formatGroupedSlashPanel: mod.formatGroupedSlashPanel,
30
+ };
31
+ }
@@ -1,4 +1,5 @@
1
1
  import { AgentLoop } from "../agent-loop.js";
2
+ import { type SlashCommandHandler, type SlashUiPorts } from "../slash-ports.js";
2
3
  export declare class StreamingREPL {
3
4
  private ui;
4
5
  private agent;
@@ -13,9 +14,12 @@ export declare class StreamingREPL {
13
14
  private closed;
14
15
  private onClose;
15
16
  private lastExecutionStatus;
17
+ private readonly handleSlashCommandOverride?;
16
18
  constructor(agent?: AgentLoop, options?: {
17
19
  resumeSession?: string;
18
20
  continueSession?: boolean;
21
+ handleSlashCommand?: SlashCommandHandler;
22
+ slashUi?: SlashUiPorts;
19
23
  });
20
24
  start(): Promise<void>;
21
25
  stop(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"streaming-repl.d.ts","sourceRoot":"","sources":["../../src/tui/streaming-repl.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAa7C,qBAAa,aAAa;IACxB,OAAO,CAAC,EAAE,CAAW;IACrB,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAC1C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmB;IAC9C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,mBAAmB,CAAM;gBAG/B,KAAK,CAAC,EAAE,SAAS,EACjB,OAAO,GAAE;QACP,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,CAAC,EAAE,OAAO,CAAC;KACtB;IAyBF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAIb,KAAK;IAkBnB,OAAO,CAAC,eAAe;IA8DvB,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,kBAAkB;YAqDZ,gBAAgB;YAKhB,iBAAiB;YAoBjB,qBAAqB;YAQrB,uBAAuB;YAQvB,aAAa;YAsEb,eAAe;YASf,2BAA2B;IA4BzC,OAAO,CAAC,0BAA0B;YAkCpB,qBAAqB;IAgCnC,OAAO,CAAC,kBAAkB;YAWZ,6BAA6B;YAU7B,yBAAyB;YA2BzB,aAAa;CAsB5B"}
1
+ {"version":3,"file":"streaming-repl.d.ts","sourceRoot":"","sources":["../../src/tui/streaming-repl.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI7C,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAS3B,qBAAa,aAAa;IACxB,OAAO,CAAC,EAAE,CAAW;IACrB,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAC1C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmB;IAC9C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,mBAAmB,CAAM;IACjC,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAC,CAAsB;gBAGhE,KAAK,CAAC,EAAE,SAAS,EACjB,OAAO,GAAE;QACP,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,kBAAkB,CAAC,EAAE,mBAAmB,CAAC;QACzC,OAAO,CAAC,EAAE,YAAY,CAAC;KACnB;IA0BF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAIb,KAAK;IAkBnB,OAAO,CAAC,eAAe;IA8DvB,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,kBAAkB;YAqDZ,gBAAgB;YAKhB,iBAAiB;YAoBjB,qBAAqB;YAQrB,uBAAuB;YAQvB,aAAa;YAsEb,eAAe;YASf,2BAA2B;IA4BzC,OAAO,CAAC,0BAA0B;YAkCpB,qBAAqB;IAiCnC,OAAO,CAAC,kBAAkB;YAWZ,6BAA6B;YAU7B,yBAAyB;YA2BzB,aAAa;CAsB5B"}
@@ -10,8 +10,8 @@
10
10
  // ============================================================
11
11
  import { AgentLoop } from "../agent-loop.js";
12
12
  import { StreamUI } from "./streaming-tui.js";
13
- import { handleSlashCommand } from "../commands/index.js";
14
13
  import { SessionScheduler } from "../session/session-scheduler.js";
14
+ import { resolveSlashHandler, } from "../slash-ports.js";
15
15
  import { SessionGoalManager } from "../session/session-goal-manager.js";
16
16
  import { SessionGoalController } from "../session/goal-controller.js";
17
17
  import { DaemonSessionApi } from "../session/daemon-session-api.js";
@@ -32,14 +32,16 @@ export class StreamingREPL {
32
32
  closed = false;
33
33
  onClose = null;
34
34
  lastExecutionStatus = "";
35
+ handleSlashCommandOverride;
35
36
  constructor(agent, options = {}) {
36
37
  this.agent = agent ?? new AgentLoop();
37
38
  const model = this.agent.getModel();
38
39
  const toolsCount = this.agent.getToolCount();
39
- this.ui = new StreamUI(model, toolsCount);
40
+ this.ui = new StreamUI(model, toolsCount, { slashUi: options.slashUi });
40
41
  this.daemonSessionApi = new DaemonSessionApi();
41
42
  this.startupResumeTarget = options.resumeSession;
42
43
  this.startupContinue = options.continueSession ?? false;
44
+ this.handleSlashCommandOverride = options.handleSlashCommand;
43
45
  this.inputQueue = new SerialInputQueue({
44
46
  maxPending: 20,
45
47
  onQueued: ({ pendingCount }) => {
@@ -402,6 +404,7 @@ export class StreamingREPL {
402
404
  }
403
405
  // v0.4 Slash Commands
404
406
  this.immediatePrompt = null;
407
+ const handleSlashCommand = await resolveSlashHandler(this.handleSlashCommandOverride);
405
408
  const handled = await handleSlashCommand(input, this.createSlashContext());
406
409
  if (handled) {
407
410
  // plan/model/permissions 等变更后刷新 chrome + statusline
@@ -1,3 +1,4 @@
1
+ import { type SlashUiPorts } from "../slash-ports.js";
1
2
  import type { RecoveryState } from "../execution/types.js";
2
3
  export type AgentEvent = {
3
4
  type: "tool_start";
@@ -73,8 +74,11 @@ export declare class StreamUI {
73
74
  private recoveryState;
74
75
  private readonly now;
75
76
  private readonly doubleCtrlCExitWindowMs;
77
+ private slashUi;
78
+ private readonly slashUiPromise;
76
79
  constructor(model?: string, tools?: number, options?: {
77
80
  now?: () => number;
81
+ slashUi?: SlashUiPorts;
78
82
  });
79
83
  start(): void;
80
84
  stop(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"streaming-tui.d.ts","sourceRoot":"","sources":["../../src/tui/streaming-tui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"streaming-tui.d.ts","sourceRoot":"","sources":["../../src/tui/streaming-tui.ts"],"names":[],"mappings":"AAmBA,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAe3B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAgE3D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC3F;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACxF;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAIzC,qBAAa,QAAQ;IACnB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAQb;IACP,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,aAAa,CAAiD;IACtE,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,WAAW,CAA0C;IAC7D,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,aAAa,CAAW;IAChC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,6BAA6B,CAAmC;IACxE,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,yBAAyB,CAAK;IACtC,OAAO,CAAC,wBAAwB,CAAK;IACrC,OAAO,CAAC,sBAAsB,CAAK;IACnC,OAAO,CAAC,4BAA4B,CAAK;IACzC,OAAO,CAAC,iBAAiB,CAAmC;IAC5D,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IACjD,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwB;gBAGrD,KAAK,GAAE,MAAwB,EAC/B,KAAK,GAAE,MAAU,EACjB,OAAO,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,YAAY,CAAA;KAAO;IAgB9D,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;IAkBZ,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIjD,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIxC,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAI5C,eAAe,CAAC,MAAM,EAAE;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,GAAG,IAAI;IAIR,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,IAAI;IAKnD,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAOlC,OAAO,CAAC,kBAAkB;IAa1B,sBAAsB,IAAI,OAAO;IAIjC,uBAAuB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAIhD,0BAA0B,IAAI,OAAO;IAKrC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAInC,aAAa,CAAC,KAAK,GAAE,MAAgB,EAAE,UAAU,GAAE,MAAe,GAAG,IAAI;IAYzE,YAAY,IAAI,IAAI;IAQpB,OAAO,CAAC,WAAW;IAiCnB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,oBAAoB;IA+C5B,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,8BAA8B;IAMtC,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,UAAU;IA4BlB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,sBAAsB;IAI9B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,eAAe;IA4EvB,OAAO,CAAC,iCAAiC;IAuBzC,OAAO,CAAC,uBAAuB;IAK/B,OAAO,CAAC,uBAAuB;IAI/B,OAAO,CAAC,qBAAqB;IAmB7B,OAAO,CAAC,4BAA4B;IAepC,UAAU,IAAI,IAAI;IASlB,iCAAiC;IACjC,uBAAuB,IAAI,OAAO;IAMlC,OAAO,CAAC,UAAU;IAyLlB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,SAAS;IAiBjB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,WAAW;IA6BnB,OAAO,CAAC,WAAW;IAwBnB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,eAAe;IAqBvB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAMpD,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAW1F,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAYvF,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAUlC,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIzC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMtE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAOhC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQnC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAOtE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAwB/B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI/B,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAI3C,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;CAIrC"}
@@ -15,7 +15,7 @@
15
15
  // appendFinal / appendError / appendState / appendDone
16
16
  // ============================================================
17
17
  import { default as stringWidth } from "string-width";
18
- import { findSlashCompletion, formatSlashCommandPanel, formatGroupedSlashPanel } from "../commands/index.js";
18
+ import { resolveSlashUiPorts, } from "../slash-ports.js";
19
19
  import { InputBuffer } from "./input-buffer.js";
20
20
  import { formatProgressPulse } from "./progress.js";
21
21
  import { formatWelcomeGuide, formatRoleHeader, formatResultHighlight, formatToolOutputCard, formatToolTimelineRow, formatTopBar, padVisible, truncateVisible, } from "./shell.js";
@@ -104,13 +104,26 @@ export class StreamUI {
104
104
  recoveryState = null;
105
105
  now;
106
106
  doubleCtrlCExitWindowMs = 2_000;
107
+ slashUi = null;
108
+ slashUiPromise;
107
109
  constructor(model = "deepseek-chat", tools = 0, options = {}) {
108
110
  this.model = model;
109
111
  this.tools = tools;
110
112
  this.now = options.now ?? (() => Date.now());
113
+ if (options.slashUi) {
114
+ this.slashUi = options.slashUi;
115
+ this.slashUiPromise = Promise.resolve(options.slashUi);
116
+ }
117
+ else {
118
+ this.slashUiPromise = resolveSlashUiPorts().then((ports) => {
119
+ this.slashUi = ports;
120
+ return ports;
121
+ });
122
+ }
111
123
  }
112
124
  start() {
113
125
  this.running = true;
126
+ void this.slashUiPromise;
114
127
  this.printHeader();
115
128
  this.printInputBar();
116
129
  this.setupInput();
@@ -486,15 +499,20 @@ export class StreamUI {
486
499
  formatCurrentSlashCompletionHints() {
487
500
  if (!this.isSlashCompletionActive(this.input.value))
488
501
  return [];
489
- const matches = findSlashCompletion(this.input.value, 8);
502
+ const ports = this.slashUi;
503
+ if (!ports) {
504
+ void this.slashUiPromise;
505
+ return [];
506
+ }
507
+ const matches = ports.findSlashCompletion(this.input.value, 8);
490
508
  if (matches.length > 0 && this.slashCompletionSelectedIndex >= matches.length) {
491
509
  this.slashCompletionSelectedIndex = 0;
492
510
  }
493
511
  const inputVal = this.input.value || "";
494
512
  if (inputVal === "/" || inputVal === "") {
495
- return formatGroupedSlashPanel(process.stdout.columns || 80);
513
+ return ports.formatGroupedSlashPanel(process.stdout.columns || 80);
496
514
  }
497
- return formatSlashCommandPanel(inputVal, this.slashCompletionSelectedIndex, process.stdout.columns || 80, 8);
515
+ return ports.formatSlashCommandPanel(inputVal, this.slashCompletionSelectedIndex, process.stdout.columns || 80, 8);
498
516
  }
499
517
  isSlashCompletionPrefix(value) {
500
518
  const text = value.trim();
@@ -506,7 +524,12 @@ export class StreamUI {
506
524
  acceptSlashCompletion() {
507
525
  if (!this.isSlashCompletionPrefix(this.input.value))
508
526
  return false;
509
- const matches = findSlashCompletion(this.input.value, 8);
527
+ const ports = this.slashUi;
528
+ if (!ports) {
529
+ void this.slashUiPromise;
530
+ return false;
531
+ }
532
+ const matches = ports.findSlashCompletion(this.input.value, 8);
510
533
  const completion = matches[this.slashCompletionSelectedIndex] ?? matches[0];
511
534
  if (!completion)
512
535
  return false;
@@ -521,7 +544,12 @@ export class StreamUI {
521
544
  moveSlashCompletionSelection(delta) {
522
545
  if (!this.isSlashCompletionPrefix(this.input.value))
523
546
  return false;
524
- const matches = findSlashCompletion(this.input.value, 8);
547
+ const ports = this.slashUi;
548
+ if (!ports) {
549
+ void this.slashUiPromise;
550
+ return false;
551
+ }
552
+ const matches = ports.findSlashCompletion(this.input.value, 8);
525
553
  if (matches.length <= 1)
526
554
  return false;
527
555
  this.slashCompletionSelectedIndex =
package/docs/install.md CHANGED
@@ -89,13 +89,19 @@ qling
89
89
  ### D1. 本仓库自建 bucket(推荐试用)
90
90
 
91
91
  ```powershell
92
- cd path\to\qling
93
- scoop bucket add qling "$PWD\packaging\scoop-bucket"
94
- scoop install qling/qling
92
+ # 公共 bucket(推荐)
93
+ scoop bucket add qling https://github.com/Zzy-min/scoop-qling
94
+ scoop install qling
95
+
96
+ # 或从本仓库本地路径
97
+ # scoop bucket add qling "$PWD\packaging\scoop-bucket"
98
+ # scoop install qling/qling
99
+
95
100
  qling --version
96
101
  ```
97
102
 
98
- 说明见 `packaging/scoop-bucket/README.md`。清单含真实 npm tarball SHA256;若原生模块 shim 不理想,manifest 会提示改用 npm 全局安装。
103
+ 便携包内嵌 Node 运行时,**无需系统安装 Node**。公共 bucket:https://github.com/Zzy-min/scoop-qling
104
+ 官方 Extras PR:https://github.com/ScoopInstaller/Extras/pull/18307
99
105
 
100
106
  ### D2. 校验与同步
101
107
 
@@ -117,7 +123,7 @@ npm run build:portable-win
117
123
  # → dist-portable/qling-win-x64.zip + portable-meta.json (sha256)
118
124
  ```
119
125
 
120
- Release 资产示例:`https://github.com/Zzy-min/qling/releases/download/v1.2.0/qling-win-x64.zip`
126
+ Release 资产示例:`https://github.com/Zzy-min/qling/releases/download/v1.2.2/qling-win-x64.zip`
121
127
  清单:`packaging/winget/Zzy-min.qling.yaml`(含 InstallerSha256)。
122
128
 
123
129
  ### 本地校验
@@ -127,7 +133,7 @@ npm run validate:packaging
127
133
  Select-String -Path packaging/winget/Zzy-min.qling.yaml -Pattern 'PackageVersion|InstallerUrl|InstallerSha256'
128
134
  ```
129
135
 
130
- 正式提交 [winget-pkgs](https://github.com/microsoft/winget-pkgs) 仍需 PR;在此之前可用 zip 解压 + `qling.cmd`(需本机 Node 18)。
136
+ 正式提交 [winget-pkgs](https://github.com/microsoft/winget-pkgs) 仍需 PR;在此之前可用 zip 解压 + `qling.cmd`(便携 zip 已内嵌 Node 运行时(亦可使用系统 Node))。
131
137
 
132
138
  ## 配置与安全
133
139
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qlingzzy/qling",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "轻灵 Qling - 本地优先的中文 AI Agent CLI 工作台 / Local-first Chinese AI Agent CLI workbench",
5
5
  "type": "module",
6
6
  "main": "./dist/sdk.js",
@@ -92,7 +92,8 @@
92
92
  "dep:layers:write-baseline": "node scripts/dep-layers.mjs --write-baseline",
93
93
  "sync:scoop-bucket": "node scripts/sync-scoop-bucket.mjs",
94
94
  "build:portable-win": "node scripts/build-portable-win.mjs",
95
- "ci:check": "npm run build && node --experimental-test-module-mocks --test \"tests/unit/**/*.test.mjs\" && node --test \"tests/smoke/**/*.test.mjs\" && node scripts/eval-smoke.mjs && node scripts/eval-tasks.mjs && node scripts/validate-packaging.mjs && node scripts/dep-layers.mjs --baseline"
95
+ "sync:winget-sha": "node scripts/sync-winget-sha.mjs",
96
+ "ci:check": "npm run build && node --experimental-test-module-mocks --test \"tests/unit/**/*.test.mjs\" && node --test \"tests/smoke/**/*.test.mjs\" && node scripts/eval-smoke.mjs && node scripts/eval-tasks.mjs && node scripts/validate-packaging.mjs && node scripts/dep-layers.mjs --strict"
96
97
  },
97
98
  "dependencies": {
98
99
  "axios": "^1.7.0",
@@ -1,16 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Build a Windows portable zip for winget / manual install.
4
- * Usage: node scripts/build-portable-win.mjs
3
+ * Build a Windows portable zip that embeds a Node.js runtime (no system Node required).
4
+ * Usage: node scripts/build-portable-win.mjs [--skip-node]
5
5
  *
6
6
  * Output: dist-portable/qling-win-x64.zip
7
7
  * Layout:
8
8
  * qling-win-x64/
9
- * package/ # npm pack contents
10
- * qling.cmd # launcher node package/dist/index.js
9
+ * runtime/node.exe # bundled Node (same major as build host by default)
10
+ * package/ # npm pack contents
11
+ * qling.exe # WinGet portable entry (Framework csc launcher)
12
+ * qling.cmd # Scoop / shell-friendly launcher
11
13
  * README.txt
12
14
  */
13
- import { spawnSync } from "node:child_process";
15
+ import { spawnSync, execFileSync } from "node:child_process";
14
16
  import { createHash } from "node:crypto";
15
17
  import {
16
18
  mkdir,
@@ -18,31 +20,34 @@ import {
18
20
  readFile,
19
21
  writeFile,
20
22
  copyFile,
21
- readdir,
22
- stat,
23
+ access,
23
24
  } from "node:fs/promises";
24
- import { createWriteStream, existsSync, createReadStream } from "node:fs";
25
+ import { createWriteStream, existsSync } from "node:fs";
25
26
  import { join, dirname, resolve } from "node:path";
26
27
  import { fileURLToPath } from "node:url";
27
28
  import { pipeline } from "node:stream/promises";
28
- import { createGzip } from "node:zlib";
29
- import { execFileSync } from "node:child_process";
29
+ import { get } from "node:https";
30
30
 
31
31
  const __dirname = dirname(fileURLToPath(import.meta.url));
32
32
  const ROOT = resolve(__dirname, "..");
33
33
  const OUT_DIR = join(ROOT, "dist-portable");
34
34
  const STAGE = join(OUT_DIR, "qling-win-x64");
35
35
  const ZIP_PATH = join(OUT_DIR, "qling-win-x64.zip");
36
+ const skipNode = process.argv.includes("--skip-node");
36
37
 
37
38
  const pkg = JSON.parse(await readFile(join(ROOT, "package.json"), "utf8"));
38
39
  const version = pkg.version;
40
+ const nodeVersion = process.version; // e.g. v22.22.1
41
+ const nodeDistName = `node-${nodeVersion}-win-x64`;
42
+ const nodeZipUrl = `https://nodejs.org/dist/${nodeVersion}/${nodeDistName}.zip`;
39
43
 
40
44
  console.log(`[portable] building qling ${version} for win-x64…`);
45
+ console.log(`[portable] host node ${nodeVersion} ${process.arch}`);
41
46
 
42
47
  await rm(OUT_DIR, { recursive: true, force: true });
43
48
  await mkdir(STAGE, { recursive: true });
44
49
 
45
- // Prefer npm pack of current tree (matches publish)
50
+ // 1) npm pack current tree
46
51
  const pack = spawnSync(
47
52
  process.platform === "win32" ? "npm.cmd" : "npm",
48
53
  ["pack", "--pack-destination", OUT_DIR],
@@ -64,61 +69,165 @@ if (!tgzName) {
64
69
  const tgzPath = join(OUT_DIR, tgzName);
65
70
  console.log(`[portable] packed ${tgzName}`);
66
71
 
67
- // Extract with tar (Node 22+ / Windows 10+ tar)
68
- const pkgDir = join(STAGE, "package");
69
- await mkdir(pkgDir, { recursive: true });
70
72
  try {
71
73
  execFileSync("tar", ["-xzf", tgzPath, "-C", STAGE], { stdio: "inherit" });
72
- // npm pack extracts to package/
73
74
  } catch (err) {
74
75
  console.error("tar extract failed:", err instanceof Error ? err.message : err);
75
76
  process.exit(1);
76
77
  }
77
78
 
79
+ // npm pack intentionally excludes node_modules. Install only runtime dependencies
80
+ // into the staged package, then rebuild the native dependency with lifecycle
81
+ // scripts enabled only for that dependency.
82
+ const stagedPackage = join(STAGE, "package");
83
+ await copyFile(join(ROOT, "package-lock.json"), join(stagedPackage, "package-lock.json"));
84
+ runChecked(
85
+ process.platform === "win32" ? "npm.cmd" : "npm",
86
+ ["ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"],
87
+ stagedPackage,
88
+ "install staged production dependencies"
89
+ );
90
+ runChecked(
91
+ process.platform === "win32" ? "npm.cmd" : "npm",
92
+ ["rebuild", "better-sqlite3"],
93
+ stagedPackage,
94
+ "rebuild staged better-sqlite3"
95
+ );
96
+
97
+ // 2) Bundle Node runtime
98
+ const runtimeDir = join(STAGE, "runtime");
99
+ await mkdir(runtimeDir, { recursive: true });
100
+ let bundledNode = false;
101
+ let nodeNote = "System Node.js >= 18 on PATH";
102
+
103
+ if (!skipNode && process.platform === "win32") {
104
+ const cacheDir = join(OUT_DIR, ".node-cache");
105
+ await mkdir(cacheDir, { recursive: true });
106
+ const nodeZipPath = join(cacheDir, `${nodeDistName}.zip`);
107
+ if (!existsSync(nodeZipPath)) {
108
+ console.log(`[portable] downloading ${nodeZipUrl}`);
109
+ await downloadFile(nodeZipUrl, nodeZipPath);
110
+ } else {
111
+ console.log(`[portable] using cached ${nodeZipPath}`);
112
+ }
113
+ const extractDir = join(cacheDir, nodeDistName);
114
+ await rm(extractDir, { recursive: true, force: true });
115
+ execFileSync("tar", ["-xf", nodeZipPath, "-C", cacheDir], { stdio: "inherit" });
116
+ const nodeExeSrc = join(extractDir, "node.exe");
117
+ await access(nodeExeSrc);
118
+ await copyFile(nodeExeSrc, join(runtimeDir, "node.exe"));
119
+ // LICENSE if present
120
+ const license = join(extractDir, "LICENSE");
121
+ if (existsSync(license)) {
122
+ await copyFile(license, join(runtimeDir, "NODE-LICENSE"));
123
+ }
124
+ bundledNode = true;
125
+ nodeNote = `Bundled Node ${nodeVersion} (runtime\\node.exe)`;
126
+ console.log(`[portable] bundled node.exe from ${nodeDistName}`);
127
+ } else if (!skipNode) {
128
+ console.warn("[portable] non-Windows host: skip embedding node.exe (use --skip-node to silence)");
129
+ }
130
+
78
131
  const launcher = `@echo off
79
132
  setlocal
80
133
  set "ROOT=%~dp0"
81
- set "NODE="
82
- where node >nul 2>nul && set "NODE=node"
83
- if not defined NODE (
84
- echo [qling] Node.js ^>= 18 is required on PATH.
134
+ set "NODE_EXE="
135
+ if exist "%ROOT%runtime\\node.exe" set "NODE_EXE=%ROOT%runtime\\node.exe"
136
+ if not defined NODE_EXE (
137
+ where node >nul 2>nul && set "NODE_EXE=node"
138
+ )
139
+ if not defined NODE_EXE (
140
+ echo [qling] No bundled runtime and no system Node on PATH.
141
+ echo [qling] Re-download the portable zip or install Node.js ^>= 18.
85
142
  exit /b 1
86
143
  )
87
- "%NODE%" "%ROOT%package\\dist\\index.js" %*
144
+ "%NODE_EXE%" "%ROOT%package\\dist\\index.js" %*
88
145
  `;
89
146
  await writeFile(join(STAGE, "qling.cmd"), launcher, "utf8");
90
147
 
148
+ // Native .exe launcher for WinGet portable (scripted .cmd/.bat not allowed)
149
+ const launcherCs = join(ROOT, "packaging", "win-launcher", "qling-launcher.cs");
150
+ const launcherExe = join(STAGE, "qling.exe");
151
+ if (!existsSync(launcherCs)) {
152
+ console.error(`[portable] missing launcher source: ${launcherCs}`);
153
+ process.exit(1);
154
+ }
155
+ const cscCandidates = [
156
+ process.env.QLING_CSC,
157
+ join(
158
+ process.env.WINDIR || "C:\\Windows",
159
+ "Microsoft.NET",
160
+ "Framework64",
161
+ "v4.0.30319",
162
+ "csc.exe"
163
+ ),
164
+ join(
165
+ process.env.WINDIR || "C:\\Windows",
166
+ "Microsoft.NET",
167
+ "Framework",
168
+ "v4.0.30319",
169
+ "csc.exe"
170
+ ),
171
+ ].filter(Boolean);
172
+ const csc = cscCandidates.find((p) => existsSync(p));
173
+ if (!csc) {
174
+ console.error(
175
+ "[portable] csc.exe not found (need .NET Framework 4.x). Set QLING_CSC to override."
176
+ );
177
+ process.exit(1);
178
+ }
179
+ const cscResult = spawnSync(
180
+ csc,
181
+ [
182
+ "/nologo",
183
+ "/target:exe",
184
+ "/platform:anycpu",
185
+ "/optimize+",
186
+ `/out:${launcherExe}`,
187
+ launcherCs,
188
+ ],
189
+ { encoding: "utf8" }
190
+ );
191
+ if (cscResult.status !== 0 || !existsSync(launcherExe)) {
192
+ console.error("[portable] csc failed:", cscResult.stdout, cscResult.stderr);
193
+ process.exit(1);
194
+ }
195
+ console.log(`[portable] compiled qling.exe via ${csc}`);
196
+
197
+ runChecked(launcherExe, ["--version"], STAGE, "verify portable launcher version");
198
+ runChecked(launcherExe, ["doctor"], STAGE, "verify portable launcher doctor");
199
+
91
200
  const readme = `Qling ${version} — Windows portable layout
92
201
  ========================================
93
202
 
94
- Requirements:
95
- - Node.js >= 18 on PATH
96
- - Visual Studio Build Tools may be needed for better-sqlite3 rebuilds
203
+ ${bundledNode ? "This build embeds a Node.js runtime — no system Node install required." : "This build expects system Node.js >= 18 on PATH."}
204
+
205
+ Runtime:
206
+ ${nodeNote}
97
207
 
98
208
  Run:
209
+ .\\qling.exe --version
99
210
  .\\qling.cmd --version
100
- .\\qling.cmd doctor
101
- .\\qling.cmd setup
211
+ .\\qling.exe doctor
212
+ .\\qling.exe setup
213
+
214
+ Notes:
215
+ - qling.exe is the WinGet portable entry point (native launcher).
216
+ - qling.cmd remains for Scoop and interactive shells.
217
+ - better-sqlite3 native binary is compiled for the Node ABI used at pack time.
218
+ - Prefer matching the bundled Node major version if you replace runtime\\node.exe.
102
219
 
103
- Preferred install for most users:
220
+ npm alternative:
104
221
  npm install -g @qlingzzy/qling@${version} --registry https://registry.npmjs.org/
105
222
 
106
223
  Source: https://github.com/Zzy-min/qling
107
224
  `;
108
225
  await writeFile(join(STAGE, "README.txt"), readme, "utf8");
109
226
 
110
- // Zip via tar (bsdtar on Windows supports zip)
111
227
  await rm(ZIP_PATH, { force: true });
112
- try {
113
- execFileSync(
114
- "tar",
115
- ["-a", "-cf", ZIP_PATH, "-C", OUT_DIR, "qling-win-x64"],
116
- { stdio: "inherit" }
117
- );
118
- } catch (err) {
119
- console.error("zip failed:", err instanceof Error ? err.message : err);
120
- process.exit(1);
121
- }
228
+ execFileSync("tar", ["-a", "-cf", ZIP_PATH, "-C", OUT_DIR, "qling-win-x64"], {
229
+ stdio: "inherit",
230
+ });
122
231
 
123
232
  const buf = await readFile(ZIP_PATH);
124
233
  const sha256 = createHash("sha256").update(buf).digest("hex");
@@ -128,10 +237,47 @@ const meta = {
128
237
  path: ZIP_PATH,
129
238
  size: buf.length,
130
239
  sha256,
240
+ bundledNode,
241
+ nodeVersion: bundledNode ? nodeVersion : null,
131
242
  createdAt: new Date().toISOString(),
132
243
  };
133
244
  await writeFile(join(OUT_DIR, "portable-meta.json"), JSON.stringify(meta, null, 2) + "\n");
134
245
 
135
246
  console.log(`[portable] wrote ${ZIP_PATH}`);
136
247
  console.log(`[portable] size=${buf.length} sha256=${sha256}`);
248
+ console.log(`[portable] bundledNode=${bundledNode}`);
137
249
  console.log(`[portable] meta: dist-portable/portable-meta.json`);
250
+
251
+ function runChecked(command, args, cwd, label) {
252
+ console.log(`[portable] ${label}…`);
253
+ const result = spawnSync(command, args, {
254
+ cwd,
255
+ encoding: "utf8",
256
+ shell: process.platform === "win32" && /\.cmd$/i.test(command),
257
+ });
258
+ if (result.stdout) process.stdout.write(result.stdout);
259
+ if (result.stderr) process.stderr.write(result.stderr);
260
+ if (result.status !== 0) {
261
+ if (result.error) console.error(`[portable] ${result.error.message}`);
262
+ console.error(`[portable] ${label} failed with exit code ${result.status}`);
263
+ process.exit(result.status ?? 1);
264
+ }
265
+ }
266
+
267
+ function downloadFile(url, dest) {
268
+ return new Promise((resolvePromise, reject) => {
269
+ const file = createWriteStream(dest);
270
+ get(url, (res) => {
271
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
272
+ file.close();
273
+ downloadFile(res.headers.location, dest).then(resolvePromise, reject);
274
+ return;
275
+ }
276
+ if (res.statusCode !== 200) {
277
+ reject(new Error(`download failed: HTTP ${res.statusCode}`));
278
+ return;
279
+ }
280
+ pipeline(res, file).then(resolvePromise, reject);
281
+ }).on("error", reject);
282
+ });
283
+ }
@@ -70,6 +70,7 @@ export function layerOf(rel) {
70
70
  p.startsWith("tools/") ||
71
71
  p.startsWith("execution/") ||
72
72
  p === "slash-context.ts" ||
73
+ p === "slash-ports.ts" ||
73
74
  p === "repl.ts"
74
75
  ) {
75
76
  return "agent-runtime";
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Fill winget InstallerSha256 from dist-portable/portable-meta.json
4
+ */
5
+ import { readFile, writeFile, readdir } from "node:fs/promises";
6
+ import { join, dirname } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { existsSync } from "node:fs";
9
+
10
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
11
+ const metaPath = join(root, "dist-portable", "portable-meta.json");
12
+ if (!existsSync(metaPath)) {
13
+ console.error("missing dist-portable/portable-meta.json — run npm run build:portable-win first");
14
+ process.exit(1);
15
+ }
16
+ const meta = JSON.parse(await readFile(metaPath, "utf8"));
17
+ const sha = String(meta.sha256 || "").toLowerCase();
18
+ if (!/^[a-f0-9]{64}$/.test(sha)) {
19
+ console.error("invalid sha256 in portable-meta.json");
20
+ process.exit(1);
21
+ }
22
+
23
+ const version = meta.version || JSON.parse(await readFile(join(root, "package.json"), "utf8")).version;
24
+ const targets = [
25
+ join(root, "packaging", "winget", "Zzy-min.qling.yaml"),
26
+ join(root, "packaging", "winget", "manifests", "Zzy-min", "qling", version, "Zzy-min.qling.installer.yaml"),
27
+ ];
28
+
29
+ for (const file of targets) {
30
+ if (!existsSync(file)) {
31
+ console.warn("skip missing", file);
32
+ continue;
33
+ }
34
+ let text = await readFile(file, "utf8");
35
+ text = text.replace(/InstallerSha256:\s*[A-Za-z0-9_]+/g, `InstallerSha256: ${sha}`);
36
+ text = text.replace(/PackageVersion:\s*[\d.]+/g, `PackageVersion: ${version}`);
37
+ text = text.replace(/v[\d.]+\//g, `v${version}/`);
38
+ await writeFile(file, text, "utf8");
39
+ console.log("updated", file);
40
+ }
41
+
42
+ // Scoop: rewrite Extras-compatible 64bit manifest
43
+ const { spawnSync } = await import("node:child_process");
44
+ const scoopScript = join(root, "scripts", "write-scoop-manifest.mjs");
45
+ const r = spawnSync(process.execPath, [scoopScript], { cwd: root, encoding: "utf8" });
46
+ process.stdout.write(r.stdout || "");
47
+ process.stderr.write(r.stderr || "");
48
+ if (r.status !== 0) process.exit(r.status || 1);
49
+
50
+ // Multi-file winget for this version dir (create if missing)
51
+ const multiDir = join(root, "packaging", "winget", "manifests", "Zzy-min", "qling", version);
52
+ if (!existsSync(multiDir)) {
53
+ console.warn("note: multi-file winget dir missing for", version, "- create under packaging/winget/manifests");
54
+ }
55
+
56
+ console.log(`sync-winget-sha OK version=${version} sha=${sha.slice(0, 16)}… bundledNode=${meta.bundledNode}`);
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Validate Scoop / winget draft manifests align with package.json version.
3
+ * Validate Scoop / winget manifests align with package.json version.
4
4
  * Usage: node scripts/validate-packaging.mjs
5
5
  */
6
6
  import { readFile } from "node:fs/promises";
7
7
  import { join, dirname } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
+ import { existsSync } from "node:fs";
9
10
 
10
11
  const root = join(dirname(fileURLToPath(import.meta.url)), "..");
11
12
  const errors = [];
@@ -18,17 +19,26 @@ const scoop = JSON.parse(scoopRaw);
18
19
  if (scoop.version !== version) {
19
20
  errors.push(`scoop version ${scoop.version} != package ${version}`);
20
21
  }
21
- if (!String(scoop.url || "").includes(version)) {
22
- errors.push(`scoop url does not pin ${version}: ${scoop.url}`);
22
+ const scoopUrl =
23
+ scoop.architecture?.["64bit"]?.url || scoop.url || "";
24
+ const scoopHash =
25
+ scoop.architecture?.["64bit"]?.hash || scoop.hash || "";
26
+ if (!String(scoopUrl).includes(version)) {
27
+ errors.push(`scoop url does not pin ${version}: ${scoopUrl}`);
23
28
  }
24
- if (!Array.isArray(scoop.notes) || !scoop.notes.some((n) => /DRAFT|draft/i.test(n))) {
25
- errors.push("scoop notes should mark DRAFT status");
29
+ if (!scoop.architecture?.["64bit"]) {
30
+ errors.push("scoop should nest url/hash under architecture.64bit (Extras style)");
26
31
  }
27
- const hash = String(scoop.hash || "");
28
- if (!hash || /TODO|REPLACE/i.test(hash)) {
29
- errors.push("scoop hash still placeholder — fill real SHA256 after npm publish");
30
- } else if (!/^(sha256:)?[a-f0-9]{64}$/i.test(hash)) {
31
- errors.push(`scoop hash looks invalid: ${hash}`);
32
+ if (!scoopHash || /TODO|REPLACE|PLACEHOLDER/i.test(String(scoopHash))) {
33
+ errors.push("scoop hash still placeholder — run build:portable-win + sync-winget-sha");
34
+ } else if (!/^(sha256:)?[a-f0-9]{64}$/i.test(String(scoopHash))) {
35
+ errors.push(`scoop hash looks invalid: ${scoopHash}`);
36
+ }
37
+ if (/sha256:/i.test(String(scoopHash))) {
38
+ errors.push("scoop hash should be plain hex (no sha256: prefix) for Extras");
39
+ }
40
+ if (!String(scoop.bin || "").includes("qling")) {
41
+ errors.push("scoop bin should expose qling");
32
42
  }
33
43
 
34
44
  const winget = await readFile(
@@ -41,8 +51,36 @@ if (!winget.includes(`PackageVersion: ${version}`)) {
41
51
  if (!winget.includes("PackageIdentifier: Zzy-min.qling")) {
42
52
  errors.push("winget PackageIdentifier missing");
43
53
  }
44
- if (!/DRAFT|not submitted/i.test(winget)) {
45
- errors.push("winget should remain marked as DRAFT");
54
+ if (/InstallerSha256:\s*(0{64}|PLACEHOLDER)/i.test(winget)) {
55
+ errors.push("winget InstallerSha256 is still placeholder");
56
+ }
57
+
58
+ const multiInstaller = join(
59
+ root,
60
+ "packaging",
61
+ "winget",
62
+ "manifests",
63
+ "Zzy-min",
64
+ "qling",
65
+ version,
66
+ "Zzy-min.qling.installer.yaml"
67
+ );
68
+ if (existsSync(multiInstaller)) {
69
+ const mi = await readFile(multiInstaller, "utf8");
70
+ if (/InstallerSha256:\s*(0{64}|PLACEHOLDER)/i.test(mi)) {
71
+ errors.push("multi-file winget installer sha still placeholder");
72
+ }
73
+ if (!mi.includes(`PackageVersion: ${version}`)) {
74
+ errors.push("multi-file winget installer version mismatch");
75
+ }
76
+ }
77
+
78
+ const bucket = join(root, "packaging", "scoop-bucket", "qling.json");
79
+ if (existsSync(bucket)) {
80
+ const b = JSON.parse(await readFile(bucket, "utf8"));
81
+ if (b.version !== version) errors.push("scoop-bucket version mismatch");
82
+ const bHash = b.architecture?.["64bit"]?.hash || b.hash;
83
+ if (bHash !== scoopHash) errors.push("scoop-bucket hash out of sync with scoop/qling.json");
46
84
  }
47
85
 
48
86
  if (errors.length) {
@@ -52,6 +90,6 @@ if (errors.length) {
52
90
  }
53
91
 
54
92
  console.log(`validate-packaging OK (version ${version})`);
55
- console.log(" scoop:", scoop.url);
93
+ console.log(" scoop:", scoopUrl);
94
+ console.log(" scoop.hash:", String(scoopHash).slice(0, 20) + "…");
56
95
  console.log(" winget: Zzy-min.qling @", version);
57
- console.log(" note: still drafts — not published to official catalogs");
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Write Scoop-compatible manifest (Extras style):
4
+ * - 4-space indent
5
+ * - plain SHA256 (no sha256: prefix)
6
+ * - architecture.64bit for url/hash/extract_dir
7
+ * - autoupdate.architecture.64bit
8
+ *
9
+ * Reads dist-portable/portable-meta.json when present.
10
+ */
11
+ import { readFile, writeFile } from "node:fs/promises";
12
+ import { join, dirname } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { existsSync } from "node:fs";
15
+
16
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
17
+ const pkg = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
18
+ const version = pkg.version;
19
+
20
+ let hash = "PLACEHOLDER";
21
+ const metaPath = join(root, "dist-portable", "portable-meta.json");
22
+ if (existsSync(metaPath)) {
23
+ const meta = JSON.parse(await readFile(metaPath, "utf8"));
24
+ hash = String(meta.sha256 || "").toLowerCase();
25
+ }
26
+
27
+ const manifest = {
28
+ version,
29
+ description: "Local-first Chinese AI Agent CLI workbench (Qling / 轻灵).",
30
+ homepage: "https://github.com/Zzy-min/qling",
31
+ license: "MIT",
32
+ notes: [
33
+ "Portable zip embeds a Node.js runtime (no system Node required).",
34
+ "Set API keys as user environment variables (never commit secrets).",
35
+ "After install: run qling doctor, then qling setup.",
36
+ ],
37
+ architecture: {
38
+ "64bit": {
39
+ url: `https://github.com/Zzy-min/qling/releases/download/v${version}/qling-win-x64.zip`,
40
+ hash,
41
+ extract_dir: "qling-win-x64",
42
+ },
43
+ },
44
+ bin: "qling.cmd",
45
+ post_install: [
46
+ "Write-Host 'Run qling doctor, then qling setup.' -ForegroundColor Cyan",
47
+ ],
48
+ checkver: {
49
+ github: "https://github.com/Zzy-min/qling",
50
+ },
51
+ autoupdate: {
52
+ architecture: {
53
+ "64bit": {
54
+ url: "https://github.com/Zzy-min/qling/releases/download/v$version/qling-win-x64.zip",
55
+ },
56
+ },
57
+ },
58
+ };
59
+
60
+ const json = JSON.stringify(manifest, null, 4) + "\n";
61
+ const targets = [
62
+ join(root, "packaging", "scoop", "qling.json"),
63
+ join(root, "packaging", "scoop-bucket", "qling.json"),
64
+ ];
65
+ for (const t of targets) {
66
+ await writeFile(t, json, "utf8");
67
+ console.log("wrote", t);
68
+ }
69
+ console.log(`version=${version} hash=${hash.slice(0, 16)}…`);
@@ -1 +0,0 @@
1
- {"version":3,"file":"durable-session-supervisor.d.ts","sourceRoot":"","sources":["../../src/session/durable-session-supervisor.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA4B;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,+BAA+B;IAMpD,KAAK,IAAI,IAAI;IAQP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAOb,IAAI;YAyBJ,iBAAiB;YAcjB,iBAAiB;YAiBjB,sBAAsB;YAiCtB,cAAc;YAoCd,iBAAiB;YAyBjB,WAAW;CA8B1B"}