@danypops/papyrus 0.8.0 → 0.9.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/README.md +3 -1
- package/extension/src/context-injection-telemetry.ts +80 -0
- package/extension/src/index.ts +18 -11
- package/package.json +1 -1
- package/src/constants.ts +4 -0
package/README.md
CHANGED
|
@@ -174,7 +174,9 @@ checklist: {
|
|
|
174
174
|
|
|
175
175
|
Proof types are `file`, `symbol`, `code`, `test`, `command`, `artifact`, and `url`. Existing array checklists remain readable as legacy items with `proof: missing`; Papyrus does not invent evidence.
|
|
176
176
|
|
|
177
|
-
Papyrus also injects an Alef-style reconciliation block
|
|
177
|
+
Papyrus also injects an Alef-style reconciliation block at `before_agent_start` while work remains: `Current`, `Desired`, `Verify`, and `Next`. The agent is explicitly instructed to ask **“Did we accomplish this task?”** and run review before marking it done. The injection disappears when every task is done or canceled.
|
|
178
|
+
|
|
179
|
+
After assembling each system-prompt addition, Papyrus emits a versioned `papyrus.context-injection.v1` observation on Pi's shared extension event bus. It contains only exact byte/character sizes, Rule count, a labeled token estimate, prompt share, sequence, and a SHA-256 payload fingerprint; Rule/Task text, prompts, project paths, and credentials are never included. Jittor can persist and assess these observations without Papyrus maintaining a second telemetry store.
|
|
178
180
|
|
|
179
181
|
Task edits mutate the existing Papyrus-owned Task identity and append an `updated` event; title, body, and labels can be revised without canceling the Task or creating a replacement. Lifecycle, relationships, gates, checklist metadata, scope, and focus remain intact.
|
|
180
182
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN,
|
|
4
|
+
PAPYRUS_CONTEXT_INJECTION_SCHEMA,
|
|
5
|
+
} from "../../src/constants.ts";
|
|
6
|
+
import type { Artifact } from "../../src/domain/artifact.ts";
|
|
7
|
+
import { ruleInjectionPreview } from "./rules.ts";
|
|
8
|
+
|
|
9
|
+
export interface ContextPayloadSize {
|
|
10
|
+
characters: number;
|
|
11
|
+
bytes: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PapyrusContextInjectionObservation {
|
|
15
|
+
schema: typeof PAPYRUS_CONTEXT_INJECTION_SCHEMA;
|
|
16
|
+
observedAt: number;
|
|
17
|
+
sequence: number;
|
|
18
|
+
producerId: string;
|
|
19
|
+
before: ContextPayloadSize;
|
|
20
|
+
rules: ContextPayloadSize & { count: number };
|
|
21
|
+
tasks: ContextPayloadSize;
|
|
22
|
+
injected: ContextPayloadSize;
|
|
23
|
+
after: ContextPayloadSize;
|
|
24
|
+
estimatedTokens: number;
|
|
25
|
+
share: number;
|
|
26
|
+
fingerprint: string;
|
|
27
|
+
unchanged: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface BuildContextInjectionInput {
|
|
31
|
+
basePrompt: string;
|
|
32
|
+
rules: Array<Pick<Artifact, "title" | "body" | "extra">>;
|
|
33
|
+
taskSummary: string | null;
|
|
34
|
+
observedAt: number;
|
|
35
|
+
sequence: number;
|
|
36
|
+
producerId: string;
|
|
37
|
+
previousFingerprint?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const encoder = new TextEncoder();
|
|
41
|
+
|
|
42
|
+
function size(value: string): ContextPayloadSize {
|
|
43
|
+
return { characters: value.length, bytes: encoder.encode(value).byteLength };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function buildContextInjection(input: BuildContextInjectionInput): {
|
|
47
|
+
prompt: string;
|
|
48
|
+
ruleBlock: string;
|
|
49
|
+
taskBlock: string;
|
|
50
|
+
observation: PapyrusContextInjectionObservation;
|
|
51
|
+
} {
|
|
52
|
+
const ruleContent = input.rules.map(ruleInjectionPreview).join("\n");
|
|
53
|
+
const ruleBlock = ruleContent ? `\n\n## Active rules (Papyrus)\n\n${ruleContent}\n` : "";
|
|
54
|
+
const taskBlock = input.taskSummary ? `\n\n## Open tasks (Papyrus)\n\n${input.taskSummary}\n` : "";
|
|
55
|
+
const injected = `${ruleBlock}${taskBlock}`;
|
|
56
|
+
const prompt = `${input.basePrompt}${injected}`;
|
|
57
|
+
const fingerprint = createHash("sha256").update(injected).digest("hex");
|
|
58
|
+
const injectedSize = size(injected);
|
|
59
|
+
const afterSize = size(prompt);
|
|
60
|
+
return {
|
|
61
|
+
prompt,
|
|
62
|
+
ruleBlock,
|
|
63
|
+
taskBlock,
|
|
64
|
+
observation: {
|
|
65
|
+
schema: PAPYRUS_CONTEXT_INJECTION_SCHEMA,
|
|
66
|
+
observedAt: input.observedAt,
|
|
67
|
+
sequence: input.sequence,
|
|
68
|
+
producerId: input.producerId,
|
|
69
|
+
before: size(input.basePrompt),
|
|
70
|
+
rules: { ...size(ruleBlock), count: input.rules.length },
|
|
71
|
+
tasks: size(taskBlock),
|
|
72
|
+
injected: injectedSize,
|
|
73
|
+
after: afterSize,
|
|
74
|
+
estimatedTokens: Math.ceil(injectedSize.characters / CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN),
|
|
75
|
+
share: afterSize.characters === 0 ? 0 : injectedSize.characters / afterSize.characters,
|
|
76
|
+
fingerprint,
|
|
77
|
+
unchanged: input.previousFingerprint === fingerprint,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
package/extension/src/index.ts
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
* Injection: active rules + open tasks appended to system prompt every turn.
|
|
8
8
|
* "Are we there yet?" — the agent sees its open work items.
|
|
9
9
|
*/
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
10
11
|
import type { ExtensionAPI, ExtensionContext, ExtensionUIContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
11
12
|
import { Type } from "typebox";
|
|
12
13
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
13
14
|
import {
|
|
14
15
|
TASK_DRIVER_MAX_TURNS,
|
|
15
16
|
TASK_DRIVER_MAX_UNCHANGED_TURNS,
|
|
17
|
+
PAPYRUS_CONTEXT_INJECTION_CHANNEL,
|
|
16
18
|
} from "../../src/constants.ts";
|
|
17
19
|
import type { Artifact } from "../../src/domain/artifact.ts";
|
|
18
20
|
import type { GateResult } from "../../src/domain/gate.ts";
|
|
@@ -23,6 +25,7 @@ import type { TaskGraph, TaskStatus } from "../../src/task-service.ts";
|
|
|
23
25
|
import { ActiveTaskContinuation, automaticPauseReason, shouldResumeFocusOnHumanInput, type ActiveTaskMarker } from "./active-task-continuation.ts";
|
|
24
26
|
import { buildTaskWidgetProjection, type TaskWidgetProjection } from "./task-widget.ts";
|
|
25
27
|
import { TASK_STATUS_PRESENTATION, taskTreeConnector } from "./task-presentation.ts";
|
|
28
|
+
import { buildContextInjection } from "./context-injection-telemetry.ts";
|
|
26
29
|
|
|
27
30
|
function text(t: string, details: Record<string, unknown> = {}) {
|
|
28
31
|
return { content: [{ type: "text" as const, text: t }], details };
|
|
@@ -130,6 +133,9 @@ class TaskOverlay {
|
|
|
130
133
|
|
|
131
134
|
export default async function (pi: ExtensionAPI) {
|
|
132
135
|
registerDomainTools(pi);
|
|
136
|
+
let contextInjectionSequence = 0;
|
|
137
|
+
const contextInjectionProducerId = randomUUID();
|
|
138
|
+
let previousContextInjectionFingerprint: string | undefined;
|
|
133
139
|
const taskContinuation = new ActiveTaskContinuation({
|
|
134
140
|
maxTurns: TASK_DRIVER_MAX_TURNS,
|
|
135
141
|
maxUnchangedTurns: TASK_DRIVER_MAX_UNCHANGED_TURNS,
|
|
@@ -391,17 +397,18 @@ export default async function (pi: ExtensionAPI) {
|
|
|
391
397
|
callService<Record<string, unknown>, Array<Pick<Artifact, "title" | "body" | "extra">>>("rules.injectable", { project_root: ctx.cwd }),
|
|
392
398
|
callService<Record<string, unknown>, string | null>("tasks.context", { project_root: ctx.cwd }),
|
|
393
399
|
]);
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
400
|
+
const injection = buildContextInjection({
|
|
401
|
+
basePrompt: event.systemPrompt ?? "",
|
|
402
|
+
rules,
|
|
403
|
+
taskSummary: summary,
|
|
404
|
+
observedAt: Date.now(),
|
|
405
|
+
sequence: ++contextInjectionSequence,
|
|
406
|
+
producerId: contextInjectionProducerId,
|
|
407
|
+
previousFingerprint: previousContextInjectionFingerprint,
|
|
408
|
+
});
|
|
409
|
+
previousContextInjectionFingerprint = injection.observation.fingerprint;
|
|
410
|
+
pi.events.emit(PAPYRUS_CONTEXT_INJECTION_CHANNEL, injection.observation);
|
|
411
|
+
if (injection.prompt !== (event.systemPrompt ?? "")) return { systemPrompt: injection.prompt };
|
|
405
412
|
} catch {
|
|
406
413
|
// DB not ready
|
|
407
414
|
}
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -17,6 +17,10 @@ export const GATE_OUTPUT_LIMIT = 200;
|
|
|
17
17
|
export const GATE_MAX_BUFFER_BYTES = 1_048_576;
|
|
18
18
|
export const GATE_FILE_MAX_BYTES = 1_048_576;
|
|
19
19
|
|
|
20
|
+
export const PAPYRUS_CONTEXT_INJECTION_CHANNEL = "papyrus.context-injection.v1";
|
|
21
|
+
export const PAPYRUS_CONTEXT_INJECTION_SCHEMA = "papyrus.context-injection/v1";
|
|
22
|
+
export const CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN = 4;
|
|
23
|
+
|
|
20
24
|
/** Compact task-context limits keep recurring prompt injection bounded. */
|
|
21
25
|
export const TASK_CONTEXT_CURRENT_LIMIT = 3;
|
|
22
26
|
export const TASK_CONTEXT_REJECTED_LIMIT = 3;
|