@gfgghgftgffg/amox-pi-sync 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MAINTENANCE.md +2 -2
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/core.ts +29 -0
- package/index.ts +4 -2
- package/package.json +1 -1
package/MAINTENANCE.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
- Upstream: [MuratAitov/pi-sync](https://github.com/MuratAitov/pi-sync), npm `pi-sync-suite@0.1.1`.
|
|
6
6
|
- Source ref: `npm:pi-sync-suite@0.1.1` (published source baseline, not the repository's moving HEAD).
|
|
7
7
|
- Upstream MIT notice is preserved verbatim in `LICENSE`: Copyright (c) 2026 Murat.
|
|
8
|
-
- Local package: `@gfgghgftgffg/amox-pi-sync@0.1.
|
|
8
|
+
- Local package: `@gfgghgftgffg/amox-pi-sync@0.1.7`, standalone TypeScript entrypoint `index.ts` and `core.ts`; no runtime dependency on the original suite.
|
|
9
9
|
|
|
10
10
|
## Scope
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ Keep raw agent-data push/pull, safe relative include/exclude paths (sessions + m
|
|
|
13
13
|
|
|
14
14
|
Settings are stored in `amox-pi-sync.json`; legacy `pi-sync-suite.json` is read only as a migration fallback until new settings are saved. Backups use `amox-pi-sync-backups` and require manual retention management via Cleanup. Manual/off startup must not access the network; auto startup fingerprints locally then starts timers and a non-awaited background pull. Setup only saves configuration and stops timers, requiring reload to activate auto mode. The automatic loop must not block the UI. Migration uses an independent `amox-pi-sync-repo`, not the old snapshot directory. The `pi-sync` status key preserves existing Starline placement; the pill bridge takes priority over legacy command mapping. `off` disables automation, not explicit commands. Presentation separates labels and values with a colon; the path count is explicitly marked configured. Starline action rows have a leading bullet, while native Pi selectors retain their own indicators. Presentation uses semantic Pi theme colours for labels, values and errors, and bold targets without fixed foreground/background colours so Starline owns selection contrast. Keep Manual/Auto/Off and Push/Pull terminology consistent across capsule, details, menus and command feedback; never append idle to the mode. Anchor bubbles to the clicked Starline cell, including submenus and confirmations; commands reuse the last clicked anchor or center before one is known. Forward pill colours by default and offer semantic theme colour overrides through bubbleColor; do not override selected-row text contrast. Colour changes must not start timers or network work. Without Starline use native Pi UI and hide the Starline colour option. Recompute theme styles when rendering, keep RPC/native status text plain, and do not store styled strings in settings. On Windows prefer native OpenSSH when present for non-ASCII home paths; retain batch mode, timeout and host-key verification. Keep large diagnostic results in the action notification rather than copying JSON dumps into status details.
|
|
15
15
|
|
|
16
|
-
Safety requirements: private SSH remote, no working-directory mutation, no semantic JSONL merging, preserve files that were never synced, back up replacements/deletions and never overwrite or delete active sessions. Deletion propagation must use `syncedPaths`: Push removes only previously synced paths missing locally, Pull removes only baseline paths absent from the new snapshot, and Pull preserves locally missing baseline paths as pending push deletions. Pre-upgrade deletions are not inferred automatically. Prune orphan sessions scans `sessions/**/*.jsonl`, compares parent basenames case-insensitively, accepts `type#8hex` names, iterates parent candidates to a fixed point, skips files modified within 10 minutes and the active session, refuses dirty repositories, commits snapshot removals before deleting local files, and never deletes symlinks or non-files. Before-push pruning runs outside `run("Push")`, is Off by default, and never blocks a push on failure. Network transfers use a 10-minute timeout and local Git commands 60 seconds; a terminated or timed-out process must never be recorded as success, even when an executor reports code 0. Pull fast-forwards or performs a clean file-level three-way merge; conflicts abort and apply nothing. A rejected non-fast-forward push fetches, integrates without rebase or force, and retries once; files integrated during push are applied on the next pull. Git commands force `core.autocrlf=false` and `core.eol=lf`, with a repository-local `* -text` attribute; do not rewrite raw bytes or existing mixed-EOL history automatically. Safe-path validation must exclude configuration and implementation data even when users select arbitrary relative data paths. Do not claim encryption, conflict-free multi-device merging or automatic secret removal.
|
|
16
|
+
Safety requirements: private SSH remote, no working-directory mutation, no semantic JSONL merging, preserve files that were never synced, back up replacements/deletions and never overwrite or delete active sessions. Deletion propagation must use `syncedPaths`: Push removes only previously synced paths missing locally, Pull removes only baseline paths absent from the new snapshot, and Pull preserves locally missing baseline paths as pending push deletions. A missing baseline is seeded at session start from files present both locally and in an existing snapshot; local-only and unpulled remote-only paths stay outside it. Pre-upgrade deletions are not inferred automatically. Prune orphan sessions scans `sessions/**/*.jsonl`, compares parent basenames case-insensitively, accepts `type#8hex` names, iterates parent candidates to a fixed point, skips files modified within 10 minutes and the active session, refuses dirty repositories, commits snapshot removals before deleting local files, and never deletes symlinks or non-files. Before-push pruning runs outside `run("Push")`, is Off by default, and never blocks a push on failure. Network transfers use a 10-minute timeout and local Git commands 60 seconds; a terminated or timed-out process must never be recorded as success, even when an executor reports code 0. Pull fast-forwards or performs a clean file-level three-way merge; conflicts abort and apply nothing. A rejected non-fast-forward push fetches, integrates without rebase or force, and retries once; files integrated during push are applied on the next pull. Git commands force `core.autocrlf=false` and `core.eol=lf`, with a repository-local `* -text` attribute; do not rewrite raw bytes or existing mixed-EOL history automatically. Safe-path validation must exclude configuration and implementation data even when users select arbitrary relative data paths. Do not claim encryption, conflict-free multi-device merging or automatic secret removal.
|
|
17
17
|
|
|
18
18
|
## 功能取舍与维护边界
|
|
19
19
|
|
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ Manual/off startup performs no network access. Auto startup computes a local fin
|
|
|
43
43
|
|
|
44
44
|
- Paths are relative to Pi's agent-data directory, not the current project. Arbitrary safe data paths can be included or excluded; the extension does not change the working directory.
|
|
45
45
|
- Raw files are transferred without Markdown/JSON export or semantic JSONL merging. Concurrent edits are not a semantic merge; an automatic three-way merge is file-level and aborts if both sides changed the same file. Coordinate devices and inspect diagnostics.
|
|
46
|
-
- Deletions of previously synced files propagate: Push removes them from the snapshot, and Pull deletes local copies removed remotely, after backup. Files that were never synced are retained. A per-device `syncedPaths` baseline prevents unpulled remote files from being mistaken for local deletions;
|
|
46
|
+
- Deletions of previously synced files propagate: Push removes them from the snapshot, and Pull deletes local copies removed remotely, after backup. Files that were never synced are retained. A per-device `syncedPaths` baseline prevents unpulled remote files from being mistaken for local deletions. On first session start a missing baseline is seeded from files present both locally and in an existing snapshot; local-only and unpulled remote-only files stay outside it. Pre-upgrade deletions are not inferred automatically. Incoming content replaces differing same-path files after backup; active sessions must not be deleted or overwritten. Restore applies only the currently managed paths.
|
|
47
47
|
- **Prune orphan sessions** (action or `/sync-prune`) scans `sessions/**/*.jsonl` for subagent sessions whose parent is missing or itself pruned. It compares parent basenames case-insensitively, accepts names matching `type#8hex`, skips files modified in the last 10 minutes and the active session, shows count/size/first names, commits snapshot removals first, then deletes local files. Staged or uncommitted repository changes abort without deleting local files. `--yes` skips confirmation. **Prune before push** runs it before manual, command and automatic pushes; a prune failure warns but does not block the push.
|
|
48
48
|
- Network Git operations (`clone`, `fetch`, `pull`, `push`) use a 10-minute timeout; local Git operations use 60 seconds. A process terminated by timeout or cancellation is a failure and is never recorded as completed, even if Pi reports exit code 0. Pull fast-forwards when possible; otherwise it performs a clean three-way merge in the snapshot repository. Conflicts abort the merge and apply no files. A rejected non-fast-forward push fetches, integrates remote history without rebase or force, and retries once; files integrated during push are applied on the next pull.
|
|
49
49
|
- Git commands force `core.autocrlf=false` and `core.eol=lf`, and the snapshot repository uses a local `* -text` attribute so Git does not convert raw bytes. Existing history with mixed CRLF/LF is not rewritten automatically; normalizing it requires an explicit manual migration and repack. On Windows, SSH prefers native OpenSSH (`System32/OpenSSH/ssh.exe`) when available to support non-ASCII user directories; otherwise it uses `ssh`. Batch mode, connection timeout and host-key verification remain enabled, overriding a custom `core.sshCommand`.
|
package/README.zh-CN.md
CHANGED
|
@@ -43,7 +43,7 @@ manual/off 启动不访问网络。auto 启动先计算本地 fingerprint,再
|
|
|
43
43
|
|
|
44
44
|
- 路径相对于 Pi agent 数据目录,不是当前项目。可包含/排除任意安全的数据相对路径;扩展不改写工作目录。
|
|
45
45
|
- 传输原始文件,不生成 Markdown/JSON 归档,不做语义化 JSONL 合并。并发编辑不是语义合并;自动三方合并按文件进行,双方修改同一文件时会中止。请协调多设备操作并查看诊断。
|
|
46
|
-
- 曾经同步过的文件被手动删除后,删除会传播:Push 从快照移除,Pull 会删除远端已移除的本地副本(删除前备份)。从未同步过的本地文件保留。每台设备用 `syncedPaths` 基线记录已同步路径,避免把“远端有、本机尚未 Pull
|
|
46
|
+
- 曾经同步过的文件被手动删除后,删除会传播:Push 从快照移除,Pull 会删除远端已移除的本地副本(删除前备份)。从未同步过的本地文件保留。每台设备用 `syncedPaths` 基线记录已同步路径,避免把“远端有、本机尚未 Pull”的文件误判为本地删除。首次启动时若没有基线,会从“本地和现有快照同时存在”的文件中初始化;本地独有和尚未 Pull 的远端文件不会进入基线。升级前已经发生的删除不会自动推断。同路径的不同内容以拉取数据为准,覆盖前备份;不得删除或覆盖活跃会话。恢复备份只处理当前纳管范围。
|
|
47
47
|
- **Prune orphan sessions**(操作项或 `/sync-prune`)扫描 `sessions/**/*.jsonl`,找出父会话不存在或父会话本身也被清理的子代理会话。父文件按 basename 不区分大小写比对,名字匹配 `type#8hex`;最近 10 分钟内修改过的文件和当前活跃会话会跳过;预览显示条数、体积和前几条名称;先提交快照删除,再删本地文件。仓库有暂存或未提交修改时拒绝执行,不删本地文件。`--yes` 跳过确认。**Prune before push** 会在手动、命令和自动 Push 前执行;清理失败只警告,不阻断 Push。
|
|
48
48
|
- 网络 Git 操作(`clone`、`fetch`、`pull`、`push`)超时为 10 分钟,本地 Git 操作为 60 秒。被超时或取消终止的进程一律视为失败,即使 Pi 返回 exit code 0,也不会写入完成时间。能快进时 Pull 直接快进,否则在快照仓库中执行干净的三方合并;冲突会中止合并且不应用任何文件。Push 被非快进拒绝时会 fetch、在不 rebase、不 force 的前提下整合远端历史,并重试一次;Push 整合的文件在下一次 Pull 时应用。
|
|
49
49
|
- Git 操作强制 `core.autocrlf=false`、`core.eol=lf`,快照仓库使用本地 `* -text` 属性,避免 Git 转换原始字节。已有 CRLF/LF 混合历史不会被自动改写;统一换行需要显式的手动迁移和 repack。Windows 优先使用原生 OpenSSH(`System32/OpenSSH/ssh.exe`),支持中文用户目录;不存在时及其他平台使用 `ssh`。保留非交互模式、连接超时及主机密钥验证,不复用自定义 `core.sshCommand`。
|
package/core.ts
CHANGED
|
@@ -374,6 +374,35 @@ export class SyncService {
|
|
|
374
374
|
}
|
|
375
375
|
await flush();
|
|
376
376
|
}
|
|
377
|
+
/** Paths currently present under the managed roots, without reading file bodies. */
|
|
378
|
+
private async listPaths(root: string): Promise<Set<string>> {
|
|
379
|
+
const result = new Set<string>();
|
|
380
|
+
const visit = async (relative: string): Promise<void> => {
|
|
381
|
+
validatePath(relative);
|
|
382
|
+
const absolute = path.join(root, relative); await noLinks(absolute);
|
|
383
|
+
const info = await stat(absolute); if (!info) return;
|
|
384
|
+
if (info.isDirectory()) { for (const child of (await fs.readdir(absolute)).sort()) await visit(`${relative}/${child}`); }
|
|
385
|
+
else if (info.isFile()) result.add(relative);
|
|
386
|
+
else throw new Error(`Not a regular file: ${relative}`);
|
|
387
|
+
};
|
|
388
|
+
for (const item of this.config.paths) await visit(validatePath(item));
|
|
389
|
+
return result;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Seed a missing baseline from files present both locally and in an existing
|
|
393
|
+
* snapshot. Local-only and unpulled remote-only files stay outside the
|
|
394
|
+
* baseline, so later deletions propagate without deleting unseen remote data.
|
|
395
|
+
*/
|
|
396
|
+
async initializeBaseline(): Promise<boolean> {
|
|
397
|
+
if (this.config.syncedPaths !== undefined) return false;
|
|
398
|
+
const gitDir = path.join(this.config.repoDir, ".git"); await noLinks(gitDir);
|
|
399
|
+
if (!(await stat(gitDir))) return false;
|
|
400
|
+
const local = await this.listPaths(this.paths.agentDir);
|
|
401
|
+
const snapshot = await this.listPaths(this.config.repoDir);
|
|
402
|
+
this.config.syncedPaths = [...local].filter(item => snapshot.has(item)).sort();
|
|
403
|
+
await saveConfig(this.config, this.paths);
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
377
406
|
private async collect(root: string, scan = false): Promise<Files> {
|
|
378
407
|
const files: Files = new Map();
|
|
379
408
|
const visit = async (relative: string): Promise<void> => {
|
package/index.ts
CHANGED
|
@@ -87,12 +87,13 @@ export default function amoxPiSync(pi: ExtensionAPI): void {
|
|
|
87
87
|
fingerprint = undefined;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
function makeService():
|
|
90
|
+
function makeService(): SyncService | undefined {
|
|
91
91
|
service = config ? new SyncService(config, paths, async (args, cwd, timeoutMs) => {
|
|
92
92
|
const result = await pi.exec("git", args, { cwd, timeout: timeoutMs });
|
|
93
93
|
return { stdout: result.stdout, stderr: result.stderr, code: result.code, killed: result.killed };
|
|
94
94
|
}) : undefined;
|
|
95
95
|
if (service) service.activeSessionFile = ctx?.sessionManager.getSessionFile();
|
|
96
|
+
return service;
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
async function run(label: string, action: (sync: SyncService) => Promise<unknown>, quiet = false): Promise<boolean> {
|
|
@@ -445,7 +446,8 @@ export default function amoxPiSync(pi: ExtensionAPI): void {
|
|
|
445
446
|
error = undefined;
|
|
446
447
|
try {
|
|
447
448
|
config = await loadConfig(paths);
|
|
448
|
-
makeService();
|
|
449
|
+
const active = makeService();
|
|
450
|
+
if (active) await active.initializeBaseline();
|
|
449
451
|
} catch (failure) { error = message(failure); context.ui.notify(`amox-pi-sync config: ${error}`, "error"); }
|
|
450
452
|
// Clear the old upstream widget without creating a replacement widget.
|
|
451
453
|
context.ui.setWidget(STATUS_KEY, undefined);
|
package/package.json
CHANGED