@wenrwa/marketplace-cli 0.8.7 → 0.8.8
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/dist/__tests__/bridge-session-anchor-no-identity.test.d.ts +1 -0
- package/dist/__tests__/bridge-session-anchor-no-identity.test.js +74 -0
- package/dist/__tests__/bridge-session-anchor.test.js +10 -2
- package/dist/bridge/sessionAnchor.d.ts +9 -0
- package/dist/bridge/sessionAnchor.js +16 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* A session anchor must not need a git identity.
|
|
5
|
+
*
|
|
6
|
+
* Production 2026-10-19 (owner: "the Changes button doesn't seem to work"):
|
|
7
|
+
* a hosted box has NO git identity — no user.name/user.email in any config;
|
|
8
|
+
* the agent's identity rides only in the PTY env (GIT_AUTHOR_*). The bridge's
|
|
9
|
+
* own `git commit-tree` therefore died with "Author identity unknown" (exit
|
|
10
|
+
* 128, probed on the live machine), no refs/wenrwa/session-* ref was ever
|
|
11
|
+
* created, and no session-scoped Changes report has ever been persisted (zero
|
|
12
|
+
* `scope = 'session'` rows in terminal_changes).
|
|
13
|
+
*
|
|
14
|
+
* This runs REAL git with `user.useConfigOnly = true` and no identity, which
|
|
15
|
+
* is the container's situation made deterministic (a laptop would otherwise
|
|
16
|
+
* auto-detect user@host and hide the bug).
|
|
17
|
+
*/
|
|
18
|
+
const fs_1 = require("fs");
|
|
19
|
+
const os_1 = require("os");
|
|
20
|
+
const path_1 = require("path");
|
|
21
|
+
const child_process_1 = require("child_process");
|
|
22
|
+
const sessionAnchor_1 = require("../bridge/sessionAnchor");
|
|
23
|
+
const changesReporter_1 = require("../bridge/changesReporter");
|
|
24
|
+
// Jest's sandboxed process.env does not reach child_process on its own, so
|
|
25
|
+
// every git call here passes the (identity-less) env explicitly — the same
|
|
26
|
+
// merge execGit does in production.
|
|
27
|
+
const sandboxEnv = () => ({ ...process.env });
|
|
28
|
+
const run = (cmd, args, opts) => (0, changesReporter_1.execGit)(cmd, args, { env: { ...sandboxEnv(), ...(opts?.env ?? {}) } });
|
|
29
|
+
const git = (args) => (0, child_process_1.execFileSync)('git', args, { env: sandboxEnv(), stdio: 'pipe' }).toString().trim();
|
|
30
|
+
const IDENTITY_KEYS = ['GIT_AUTHOR_NAME', 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_NAME', 'GIT_COMMITTER_EMAIL', 'EMAIL'];
|
|
31
|
+
describe('createSessionAnchor on a box with no git identity', () => {
|
|
32
|
+
let dir;
|
|
33
|
+
let repo;
|
|
34
|
+
const saved = {};
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
dir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'anchor-noid-'));
|
|
37
|
+
repo = (0, path_1.join)(dir, 'repo');
|
|
38
|
+
const globalCfg = (0, path_1.join)(dir, 'gitconfig');
|
|
39
|
+
(0, fs_1.writeFileSync)(globalCfg, '[user]\n\tuseConfigOnly = true\n');
|
|
40
|
+
for (const k of [...IDENTITY_KEYS, 'GIT_CONFIG_GLOBAL', 'GIT_CONFIG_NOSYSTEM', 'HOME'])
|
|
41
|
+
saved[k] = process.env[k];
|
|
42
|
+
for (const k of IDENTITY_KEYS)
|
|
43
|
+
delete process.env[k];
|
|
44
|
+
process.env.GIT_CONFIG_GLOBAL = globalCfg;
|
|
45
|
+
process.env.GIT_CONFIG_NOSYSTEM = '1';
|
|
46
|
+
process.env.HOME = dir;
|
|
47
|
+
git(['init', '-q', repo]);
|
|
48
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(repo, 'a.txt'), 'hello\n');
|
|
49
|
+
});
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
for (const [k, v] of Object.entries(saved)) {
|
|
52
|
+
if (v === undefined)
|
|
53
|
+
delete process.env[k];
|
|
54
|
+
else
|
|
55
|
+
process.env[k] = v;
|
|
56
|
+
}
|
|
57
|
+
(0, fs_1.rmSync)(dir, { recursive: true, force: true });
|
|
58
|
+
});
|
|
59
|
+
it('git itself refuses a commit here (the precondition is real)', () => {
|
|
60
|
+
const tree = git(['-C', repo, 'write-tree']);
|
|
61
|
+
expect(() => git(['-C', repo, 'commit-tree', tree, '-m', 'x'])).toThrow(/identity unknown|auto-detection is disabled/i);
|
|
62
|
+
});
|
|
63
|
+
it('still creates the anchor commit and its ref', async () => {
|
|
64
|
+
const res = await (0, sessionAnchor_1.createSessionAnchor)(repo, 'sess-noid', { run });
|
|
65
|
+
expect(res.ok).toBe(true);
|
|
66
|
+
if (!res.ok)
|
|
67
|
+
return;
|
|
68
|
+
const refSha = git(['-C', repo, 'rev-parse', (0, sessionAnchor_1.anchorRefName)('sess-noid')]);
|
|
69
|
+
expect(refSha).toBe(res.sha);
|
|
70
|
+
// The anchor snapshots the untracked file, so a later diff is relative to it.
|
|
71
|
+
const files = git(['-C', repo, 'ls-tree', '--name-only', res.sha]);
|
|
72
|
+
expect(files).toBe('a.txt');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -68,9 +68,17 @@ describe('createSessionAnchor', () => {
|
|
|
68
68
|
expect(calls[1].args).toEqual(['-C', CWD, 'write-tree']);
|
|
69
69
|
expect(calls[1].opts?.env?.GIT_INDEX_FILE).toBe(indexFile);
|
|
70
70
|
// 3. commit-tree — no parent (a fresh anchor, not a chain), no index
|
|
71
|
-
// involved at all (commit-tree never reads one)
|
|
71
|
+
// involved at all (commit-tree never reads one). Its only env is a
|
|
72
|
+
// fixed identity: a hosted box has none (see
|
|
73
|
+
// bridge-session-anchor-no-identity.test.ts).
|
|
72
74
|
expect(calls[2].args).toEqual(['-C', CWD, 'commit-tree', 'treesha0000', '-m', 'wenrwa session anchor']);
|
|
73
|
-
expect(calls[2].opts?.env).
|
|
75
|
+
expect(calls[2].opts?.env).toEqual({
|
|
76
|
+
GIT_AUTHOR_NAME: 'HiveOp',
|
|
77
|
+
GIT_AUTHOR_EMAIL: 'session-anchor@hiveop.local',
|
|
78
|
+
GIT_COMMITTER_NAME: 'HiveOp',
|
|
79
|
+
GIT_COMMITTER_EMAIL: 'session-anchor@hiveop.local',
|
|
80
|
+
});
|
|
81
|
+
expect(calls[2].opts?.env?.GIT_INDEX_FILE).toBeUndefined();
|
|
74
82
|
// 4. update-ref — points the session's ref at the new commit, no env.
|
|
75
83
|
expect(calls[3].args).toEqual(['-C', CWD, 'update-ref', 'refs/wenrwa/session-sess-1', 'commitsha1111']);
|
|
76
84
|
expect(calls[3].opts?.env).toBeUndefined();
|
|
@@ -22,6 +22,15 @@ export type SweepAnchorsResult = {
|
|
|
22
22
|
ok: false;
|
|
23
23
|
error: string;
|
|
24
24
|
};
|
|
25
|
+
/** The fixed author/committer of every anchor commit — see createSessionAnchor. */
|
|
26
|
+
export declare const ANCHOR_IDENTITY: {
|
|
27
|
+
env: {
|
|
28
|
+
GIT_AUTHOR_NAME: string;
|
|
29
|
+
GIT_AUTHOR_EMAIL: string;
|
|
30
|
+
GIT_COMMITTER_NAME: string;
|
|
31
|
+
GIT_COMMITTER_EMAIL: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
25
34
|
/** Every session anchor ref lives under this namespace — sweepStaleAnchors's glob and this function must agree. */
|
|
26
35
|
export declare function anchorRefName(sessionId: string): string;
|
|
27
36
|
/**
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ANCHOR_IDENTITY = void 0;
|
|
36
37
|
exports.anchorRefName = anchorRefName;
|
|
37
38
|
exports.createSessionAnchor = createSessionAnchor;
|
|
38
39
|
exports.releaseSessionAnchor = releaseSessionAnchor;
|
|
@@ -80,6 +81,15 @@ exports.sweepStaleAnchors = sweepStaleAnchors;
|
|
|
80
81
|
const os = __importStar(require("os"));
|
|
81
82
|
const path = __importStar(require("path"));
|
|
82
83
|
const crypto_1 = require("crypto");
|
|
84
|
+
/** The fixed author/committer of every anchor commit — see createSessionAnchor. */
|
|
85
|
+
exports.ANCHOR_IDENTITY = {
|
|
86
|
+
env: {
|
|
87
|
+
GIT_AUTHOR_NAME: 'HiveOp',
|
|
88
|
+
GIT_AUTHOR_EMAIL: 'session-anchor@hiveop.local',
|
|
89
|
+
GIT_COMMITTER_NAME: 'HiveOp',
|
|
90
|
+
GIT_COMMITTER_EMAIL: 'session-anchor@hiveop.local',
|
|
91
|
+
},
|
|
92
|
+
};
|
|
83
93
|
/** Every session anchor ref lives under this namespace — sweepStaleAnchors's glob and this function must agree. */
|
|
84
94
|
function anchorRefName(sessionId) {
|
|
85
95
|
return `refs/wenrwa/session-${sessionId}`;
|
|
@@ -114,7 +124,12 @@ async function createSessionAnchor(cwd, sessionId, deps) {
|
|
|
114
124
|
const tree = treeRes.stdout.trim();
|
|
115
125
|
// commit-tree never reads an index — no env override, and deliberately
|
|
116
126
|
// no `-p <parent>`: this commit is a floating snapshot, not history.
|
|
117
|
-
|
|
127
|
+
// The identity is set HERE, never inherited: a hosted box has no git
|
|
128
|
+
// identity at all (the agent's rides only in its PTY env), and without
|
|
129
|
+
// one commit-tree dies "Author identity unknown" — which is why no hosted
|
|
130
|
+
// session report had ever been produced (prod 2026-10-19). The anchor is
|
|
131
|
+
// a hidden ref nobody pushes, so a fixed identity costs nothing.
|
|
132
|
+
const commitRes = await deps.run('git', ['-C', cwd, 'commit-tree', tree, '-m', 'wenrwa session anchor'], exports.ANCHOR_IDENTITY);
|
|
118
133
|
if (commitRes.code !== 0) {
|
|
119
134
|
return { ok: false, error: `anchor-commit-failed: ${commitRes.stdout.trim() || 'git commit-tree failed'}` };
|
|
120
135
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenrwa/marketplace-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
4
4
|
"description": "CLI tool for the Wenrwa Agent Marketplace — monitor bounties, agents, workspaces, and treasury from your terminal",
|
|
5
5
|
"author": "Wenrwa <dev@wenrwa.com>",
|
|
6
6
|
"license": "MIT",
|