@prjct.app/pi-signals 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +4 -2
- package/bin/cli.mjs +23 -0
- package/dist/cli.mjs +2 -1
- package/package.json +6 -4
- package/src/cli.ts +1 -1
- package/src/host.ts +15 -0
- package/src/index.ts +4 -2
- package/src/repair.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## 0.1.2 (2026-10-09)
|
|
2
|
+
|
|
3
|
+
- No system-prompt rule. signal_report no longer asks every session to watch Pi and extensions; its description says what it records and when to inspect targets first.
|
|
4
|
+
|
|
5
|
+
## 0.1.1 (2026-10-07)
|
|
6
|
+
|
|
7
|
+
- Bind the standalone npm CLI to the Pi host's public SDK peers, which Pi intentionally omits from extension installs. Record the host location on session start and use the same bootstrap for scheduled runs.
|
|
8
|
+
- Reject repair candidates that changed their base through an agent-created commit before coordinator verification.
|
|
9
|
+
|
|
1
10
|
## 0.1.0 (2026-10-07)
|
|
2
11
|
|
|
3
12
|
- Capture latency regressions, crashes and concrete response-quality regressions in global `.prjct/shared/signals/signals.sqlite`, with original evidence, versions and model/reasoning provenance.
|
package/README.md
CHANGED
|
@@ -13,10 +13,12 @@ The MVP retains verified commits and branches locally. It does **not** create Gi
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
pi install npm:@prjct.app/pi-signals@0.1.
|
|
16
|
+
pi install npm:@prjct.app/pi-signals@0.1.1
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Node >=22.19 and Pi >=1.0.4 are required. The CLI is shipped inside the npm package; no source checkout or development loader is required. SDKs are peer dependencies and privacy protection comes from the independent `@prjct.app/pi-secrets` dependency. Existing sessions load the extension on an idle `/reload`; installation does not restart them.
|
|
19
|
+
Node >=22.19 and Pi >=1.0.4 are required. The CLI is shipped inside the npm package; no source checkout or development loader is required. SDKs are peer dependencies and privacy protection comes from the independent `@prjct.app/pi-secrets` dependency. Existing sessions load the extension on an idle `/reload`; installation does not restart them. On session start the extension records Pi's public SDK location in `host.json` so the standalone CLI uses the host-provided peers. Before the first Pi load, `PI_SIGNALS_SDK_ROOT` can explicitly supply that installed SDK directory.
|
|
20
|
+
|
|
21
|
+
Pi's private npm bin directory may not be on your shell PATH. In that case use `~/.pi/agent/npm/node_modules/.bin/pi-signals` (adjust for `PI_CODING_AGENT_DIR`). The scheduler records the absolute npm entry point and does not depend on a PATH alias.
|
|
20
22
|
|
|
21
23
|
## Agent tools and commands
|
|
22
24
|
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { registerHooks } from 'node:module';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { pathToFileURL } from 'node:url';
|
|
7
|
+
|
|
8
|
+
// Pi omits its SDK peers when installing extension packages. Bind those peers to
|
|
9
|
+
// the engine that loaded the extension, using normal Node resolution from its root.
|
|
10
|
+
const peers = new Set(['@earendil-works/pi-coding-agent', '@earendil-works/pi-ai', 'typebox']);
|
|
11
|
+
try {
|
|
12
|
+
const root = process.env.PI_SIGNALS_SDK_ROOT ?? JSON.parse(readFileSync(join(process.env.PRJCT_HOME ?? join(homedir(), '.prjct'), 'shared/signals/host.json'), 'utf8')).packageDir;
|
|
13
|
+
const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
14
|
+
if (manifest.name !== '@earendil-works/pi-coding-agent') throw new Error('Expected the installed Pi SDK package');
|
|
15
|
+
const parentURL = pathToFileURL(join(root, 'package.json')).href;
|
|
16
|
+
registerHooks({ resolve(specifier, context, nextResolve) {
|
|
17
|
+
return nextResolve(specifier, peers.has(specifier) ? { ...context, parentURL } : context);
|
|
18
|
+
} });
|
|
19
|
+
} catch {
|
|
20
|
+
console.error('Load pi-signals in Pi once, or set PI_SIGNALS_SDK_ROOT to the installed Pi SDK package directory.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
await import('../dist/cli.mjs');
|
package/dist/cli.mjs
CHANGED
|
@@ -923,6 +923,7 @@ ${JSON.stringify({ issue })}`,
|
|
|
923
923
|
});
|
|
924
924
|
if (!state.fix) return blocked("No structured repair candidate was provided.");
|
|
925
925
|
if (!repo.tests.length) return blocked("No trusted package validation commands are configured.");
|
|
926
|
+
if (await must(path, ["git", "rev-parse", "HEAD"], signal) !== base) return blocked("The agent changed the candidate base before verification; no precommitted changes are accepted.");
|
|
926
927
|
const fix = state.fix;
|
|
927
928
|
const baselinePath = join6(stateDir(), "worktrees", `baseline-${issue.id.slice(0, 12)}-${Date.now()}`);
|
|
928
929
|
await must(repo.path, ["git", "worktree", "add", "--detach", baselinePath, base], signal);
|
|
@@ -1109,7 +1110,7 @@ import { fileURLToPath } from "node:url";
|
|
|
1109
1110
|
async function main() {
|
|
1110
1111
|
const [action = "status", argument] = process.argv.slice(2);
|
|
1111
1112
|
if (action === "schedule" && argument === "install") {
|
|
1112
|
-
return (await Promise.resolve().then(() => (init_scheduler(), scheduler_exports))).installSchedule(fileURLToPath(import.meta.url));
|
|
1113
|
+
return (await Promise.resolve().then(() => (init_scheduler(), scheduler_exports))).installSchedule(fileURLToPath(new URL("../bin/cli.mjs", import.meta.url)));
|
|
1113
1114
|
}
|
|
1114
1115
|
if (action === "once") {
|
|
1115
1116
|
const controller = new AbortController();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prjct.app/pi-signals",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Local failure signals, daily agent-owned triage and verified repair candidates for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"README.md",
|
|
29
29
|
"LICENSE",
|
|
30
30
|
"CHANGELOG.md",
|
|
31
|
-
"dist"
|
|
31
|
+
"dist",
|
|
32
|
+
"bin"
|
|
32
33
|
],
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"@prjct.app/pi-secrets": "^0.2.6"
|
|
@@ -52,9 +53,10 @@
|
|
|
52
53
|
"test": "node --import ./tests/isolate.mjs --import tsx --test --test-concurrency=2 tests/*.test.ts",
|
|
53
54
|
"check:package": "npm pack --dry-run --ignore-scripts",
|
|
54
55
|
"build": "node scripts/build.mjs",
|
|
55
|
-
"prepack": "npm run build"
|
|
56
|
+
"prepack": "npm run build",
|
|
57
|
+
"pretest": "npm run build"
|
|
56
58
|
},
|
|
57
59
|
"bin": {
|
|
58
|
-
"pi-signals": "
|
|
60
|
+
"pi-signals": "bin/cli.mjs"
|
|
59
61
|
}
|
|
60
62
|
}
|
package/src/cli.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { SignalStore } from './store.ts';
|
|
|
6
6
|
async function main() {
|
|
7
7
|
const [action = 'status', argument] = process.argv.slice(2);
|
|
8
8
|
if (action === 'schedule' && argument === 'install') {
|
|
9
|
-
return (await import('./scheduler.ts')).installSchedule(fileURLToPath(import.meta.url));
|
|
9
|
+
return (await import('./scheduler.ts')).installSchedule(fileURLToPath(new URL('../bin/cli.mjs', import.meta.url)));
|
|
10
10
|
}
|
|
11
11
|
if (action === 'once') {
|
|
12
12
|
const controller = new AbortController();
|
package/src/host.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { getPackageDir, VERSION } from '@earendil-works/pi-coding-agent';
|
|
5
|
+
import { stateDir } from './config.ts';
|
|
6
|
+
|
|
7
|
+
/** Record only the host's public SDK location, never credentials or model data. */
|
|
8
|
+
export function rememberHost() {
|
|
9
|
+
const root = stateDir(), file = join(root, 'host.json');
|
|
10
|
+
const value = JSON.stringify({ packageDir: getPackageDir(), version: VERSION }) + '\n';
|
|
11
|
+
try { if (readFileSync(file, 'utf8') === value) return; } catch { /* First load. */ }
|
|
12
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
13
|
+
const temporary = join(root, `host-${randomUUID()}.tmp`);
|
|
14
|
+
writeFileSync(temporary, value, { mode: 0o600 }); renameSync(temporary, file);
|
|
15
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { config, targets, databasePath } from './config.ts';
|
|
|
4
4
|
import { hideLocalPaths, prepareReport } from './report.ts';
|
|
5
5
|
import { InspectSchema, OBSERVATION, ReportSchema } from './schema.ts';
|
|
6
6
|
import { SignalStore } from './store.ts';
|
|
7
|
+
import { rememberHost } from './host.ts';
|
|
7
8
|
|
|
8
9
|
const defaults = { config, targets, protect: protectOutboundData, databasePath };
|
|
9
10
|
export type SignalsDependencies = Partial<typeof defaults>;
|
|
@@ -11,6 +12,7 @@ const result = (value: unknown) => ({ content: [{ type: 'text' as const, text: J
|
|
|
11
12
|
const page = <T>(rows: T[], offset: number) => ({ items: rows.slice(0, 20), nextOffset: rows.length > 20 ? offset + 20 : null });
|
|
12
13
|
export function installSignals(pi: ExtensionAPI, dependencies: SignalsDependencies = {}) {
|
|
13
14
|
const deps = { ...defaults, ...dependencies };
|
|
15
|
+
pi.on('session_start', () => { try { rememberHost(); } catch { /* A host path hint must not interrupt an active session. */ } });
|
|
14
16
|
const catalog = (ctx: ExtensionContext) => {
|
|
15
17
|
const settings = deps.config();
|
|
16
18
|
if (!settings.enabled) throw new Error('Signals is disabled by global configuration.');
|
|
@@ -18,8 +20,8 @@ export function installSignals(pi: ExtensionAPI, dependencies: SignalsDependenci
|
|
|
18
20
|
};
|
|
19
21
|
pi.registerTool({
|
|
20
22
|
name: 'signal_report', label: 'Record failure signal',
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
// No promptGuidelines: a system-prompt rule asked every session to watch Pi, whatever the task.
|
|
24
|
+
description: 'Record a local issue in the global .prjct database for an installed extension or Pi: latency regression, crash, or concrete response-quality regression. Supply a stable defect signature, exact session evidence, reproduction steps, expected and actual behavior. Find targets with signal_inspect first; a detection is a signal, not proof of causality. No GitHub or model request occurs during capture; afterwards, continue the original task.',
|
|
23
25
|
parameters: ReportSchema,
|
|
24
26
|
async execute(_id, input, _signal, _update, ctx) {
|
|
25
27
|
const target = catalog(ctx).find(target => target.id === input.target);
|
package/src/repair.ts
CHANGED
|
@@ -32,6 +32,7 @@ export async function repair(issue: LocalIssue, repo: Repository, path: string,
|
|
|
32
32
|
});
|
|
33
33
|
if (!state.fix) return blocked('No structured repair candidate was provided.');
|
|
34
34
|
if (!repo.tests.length) return blocked('No trusted package validation commands are configured.');
|
|
35
|
+
if (await must(path, ['git', 'rev-parse', 'HEAD'], signal) !== base) return blocked('The agent changed the candidate base before verification; no precommitted changes are accepted.');
|
|
35
36
|
const fix = state.fix;
|
|
36
37
|
const baselinePath = join(stateDir(), 'worktrees', `baseline-${issue.id.slice(0, 12)}-${Date.now()}`);
|
|
37
38
|
await must(repo.path, ['git', 'worktree', 'add', '--detach', baselinePath, base], signal);
|