@qlingzzy/qling 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## v1.2.1 (2026-07-15)
6
+
7
+ ### Official distribution + strict layers + self-contained portable
8
+
9
+ - **分层**:清零反向依赖;`ci:check` 启用 `dep:layers --strict`;`installSlashPorts` + `DurableSessionSupervisor` 上提 `agent/`。
10
+ - **便携包**:`build:portable-win` 内嵌 Node 运行时(`runtime/node.exe`),无需本机安装 Node。
11
+ - **Scoop/winget**:manifest 指向 GitHub Release portable zip;多文件 winget 清单;官方 PR 按审阅修正 64bit 结构与 hash 格式。
12
+ - **公共 Scoop bucket**:https://github.com/Zzy-min/scoop-qling
13
+
5
14
  ## v1.2.0 (2026-07-14)
6
15
 
7
16
  ### 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.1-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.1` — 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.1` (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.1-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.1;作用域名,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.1`
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");
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) {
@@ -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":"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"}
@@ -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.1/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.1",
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,17 @@
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.cmd # launcher → runtime\node.exe package\dist\index.js
11
12
  * README.txt
12
13
  */
13
- import { spawnSync } from "node:child_process";
14
+ import { spawnSync, execFileSync } from "node:child_process";
14
15
  import { createHash } from "node:crypto";
15
16
  import {
16
17
  mkdir,
@@ -18,31 +19,34 @@ import {
18
19
  readFile,
19
20
  writeFile,
20
21
  copyFile,
21
- readdir,
22
- stat,
22
+ access,
23
23
  } from "node:fs/promises";
24
- import { createWriteStream, existsSync, createReadStream } from "node:fs";
24
+ import { createWriteStream, existsSync } from "node:fs";
25
25
  import { join, dirname, resolve } from "node:path";
26
26
  import { fileURLToPath } from "node:url";
27
27
  import { pipeline } from "node:stream/promises";
28
- import { createGzip } from "node:zlib";
29
- import { execFileSync } from "node:child_process";
28
+ import { get } from "node:https";
30
29
 
31
30
  const __dirname = dirname(fileURLToPath(import.meta.url));
32
31
  const ROOT = resolve(__dirname, "..");
33
32
  const OUT_DIR = join(ROOT, "dist-portable");
34
33
  const STAGE = join(OUT_DIR, "qling-win-x64");
35
34
  const ZIP_PATH = join(OUT_DIR, "qling-win-x64.zip");
35
+ const skipNode = process.argv.includes("--skip-node");
36
36
 
37
37
  const pkg = JSON.parse(await readFile(join(ROOT, "package.json"), "utf8"));
38
38
  const version = pkg.version;
39
+ const nodeVersion = process.version; // e.g. v22.22.1
40
+ const nodeDistName = `node-${nodeVersion}-win-x64`;
41
+ const nodeZipUrl = `https://nodejs.org/dist/${nodeVersion}/${nodeDistName}.zip`;
39
42
 
40
43
  console.log(`[portable] building qling ${version} for win-x64…`);
44
+ console.log(`[portable] host node ${nodeVersion} ${process.arch}`);
41
45
 
42
46
  await rm(OUT_DIR, { recursive: true, force: true });
43
47
  await mkdir(STAGE, { recursive: true });
44
48
 
45
- // Prefer npm pack of current tree (matches publish)
49
+ // 1) npm pack current tree
46
50
  const pack = spawnSync(
47
51
  process.platform === "win32" ? "npm.cmd" : "npm",
48
52
  ["pack", "--pack-destination", OUT_DIR],
@@ -64,61 +68,92 @@ if (!tgzName) {
64
68
  const tgzPath = join(OUT_DIR, tgzName);
65
69
  console.log(`[portable] packed ${tgzName}`);
66
70
 
67
- // Extract with tar (Node 22+ / Windows 10+ tar)
68
- const pkgDir = join(STAGE, "package");
69
- await mkdir(pkgDir, { recursive: true });
70
71
  try {
71
72
  execFileSync("tar", ["-xzf", tgzPath, "-C", STAGE], { stdio: "inherit" });
72
- // npm pack extracts to package/
73
73
  } catch (err) {
74
74
  console.error("tar extract failed:", err instanceof Error ? err.message : err);
75
75
  process.exit(1);
76
76
  }
77
77
 
78
+ // 2) Bundle Node runtime
79
+ const runtimeDir = join(STAGE, "runtime");
80
+ await mkdir(runtimeDir, { recursive: true });
81
+ let bundledNode = false;
82
+ let nodeNote = "System Node.js >= 18 on PATH";
83
+
84
+ if (!skipNode && process.platform === "win32") {
85
+ const cacheDir = join(OUT_DIR, ".node-cache");
86
+ await mkdir(cacheDir, { recursive: true });
87
+ const nodeZipPath = join(cacheDir, `${nodeDistName}.zip`);
88
+ if (!existsSync(nodeZipPath)) {
89
+ console.log(`[portable] downloading ${nodeZipUrl}`);
90
+ await downloadFile(nodeZipUrl, nodeZipPath);
91
+ } else {
92
+ console.log(`[portable] using cached ${nodeZipPath}`);
93
+ }
94
+ const extractDir = join(cacheDir, nodeDistName);
95
+ await rm(extractDir, { recursive: true, force: true });
96
+ execFileSync("tar", ["-xf", nodeZipPath, "-C", cacheDir], { stdio: "inherit" });
97
+ const nodeExeSrc = join(extractDir, "node.exe");
98
+ await access(nodeExeSrc);
99
+ await copyFile(nodeExeSrc, join(runtimeDir, "node.exe"));
100
+ // LICENSE if present
101
+ const license = join(extractDir, "LICENSE");
102
+ if (existsSync(license)) {
103
+ await copyFile(license, join(runtimeDir, "NODE-LICENSE"));
104
+ }
105
+ bundledNode = true;
106
+ nodeNote = `Bundled Node ${nodeVersion} (runtime\\node.exe)`;
107
+ console.log(`[portable] bundled node.exe from ${nodeDistName}`);
108
+ } else if (!skipNode) {
109
+ console.warn("[portable] non-Windows host: skip embedding node.exe (use --skip-node to silence)");
110
+ }
111
+
78
112
  const launcher = `@echo off
79
113
  setlocal
80
114
  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.
115
+ set "NODE_EXE="
116
+ if exist "%ROOT%runtime\\node.exe" set "NODE_EXE=%ROOT%runtime\\node.exe"
117
+ if not defined NODE_EXE (
118
+ where node >nul 2>nul && set "NODE_EXE=node"
119
+ )
120
+ if not defined NODE_EXE (
121
+ echo [qling] No bundled runtime and no system Node on PATH.
122
+ echo [qling] Re-download the portable zip or install Node.js ^>= 18.
85
123
  exit /b 1
86
124
  )
87
- "%NODE%" "%ROOT%package\\dist\\index.js" %*
125
+ "%NODE_EXE%" "%ROOT%package\\dist\\index.js" %*
88
126
  `;
89
127
  await writeFile(join(STAGE, "qling.cmd"), launcher, "utf8");
90
128
 
91
129
  const readme = `Qling ${version} — Windows portable layout
92
130
  ========================================
93
131
 
94
- Requirements:
95
- - Node.js >= 18 on PATH
96
- - Visual Studio Build Tools may be needed for better-sqlite3 rebuilds
132
+ ${bundledNode ? "This build embeds a Node.js runtime — no system Node install required." : "This build expects system Node.js >= 18 on PATH."}
133
+
134
+ Runtime:
135
+ ${nodeNote}
97
136
 
98
137
  Run:
99
138
  .\\qling.cmd --version
100
139
  .\\qling.cmd doctor
101
140
  .\\qling.cmd setup
102
141
 
103
- Preferred install for most users:
142
+ Notes:
143
+ - better-sqlite3 native binary is compiled for the Node ABI used at pack time.
144
+ - Prefer matching the bundled Node major version if you replace runtime\\node.exe.
145
+
146
+ npm alternative:
104
147
  npm install -g @qlingzzy/qling@${version} --registry https://registry.npmjs.org/
105
148
 
106
149
  Source: https://github.com/Zzy-min/qling
107
150
  `;
108
151
  await writeFile(join(STAGE, "README.txt"), readme, "utf8");
109
152
 
110
- // Zip via tar (bsdtar on Windows supports zip)
111
153
  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
- }
154
+ execFileSync("tar", ["-a", "-cf", ZIP_PATH, "-C", OUT_DIR, "qling-win-x64"], {
155
+ stdio: "inherit",
156
+ });
122
157
 
123
158
  const buf = await readFile(ZIP_PATH);
124
159
  const sha256 = createHash("sha256").update(buf).digest("hex");
@@ -128,10 +163,31 @@ const meta = {
128
163
  path: ZIP_PATH,
129
164
  size: buf.length,
130
165
  sha256,
166
+ bundledNode,
167
+ nodeVersion: bundledNode ? nodeVersion : null,
131
168
  createdAt: new Date().toISOString(),
132
169
  };
133
170
  await writeFile(join(OUT_DIR, "portable-meta.json"), JSON.stringify(meta, null, 2) + "\n");
134
171
 
135
172
  console.log(`[portable] wrote ${ZIP_PATH}`);
136
173
  console.log(`[portable] size=${buf.length} sha256=${sha256}`);
174
+ console.log(`[portable] bundledNode=${bundledNode}`);
137
175
  console.log(`[portable] meta: dist-portable/portable-meta.json`);
176
+
177
+ function downloadFile(url, dest) {
178
+ return new Promise((resolvePromise, reject) => {
179
+ const file = createWriteStream(dest);
180
+ get(url, (res) => {
181
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
182
+ file.close();
183
+ downloadFile(res.headers.location, dest).then(resolvePromise, reject);
184
+ return;
185
+ }
186
+ if (res.statusCode !== 200) {
187
+ reject(new Error(`download failed: HTTP ${res.statusCode}`));
188
+ return;
189
+ }
190
+ pipeline(res, file).then(resolvePromise, reject);
191
+ }).on("error", reject);
192
+ });
193
+ }
@@ -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"}