@poa-box/agent 0.1.0
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/.env.agent.template +20 -0
- package/README.md +46 -0
- package/brain/Config/agent-config.json +14 -0
- package/brain/Config/brain-allowlist.json +20 -0
- package/brain/Identity/goals.template.md +23 -0
- package/brain/Identity/how-i-think.md +406 -0
- package/brain/Identity/who-i-am.template.md +34 -0
- package/brain/Knowledge/BOOTSTRAP.md +66 -0
- package/brain/Knowledge/audit-corpus-index.json +406 -0
- package/brain/Knowledge/discussions.json +245 -0
- package/brain/Knowledge/pop.brain.brainstorms.generated.md +48 -0
- package/brain/Knowledge/pop.brain.brainstorms.genesis.bin +0 -0
- package/brain/Knowledge/pop.brain.heuristics.snapshot.bin +0 -0
- package/brain/Knowledge/pop.brain.projects.generated.md +16 -0
- package/brain/Knowledge/pop.brain.projects.genesis.bin +0 -0
- package/brain/Knowledge/pop.brain.retros.generated.md +91 -0
- package/brain/Knowledge/pop.brain.retros.genesis.bin +0 -0
- package/brain/Knowledge/pop.brain.shared.generated.md +3811 -0
- package/brain/Knowledge/pop.brain.shared.genesis.bin +0 -0
- package/brain/Knowledge/projects.md +181 -0
- package/brain/Knowledge/risk-framework.md +90 -0
- package/brain/Knowledge/shared.md +416 -0
- package/brain/Knowledge/sprint-priorities.md +439 -0
- package/brain/Memory/.gitkeep +0 -0
- package/dist/commands/agent/daily-digest.d.ts +24 -0
- package/dist/commands/agent/daily-digest.js +336 -0
- package/dist/commands/agent/delegate.d.ts +12 -0
- package/dist/commands/agent/delegate.js +91 -0
- package/dist/commands/agent/deploy-to-org.d.ts +20 -0
- package/dist/commands/agent/deploy-to-org.js +154 -0
- package/dist/commands/agent/index.d.ts +2 -0
- package/dist/commands/agent/index.js +27 -0
- package/dist/commands/agent/init.d.ts +19 -0
- package/dist/commands/agent/init.js +303 -0
- package/dist/commands/agent/onboard.d.ts +22 -0
- package/dist/commands/agent/onboard.js +192 -0
- package/dist/commands/agent/paymaster-status.d.ts +14 -0
- package/dist/commands/agent/paymaster-status.js +130 -0
- package/dist/commands/agent/register.d.ts +21 -0
- package/dist/commands/agent/register.js +116 -0
- package/dist/commands/agent/setup-sponsorship.d.ts +22 -0
- package/dist/commands/agent/setup-sponsorship.js +154 -0
- package/dist/commands/agent/status.d.ts +12 -0
- package/dist/commands/agent/status.js +171 -0
- package/dist/commands/agent/triage.d.ts +12 -0
- package/dist/commands/agent/triage.js +503 -0
- package/dist/commands/brain/advance-stage.d.ts +42 -0
- package/dist/commands/brain/advance-stage.js +206 -0
- package/dist/commands/brain/allowlist.d.ts +30 -0
- package/dist/commands/brain/allowlist.js +274 -0
- package/dist/commands/brain/append-lesson.d.ts +55 -0
- package/dist/commands/brain/append-lesson.js +245 -0
- package/dist/commands/brain/brainstorm.d.ts +154 -0
- package/dist/commands/brain/brainstorm.js +573 -0
- package/dist/commands/brain/daemon.d.ts +31 -0
- package/dist/commands/brain/daemon.js +348 -0
- package/dist/commands/brain/doctor.d.ts +27 -0
- package/dist/commands/brain/doctor.js +497 -0
- package/dist/commands/brain/edit-lesson.d.ts +51 -0
- package/dist/commands/brain/edit-lesson.js +248 -0
- package/dist/commands/brain/import-snapshot.d.ts +68 -0
- package/dist/commands/brain/import-snapshot.js +177 -0
- package/dist/commands/brain/index.d.ts +2 -0
- package/dist/commands/brain/index.js +67 -0
- package/dist/commands/brain/list.d.ts +21 -0
- package/dist/commands/brain/list.js +83 -0
- package/dist/commands/brain/migrate-projects.d.ts +44 -0
- package/dist/commands/brain/migrate-projects.js +209 -0
- package/dist/commands/brain/migrate.d.ts +74 -0
- package/dist/commands/brain/migrate.js +306 -0
- package/dist/commands/brain/new-project.d.ts +53 -0
- package/dist/commands/brain/new-project.js +226 -0
- package/dist/commands/brain/read.d.ts +24 -0
- package/dist/commands/brain/read.js +81 -0
- package/dist/commands/brain/remove-lesson.d.ts +47 -0
- package/dist/commands/brain/remove-lesson.js +206 -0
- package/dist/commands/brain/remove-project.d.ts +36 -0
- package/dist/commands/brain/remove-project.js +177 -0
- package/dist/commands/brain/retro-file-tasks.d.ts +84 -0
- package/dist/commands/brain/retro-file-tasks.js +372 -0
- package/dist/commands/brain/retro-list.d.ts +28 -0
- package/dist/commands/brain/retro-list.js +125 -0
- package/dist/commands/brain/retro-mark-change.d.ts +58 -0
- package/dist/commands/brain/retro-mark-change.js +176 -0
- package/dist/commands/brain/retro-remove.d.ts +36 -0
- package/dist/commands/brain/retro-remove.js +142 -0
- package/dist/commands/brain/retro-respond.d.ts +56 -0
- package/dist/commands/brain/retro-respond.js +250 -0
- package/dist/commands/brain/retro-show.d.ts +23 -0
- package/dist/commands/brain/retro-show.js +100 -0
- package/dist/commands/brain/retro-start.d.ts +55 -0
- package/dist/commands/brain/retro-start.js +311 -0
- package/dist/commands/brain/search.d.ts +48 -0
- package/dist/commands/brain/search.js +190 -0
- package/dist/commands/brain/snapshot.d.ts +32 -0
- package/dist/commands/brain/snapshot.js +243 -0
- package/dist/commands/brain/status.d.ts +15 -0
- package/dist/commands/brain/status.js +166 -0
- package/dist/commands/brain/subscribe.d.ts +28 -0
- package/dist/commands/brain/subscribe.js +90 -0
- package/dist/commands/brain/tag.d.ts +46 -0
- package/dist/commands/brain/tag.js +192 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +22 -0
- package/dist/lib/brain-daemon.d.ts +126 -0
- package/dist/lib/brain-daemon.js +811 -0
- package/dist/lib/brain-membership.d.ts +58 -0
- package/dist/lib/brain-membership.js +115 -0
- package/dist/lib/brain-migrate-projects.d.ts +43 -0
- package/dist/lib/brain-migrate-projects.js +247 -0
- package/dist/lib/brain-migrate.d.ts +77 -0
- package/dist/lib/brain-migrate.js +328 -0
- package/dist/lib/brain-ops.d.ts +271 -0
- package/dist/lib/brain-ops.js +571 -0
- package/dist/lib/brain-paths.d.ts +15 -0
- package/dist/lib/brain-paths.js +33 -0
- package/dist/lib/brain-projections.d.ts +216 -0
- package/dist/lib/brain-projections.js +829 -0
- package/dist/lib/brain-schemas.d.ts +36 -0
- package/dist/lib/brain-schemas.js +316 -0
- package/dist/lib/brain-signing.d.ts +103 -0
- package/dist/lib/brain-signing.js +256 -0
- package/dist/lib/brain.d.ts +198 -0
- package/dist/lib/brain.js +1057 -0
- package/dist/pop-agent.d.ts +1 -0
- package/dist/pop-agent.js +18 -0
- package/docs/agent.md +126 -0
- package/docs/agents/brain-anti-entropy.md +127 -0
- package/docs/agents/brain-cross-device-onboarding.md +210 -0
- package/docs/agents/brain-cross-machine-smoke.md +241 -0
- package/docs/agents/brain-layer-setup.md +725 -0
- package/docs/agents/offboarding-protocol.md +188 -0
- package/docs/agents/onboarding-protocol.md +243 -0
- package/docs/agents/running-an-agent.md +200 -0
- package/docs/brain.md +560 -0
- package/package.json +61 -0
- package/scripts/apply.sh +140 -0
- package/scripts/onboard.sh +205 -0
- package/scripts/setup-agent.ts +272 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* pop brain subscribe — keep the process alive and log incoming head-CID
|
|
4
|
+
* announcements on a doc's gossipsub topic. The manual test surface for
|
|
5
|
+
* MVP step 5.
|
|
6
|
+
*
|
|
7
|
+
* Typical 2-terminal smoke test:
|
|
8
|
+
* Terminal A: pop brain subscribe --doc pop.brain.shared
|
|
9
|
+
* Terminal B: (any write that calls applyBrainChange — step 7 ships
|
|
10
|
+
* the write CLI; until then, a direct script or test)
|
|
11
|
+
*
|
|
12
|
+
* Terminal A logs each announcement it sees: docId, CID, claimed author
|
|
13
|
+
* (not trusted — see brain.ts#BrainHeadAnnouncement), the peer it came
|
|
14
|
+
* from. Block fetch + load is step 6.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.subscribeHandler = void 0;
|
|
18
|
+
const brain_1 = require("../../lib/brain");
|
|
19
|
+
exports.subscribeHandler = {
|
|
20
|
+
builder: (yargs) => yargs
|
|
21
|
+
.option('doc', {
|
|
22
|
+
describe: 'Brain document ID (e.g. pop.brain.shared)',
|
|
23
|
+
type: 'string',
|
|
24
|
+
demandOption: true,
|
|
25
|
+
})
|
|
26
|
+
.option('log-only', {
|
|
27
|
+
describe: 'Only log announcements — do NOT fetch blocks or update manifest',
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
default: false,
|
|
30
|
+
}),
|
|
31
|
+
handler: async (argv) => {
|
|
32
|
+
const docId = argv.doc;
|
|
33
|
+
const topic = (0, brain_1.topicForDoc)(docId);
|
|
34
|
+
console.log(`Subscribing to ${topic} ...`);
|
|
35
|
+
const helia = await (0, brain_1.initBrainNode)();
|
|
36
|
+
const peerId = helia.libp2p.peerId.toString();
|
|
37
|
+
console.log(`Local peer: ${peerId}`);
|
|
38
|
+
console.log(`Listening on:`);
|
|
39
|
+
for (const a of helia.libp2p.getMultiaddrs()) {
|
|
40
|
+
console.log(` ${a.toString()}`);
|
|
41
|
+
}
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log('Waiting for announcements. Press Ctrl-C to exit.');
|
|
44
|
+
console.log('');
|
|
45
|
+
const logOnly = argv.logOnly === true;
|
|
46
|
+
if (logOnly) {
|
|
47
|
+
console.log('(log-only mode — will NOT fetch blocks)');
|
|
48
|
+
console.log('');
|
|
49
|
+
}
|
|
50
|
+
const unsubscribe = await (0, brain_1.subscribeBrainTopic)(docId, (ann, from) => {
|
|
51
|
+
const when = new Date(ann.timestamp * 1000).toISOString();
|
|
52
|
+
console.log(`[${when}] head ${ann.cid}`);
|
|
53
|
+
console.log(` doc=${ann.docId} author=${ann.author} from=${from}`);
|
|
54
|
+
if (logOnly)
|
|
55
|
+
return;
|
|
56
|
+
// Step 6: fetch the block via Bitswap, verify, merge, update manifest.
|
|
57
|
+
// Runs async inside the event callback — we fire and forget so
|
|
58
|
+
// the listener doesn't block on slow network fetches.
|
|
59
|
+
(0, brain_1.fetchAndMergeRemoteHead)(ann.docId, ann.cid)
|
|
60
|
+
.then((result) => {
|
|
61
|
+
if (result.action === 'reject') {
|
|
62
|
+
console.log(` -> REJECTED: ${result.reason}`);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log(` -> ${result.action}: ${result.reason}`);
|
|
66
|
+
if ('headCid' in result && result.headCid !== ann.cid) {
|
|
67
|
+
console.log(` new local head = ${result.headCid}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
.catch((err) => {
|
|
72
|
+
console.log(` -> ERROR during sync: ${err.message}`);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
// Keep the process alive. SIGINT cleanly unsubscribes and stops Helia.
|
|
76
|
+
const shutdown = async () => {
|
|
77
|
+
console.log('\nShutting down...');
|
|
78
|
+
try {
|
|
79
|
+
unsubscribe();
|
|
80
|
+
}
|
|
81
|
+
catch { }
|
|
82
|
+
await (0, brain_1.stopBrainNode)();
|
|
83
|
+
process.exit(0);
|
|
84
|
+
};
|
|
85
|
+
process.on('SIGINT', shutdown);
|
|
86
|
+
process.on('SIGTERM', shutdown);
|
|
87
|
+
// Block forever — keepalive so libp2p keeps running.
|
|
88
|
+
await new Promise(() => { });
|
|
89
|
+
},
|
|
90
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pop brain tag — add or remove tags on an existing brain lesson.
|
|
3
|
+
*
|
|
4
|
+
* Task #347 (Retro #1 fallback #6). Companion to `pop brain search`.
|
|
5
|
+
* Tags are an optional `string[]` field on each lesson. Vocabulary is
|
|
6
|
+
* free-form — suggested conventions live in docs/agents/brain-layer-setup.md
|
|
7
|
+
* but nothing enforces them. The goal is search-ability not rigor.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* pop brain tag --doc pop.brain.shared --lesson-id <id> --add tag1,tag2
|
|
11
|
+
* pop brain tag --doc pop.brain.shared --lesson-id <id> --remove old-tag
|
|
12
|
+
*
|
|
13
|
+
* --add and --remove both accept comma-separated lists. At least one must
|
|
14
|
+
* be supplied. Idempotent: re-adding a tag or removing a missing tag is a
|
|
15
|
+
* no-op on the tags array itself but still writes a new head CID (the
|
|
16
|
+
* tag operation is a legitimate change event for the provenance trail).
|
|
17
|
+
*/
|
|
18
|
+
import type { Argv, ArgumentsCamelCase } from 'yargs';
|
|
19
|
+
interface TagArgs {
|
|
20
|
+
doc: string;
|
|
21
|
+
lessonId: string;
|
|
22
|
+
add?: string;
|
|
23
|
+
remove?: string;
|
|
24
|
+
allowInvalidShape?: boolean;
|
|
25
|
+
'idempotency-key'?: string;
|
|
26
|
+
'no-idempotency'?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare const tagHandler: {
|
|
29
|
+
builder: (yargs: Argv) => Argv<{
|
|
30
|
+
doc: string;
|
|
31
|
+
} & {
|
|
32
|
+
"lesson-id": string;
|
|
33
|
+
} & {
|
|
34
|
+
add: string | undefined;
|
|
35
|
+
} & {
|
|
36
|
+
remove: string | undefined;
|
|
37
|
+
} & {
|
|
38
|
+
"allow-invalid-shape": boolean;
|
|
39
|
+
} & {
|
|
40
|
+
"idempotency-key": string | undefined;
|
|
41
|
+
} & {
|
|
42
|
+
"no-idempotency": boolean;
|
|
43
|
+
}>;
|
|
44
|
+
handler: (argv: ArgumentsCamelCase<TagArgs>) => Promise<void>;
|
|
45
|
+
};
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* pop brain tag — add or remove tags on an existing brain lesson.
|
|
4
|
+
*
|
|
5
|
+
* Task #347 (Retro #1 fallback #6). Companion to `pop brain search`.
|
|
6
|
+
* Tags are an optional `string[]` field on each lesson. Vocabulary is
|
|
7
|
+
* free-form — suggested conventions live in docs/agents/brain-layer-setup.md
|
|
8
|
+
* but nothing enforces them. The goal is search-ability not rigor.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* pop brain tag --doc pop.brain.shared --lesson-id <id> --add tag1,tag2
|
|
12
|
+
* pop brain tag --doc pop.brain.shared --lesson-id <id> --remove old-tag
|
|
13
|
+
*
|
|
14
|
+
* --add and --remove both accept comma-separated lists. At least one must
|
|
15
|
+
* be supplied. Idempotent: re-adding a tag or removing a missing tag is a
|
|
16
|
+
* no-op on the tags array itself but still writes a new head CID (the
|
|
17
|
+
* tag operation is a legitimate change event for the provenance trail).
|
|
18
|
+
*/
|
|
19
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
22
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
23
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
24
|
+
}
|
|
25
|
+
Object.defineProperty(o, k2, desc);
|
|
26
|
+
}) : (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
o[k2] = m[k];
|
|
29
|
+
}));
|
|
30
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
31
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
32
|
+
}) : function(o, v) {
|
|
33
|
+
o["default"] = v;
|
|
34
|
+
});
|
|
35
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
36
|
+
var ownKeys = function(o) {
|
|
37
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
38
|
+
var ar = [];
|
|
39
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
40
|
+
return ar;
|
|
41
|
+
};
|
|
42
|
+
return ownKeys(o);
|
|
43
|
+
};
|
|
44
|
+
return function (mod) {
|
|
45
|
+
if (mod && mod.__esModule) return mod;
|
|
46
|
+
var result = {};
|
|
47
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
48
|
+
__setModuleDefault(result, mod);
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
})();
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.tagHandler = void 0;
|
|
54
|
+
const ethers_1 = require("ethers");
|
|
55
|
+
const brain_1 = require("../../lib/brain");
|
|
56
|
+
const brain_ops_1 = require("../../lib/brain-ops");
|
|
57
|
+
const idempotency_1 = require("@poa-box/cli/lib/idempotency");
|
|
58
|
+
const output = __importStar(require("@poa-box/cli/lib/output"));
|
|
59
|
+
function parseList(s) {
|
|
60
|
+
if (!s)
|
|
61
|
+
return [];
|
|
62
|
+
return s
|
|
63
|
+
.split(',')
|
|
64
|
+
.map((x) => x.trim())
|
|
65
|
+
.filter((x) => x.length > 0);
|
|
66
|
+
}
|
|
67
|
+
exports.tagHandler = {
|
|
68
|
+
builder: (yargs) => yargs
|
|
69
|
+
.option('doc', {
|
|
70
|
+
describe: 'Brain document ID (e.g. pop.brain.shared)',
|
|
71
|
+
type: 'string',
|
|
72
|
+
demandOption: true,
|
|
73
|
+
})
|
|
74
|
+
.option('lesson-id', {
|
|
75
|
+
describe: 'The lesson.id of the entry to tag',
|
|
76
|
+
type: 'string',
|
|
77
|
+
demandOption: true,
|
|
78
|
+
})
|
|
79
|
+
.option('add', {
|
|
80
|
+
describe: 'Comma-separated list of tags to add',
|
|
81
|
+
type: 'string',
|
|
82
|
+
})
|
|
83
|
+
.option('remove', {
|
|
84
|
+
describe: 'Comma-separated list of tags to remove',
|
|
85
|
+
type: 'string',
|
|
86
|
+
})
|
|
87
|
+
.option('allow-invalid-shape', {
|
|
88
|
+
describe: 'Bypass write-time schema validation (Task #346).',
|
|
89
|
+
type: 'boolean',
|
|
90
|
+
default: false,
|
|
91
|
+
})
|
|
92
|
+
.option('idempotency-key', {
|
|
93
|
+
type: 'string',
|
|
94
|
+
describe: 'Task #374 (HB#215): explicit idempotency key. Agent-scoped.',
|
|
95
|
+
})
|
|
96
|
+
.option('no-idempotency', {
|
|
97
|
+
type: 'boolean',
|
|
98
|
+
default: false,
|
|
99
|
+
describe: 'Bypass the idempotency cache.',
|
|
100
|
+
})
|
|
101
|
+
.check((argv) => {
|
|
102
|
+
if (!argv.add && !argv.remove) {
|
|
103
|
+
throw new Error('Must supply at least one of --add or --remove');
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}),
|
|
107
|
+
handler: async (argv) => {
|
|
108
|
+
try {
|
|
109
|
+
const addTags = parseList(argv.add);
|
|
110
|
+
const removeTags = parseList(argv.remove);
|
|
111
|
+
// Pre-flight existence check — fail fast with candidate ids if not
|
|
112
|
+
// found. Same UX as edit-lesson / remove-lesson.
|
|
113
|
+
const { doc: currentDoc } = await (0, brain_1.openBrainDoc)(argv.doc);
|
|
114
|
+
const lessons = Array.isArray(currentDoc?.lessons) ? currentDoc.lessons : [];
|
|
115
|
+
const target = lessons.find((l) => l && l.id === argv.lessonId);
|
|
116
|
+
if (!target) {
|
|
117
|
+
const candidates = lessons
|
|
118
|
+
.map((l) => l?.id)
|
|
119
|
+
.filter((id) => typeof id === 'string')
|
|
120
|
+
.slice(0, 8);
|
|
121
|
+
output.error(`Lesson "${argv.lessonId}" not found in ${argv.doc}. ` +
|
|
122
|
+
`Available ids (first 8): ${candidates.join(', ') || '(none)'}`);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Task #374: idempotency check (agent-scoped).
|
|
127
|
+
const signerKey = process.env.POP_PRIVATE_KEY;
|
|
128
|
+
const authorScope = signerKey ? new ethers_1.ethers.Wallet(signerKey).address.toLowerCase() : 'anonymous';
|
|
129
|
+
const idempKey = argv.idempotencyKey || (0, idempotency_1.argvToIdempotencyString)(argv);
|
|
130
|
+
if (!argv.noIdempotency) {
|
|
131
|
+
const cached = (0, idempotency_1.checkIdempotencyCache)(authorScope, 'brain.tag', idempKey);
|
|
132
|
+
if (cached) {
|
|
133
|
+
if (output.isJsonMode()) {
|
|
134
|
+
output.json({ status: 'ok', cached: true, ...cached });
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log(` Lesson "${argv.lessonId}" tags already updated (idempotency cache hit)`);
|
|
139
|
+
console.log(` head: ${cached.headCid}`);
|
|
140
|
+
console.log('');
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const result = await (0, brain_ops_1.routedDispatch)({
|
|
146
|
+
type: 'tagLesson',
|
|
147
|
+
docId: argv.doc,
|
|
148
|
+
lessonId: argv.lessonId,
|
|
149
|
+
addTags,
|
|
150
|
+
removeTags,
|
|
151
|
+
allowInvalidShape: argv.allowInvalidShape,
|
|
152
|
+
});
|
|
153
|
+
if (!argv.noIdempotency) {
|
|
154
|
+
(0, idempotency_1.recordIdempotentResult)(authorScope, 'brain.tag', idempKey, {
|
|
155
|
+
docId: argv.doc,
|
|
156
|
+
lessonId: argv.lessonId,
|
|
157
|
+
added: addTags,
|
|
158
|
+
removed: removeTags,
|
|
159
|
+
headCid: result.headCid,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (output.isJsonMode()) {
|
|
163
|
+
output.json({
|
|
164
|
+
status: 'ok',
|
|
165
|
+
docId: argv.doc,
|
|
166
|
+
lessonId: argv.lessonId,
|
|
167
|
+
added: addTags,
|
|
168
|
+
removed: removeTags,
|
|
169
|
+
headCid: result.headCid,
|
|
170
|
+
routedViaDaemon: result.routedViaDaemon,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
console.log('');
|
|
175
|
+
console.log(` Lesson "${argv.lessonId}" tags updated in ${argv.doc}`);
|
|
176
|
+
if (addTags.length > 0)
|
|
177
|
+
console.log(` added: ${addTags.join(', ')}`);
|
|
178
|
+
if (removeTags.length > 0)
|
|
179
|
+
console.log(` removed: ${removeTags.join(', ')}`);
|
|
180
|
+
console.log(` head: ${result.headCid}`);
|
|
181
|
+
console.log('');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
output.error(err.message);
|
|
186
|
+
process.exitCode = 1;
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
await (0, brain_1.stopBrainNode)();
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @poa-box/agent — agent + brain command surface for the POP CLI.
|
|
3
|
+
*
|
|
4
|
+
* This module is the plugin contract with @poa-box/cli: the CLI probes for this
|
|
5
|
+
* package at startup (require('@poa-box/agent'), falling back to the in-repo
|
|
6
|
+
* packages/agent/dist path) and, when present, registers these two command
|
|
7
|
+
* groups. Visibility is the CLI's decision — hidden from `pop --help` unless
|
|
8
|
+
* POP_AGENT_MODE=1, always visible under the `pop-agent` bin.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here may import the p2p/CRDT stack at module load. The brain
|
|
11
|
+
* runtime (libp2p, helia, automerge) is loaded lazily inside lib/brain.ts via
|
|
12
|
+
* an ESM-import bridge, and must stay that way: this module loads on EVERY
|
|
13
|
+
* `pop` invocation when the package is installed, and a human running
|
|
14
|
+
* `pop task list` must not pay for a gossip mesh they will never use.
|
|
15
|
+
*/
|
|
16
|
+
export { registerAgentCommands } from './commands/agent';
|
|
17
|
+
export { registerBrainCommands } from './commands/brain';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @poa-box/agent — agent + brain command surface for the POP CLI.
|
|
4
|
+
*
|
|
5
|
+
* This module is the plugin contract with @poa-box/cli: the CLI probes for this
|
|
6
|
+
* package at startup (require('@poa-box/agent'), falling back to the in-repo
|
|
7
|
+
* packages/agent/dist path) and, when present, registers these two command
|
|
8
|
+
* groups. Visibility is the CLI's decision — hidden from `pop --help` unless
|
|
9
|
+
* POP_AGENT_MODE=1, always visible under the `pop-agent` bin.
|
|
10
|
+
*
|
|
11
|
+
* Nothing here may import the p2p/CRDT stack at module load. The brain
|
|
12
|
+
* runtime (libp2p, helia, automerge) is loaded lazily inside lib/brain.ts via
|
|
13
|
+
* an ESM-import bridge, and must stay that way: this module loads on EVERY
|
|
14
|
+
* `pop` invocation when the package is installed, and a human running
|
|
15
|
+
* `pop task list` must not pay for a gossip mesh they will never use.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.registerBrainCommands = exports.registerAgentCommands = void 0;
|
|
19
|
+
var agent_1 = require("./commands/agent");
|
|
20
|
+
Object.defineProperty(exports, "registerAgentCommands", { enumerable: true, get: function () { return agent_1.registerAgentCommands; } });
|
|
21
|
+
var brain_1 = require("./commands/brain");
|
|
22
|
+
Object.defineProperty(exports, "registerBrainCommands", { enumerable: true, get: function () { return brain_1.registerBrainCommands; } });
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brain daemon — persistent libp2p process that keeps gossipsub alive between
|
|
3
|
+
* agent CLI invocations and periodically re-broadcasts the local manifest's
|
|
4
|
+
* head CIDs so peers coming online can catch up.
|
|
5
|
+
*
|
|
6
|
+
* ## Why
|
|
7
|
+
*
|
|
8
|
+
* The brain layer's HB#322 dogfood finding: three consecutive vigil_01 brain
|
|
9
|
+
* writes were invisible to argus_prime because agent sessions run in separate
|
|
10
|
+
* 15-min cron slots and never overlap in wall-clock time. Gossipsub is
|
|
11
|
+
* broadcast-only (no store-and-forward), so announcements published while a
|
|
12
|
+
* peer is offline vanish permanently. Every agent ended up with a per-agent
|
|
13
|
+
* append-only journal, not a shared substrate.
|
|
14
|
+
*
|
|
15
|
+
* Hudson's HB#322 directive: fix it properly with a long-running daemon. No
|
|
16
|
+
* shortcuts. Model it on the go-ds-crdt reference architecture
|
|
17
|
+
* (github.com/ipfs/go-ds-crdt, examples/globaldb/globaldb.go).
|
|
18
|
+
*
|
|
19
|
+
* ## Design (mapped from go-ds-crdt)
|
|
20
|
+
*
|
|
21
|
+
* go-ds-crdt | This module
|
|
22
|
+
* -----------------------------|--------------------------------------
|
|
23
|
+
* Datastore (long-lived) | runDaemon() — single process per agent
|
|
24
|
+
* Broadcaster (PubSub) | existing publishBrainHead / subscribeBrainTopic
|
|
25
|
+
* DAGSyncer (DAGService) | existing Helia blockstore + Bitswap
|
|
26
|
+
* RebroadcastInterval = 1m | REBROADCAST_INTERVAL_MS = 60_000
|
|
27
|
+
* keepalive netTopic (20s) | KEEPALIVE_TOPIC + 20s interval
|
|
28
|
+
* seenHeads map | DEFERRED — v1 rebroadcasts unconditionally
|
|
29
|
+
* RepairInterval = 1h | DEFERRED — MVP envelope is snapshot-per-write
|
|
30
|
+
* | so there's no DAG to walk
|
|
31
|
+
* signal handling | SIGTERM / SIGINT / SIGHUP
|
|
32
|
+
* PutHook / DeleteHook | DEFERRED — v2 adds IPC routing so existing
|
|
33
|
+
* | commands become clients
|
|
34
|
+
*
|
|
35
|
+
* ## Process model
|
|
36
|
+
*
|
|
37
|
+
* pop brain daemon start parent spawns a detached child running
|
|
38
|
+
* `node dist/index.js brain daemon __run`;
|
|
39
|
+
* parent writes PID file and exits
|
|
40
|
+
* pop brain daemon __run the child entrypoint, calls runDaemon()
|
|
41
|
+
* pop brain daemon stop reads PID, sends SIGTERM, waits cleanup
|
|
42
|
+
* pop brain daemon status reads PID, checks liveness, opens Unix
|
|
43
|
+
* socket, sends {method: "status"}, prints
|
|
44
|
+
* pop brain daemon logs tails daemon.log
|
|
45
|
+
*
|
|
46
|
+
* ${POP_BRAIN_HOME}/daemon.pid parent-written PID file
|
|
47
|
+
* ${POP_BRAIN_HOME}/daemon.sock Unix socket for IPC (mode 0600)
|
|
48
|
+
* ${POP_BRAIN_HOME}/daemon.log append-only log
|
|
49
|
+
*
|
|
50
|
+
* ## IPC protocol
|
|
51
|
+
*
|
|
52
|
+
* Newline-delimited JSON over Unix socket. Each request is one line:
|
|
53
|
+
*
|
|
54
|
+
* {"id": "1", "method": "status"}
|
|
55
|
+
*
|
|
56
|
+
* Each response is one line:
|
|
57
|
+
*
|
|
58
|
+
* {"id": "1", "result": {...}}
|
|
59
|
+
* {"id": "1", "error": "..."}
|
|
60
|
+
*
|
|
61
|
+
* First-ship methods: status. Second-ship methods: appendLesson, readDoc,
|
|
62
|
+
* snapshot — those let existing CLI commands route through a running daemon
|
|
63
|
+
* instead of spinning up their own libp2p.
|
|
64
|
+
*/
|
|
65
|
+
export declare const REBROADCAST_INTERVAL_MS = 60000;
|
|
66
|
+
export declare const REBROADCAST_JITTER = 0.3;
|
|
67
|
+
export declare const REBROADCAST_GRACE_MS = 5000;
|
|
68
|
+
export declare const KEEPALIVE_INTERVAL_MS = 20000;
|
|
69
|
+
export declare const REDIAL_INTERVAL_MS = 30000;
|
|
70
|
+
export declare const KEEPALIVE_TOPIC = "pop/brain/net/v1";
|
|
71
|
+
/**
|
|
72
|
+
* Canonical brain docs every daemon subscribes to at startup regardless
|
|
73
|
+
* of local manifest state. A fresh brain home has an empty manifest, so
|
|
74
|
+
* without this list the daemon would not subscribe to any doc topics
|
|
75
|
+
* and could never receive remote head announcements for
|
|
76
|
+
* `pop.brain.shared` / `pop.brain.projects` until after its first
|
|
77
|
+
* local write.
|
|
78
|
+
*
|
|
79
|
+
* Adding a new canonical doc here makes every daemon pick it up on
|
|
80
|
+
* next restart. To experiment with a non-canonical doc, just perform a
|
|
81
|
+
* local write via `pop brain append-lesson --doc <id>` — the write
|
|
82
|
+
* path adds the doc to the manifest, and the next daemon loop iteration
|
|
83
|
+
* picks it up via listBrainDocs().
|
|
84
|
+
*/
|
|
85
|
+
export declare const CANONICAL_BRAIN_DOCS: string[];
|
|
86
|
+
export declare function getDaemonPidPath(): string;
|
|
87
|
+
export declare function getDaemonSockPath(): string;
|
|
88
|
+
export declare function getDaemonLogPath(): string;
|
|
89
|
+
/**
|
|
90
|
+
* Check if a daemon appears to be running for this brain home.
|
|
91
|
+
* Returns the PID if alive, null otherwise. Cleans up stale PID files.
|
|
92
|
+
*/
|
|
93
|
+
export declare function getRunningDaemonPid(): number | null;
|
|
94
|
+
/**
|
|
95
|
+
* Run the daemon event loop. Blocks until a termination signal arrives.
|
|
96
|
+
* This is the __run entrypoint invoked by the detached child process.
|
|
97
|
+
*
|
|
98
|
+
* NEVER call this from the parent CLI path — it will never return.
|
|
99
|
+
*/
|
|
100
|
+
export declare function runDaemon(): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Typed IPC error. Attaches a `.code` for the caller to branch on.
|
|
103
|
+
*
|
|
104
|
+
* phase = 'pre-connect' The connection was never established (socket
|
|
105
|
+
* missing, ECONNREFUSED). Safe to fall back to a
|
|
106
|
+
* local execution path — the write did not land
|
|
107
|
+
* in the daemon's process.
|
|
108
|
+
* phase = 'post-connect' The connection was established and the request
|
|
109
|
+
* was sent, but a response did not come back. The
|
|
110
|
+
* write may or may not have landed. NOT safe to
|
|
111
|
+
* fall back — see routedDispatch() in brain-ops.ts.
|
|
112
|
+
*/
|
|
113
|
+
export declare class BrainIpcError extends Error {
|
|
114
|
+
code: string;
|
|
115
|
+
phase: 'pre-connect' | 'post-connect';
|
|
116
|
+
constructor(message: string, code: string, phase: 'pre-connect' | 'post-connect');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* IPC client helper: send a request to the running daemon.
|
|
120
|
+
*
|
|
121
|
+
* Throws a BrainIpcError whose `.phase` indicates whether the failure is
|
|
122
|
+
* safe to recover from by falling back to a local code path. Pre-connect
|
|
123
|
+
* failures (ECONNREFUSED, ENOENT, daemon not running) are safe. Post-connect
|
|
124
|
+
* failures (timeout, ECONNRESET, EPIPE) leave the write in an unknown state.
|
|
125
|
+
*/
|
|
126
|
+
export declare function sendIpcRequest(method: string, params?: any, timeoutMs?: number): Promise<any>;
|