pi-simocracy 0.6.1 → 0.6.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/README.md +11 -0
- package/package.json +1 -1
- package/src/index.ts +7 -2
- package/src/persona.ts +26 -0
- package/src/simocracy.ts +45 -0
package/README.md
CHANGED
|
@@ -69,6 +69,17 @@ Pi calls `simocracy_lookup_record` to find the AT-URI, then `simocracy_post_comm
|
|
|
69
69
|
|
|
70
70
|
---
|
|
71
71
|
|
|
72
|
+
## Loaded-sim system prompt
|
|
73
|
+
|
|
74
|
+
When a sim is loaded, pi injects the sim's identity, constitution, and speaking style into the system prompt every turn. On top of that, the persona prompt appends a **Simocracy navigation cheat-sheet** fetched live from [`simocracy.org/skill.md`](https://www.simocracy.org/skill.md) at sim-load time — that's where the URL patterns (`/sims/<did>/<rkey>`, `/profile/<handle>`, …), indexer endpoints, and recommended tool-routing live. simocracy.org is the single source of truth; this extension keeps no baked-in fallback. If the fetch fails (offline, simocracy.org down, route not yet deployed) the section is simply omitted — the sim still loads, it just lacks the navigation guidance until the URL becomes reachable.
|
|
75
|
+
|
|
76
|
+
Override or disable:
|
|
77
|
+
|
|
78
|
+
- `SIMOCRACY_SKILL_URL=…` — point at a staging URL (the route is also viewable in a browser).
|
|
79
|
+
- `SIMOCRACY_SKILL_MD_DISABLED=1` — skip the fetch entirely (useful on metered connections / offline).
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
72
83
|
## Sprite rendering
|
|
73
84
|
|
|
74
85
|
Two formats supported:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-simocracy",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Pi extension: load a Simocracy sim into your chat — see its pixel-art sprite render inline in the terminal and roleplay with it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "David Dao <david@gainforest.earth> (https://github.com/daviddao)",
|
package/src/index.ts
CHANGED
|
@@ -95,6 +95,7 @@ import {
|
|
|
95
95
|
fetchAgentsForSim,
|
|
96
96
|
fetchStyleForSim,
|
|
97
97
|
fetchBlob,
|
|
98
|
+
fetchSkillMd,
|
|
98
99
|
resolveHandle,
|
|
99
100
|
parseAtUri,
|
|
100
101
|
type AgentsRecord,
|
|
@@ -533,12 +534,14 @@ async function loadSimByName(query: string): Promise<{
|
|
|
533
534
|
}
|
|
534
535
|
|
|
535
536
|
async function hydrateLoadedSim(match: SimMatch): Promise<LoadedSim> {
|
|
536
|
-
// Fetch agents (constitution), style, sprite ANSI
|
|
537
|
-
|
|
537
|
+
// Fetch agents (constitution), style, sprite ANSI, handle, and the
|
|
538
|
+
// simocracy.org navigation cheat-sheet in parallel.
|
|
539
|
+
const [agents, style, sprite, handle, skill] = await Promise.all([
|
|
538
540
|
fetchAgentsForSim(match.uri).catch(() => null) as Promise<AgentsRecord | null>,
|
|
539
541
|
fetchStyleForSim(match.uri).catch(() => null) as Promise<StyleRecord | null>,
|
|
540
542
|
renderSprite(match).catch(() => null),
|
|
541
543
|
resolveHandle(match.did).catch(() => null),
|
|
544
|
+
fetchSkillMd(),
|
|
542
545
|
]);
|
|
543
546
|
|
|
544
547
|
return {
|
|
@@ -566,6 +569,8 @@ async function hydrateLoadedSim(match: SimMatch): Promise<LoadedSim> {
|
|
|
566
569
|
shortDescription: agents?.shortDescription,
|
|
567
570
|
description: agents?.description,
|
|
568
571
|
style: style?.description,
|
|
572
|
+
skillMd: skill.text,
|
|
573
|
+
skillMdError: skill.text ? undefined : skill.error,
|
|
569
574
|
};
|
|
570
575
|
}
|
|
571
576
|
|
package/src/persona.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* normal `/sim` chat and with the `simocracy_chat` tool.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { getSimocracySkillUrl } from "./simocracy.ts";
|
|
11
|
+
|
|
10
12
|
export interface LoadedSim {
|
|
11
13
|
uri: string;
|
|
12
14
|
did: string;
|
|
@@ -55,6 +57,16 @@ export interface LoadedSim {
|
|
|
55
57
|
/** Native PNG height in pixels. */
|
|
56
58
|
heightPx: number;
|
|
57
59
|
};
|
|
60
|
+
/** Fetched contents of `https://www.simocracy.org/skill.md` at
|
|
61
|
+
* sim-load time, when the fetch succeeded. Appended verbatim to
|
|
62
|
+
* the persona prompt by `buildSimPrompt`. Kept on the sim so a
|
|
63
|
+
* re-render or a future `simocracy_chat` call uses the same
|
|
64
|
+
* content the original load used. */
|
|
65
|
+
skillMd?: string;
|
|
66
|
+
/** Reason the skill.md fetch did not run / failed, when relevant.
|
|
67
|
+
* Stored only for diagnostics — never injected into the persona
|
|
68
|
+
* prompt. Surface optionally via `/sim status`. */
|
|
69
|
+
skillMdError?: string;
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
/**
|
|
@@ -86,6 +98,20 @@ export function buildSimPrompt(sim: LoadedSim): string {
|
|
|
86
98
|
lines.push(`## ${sim.name}'s speaking style`);
|
|
87
99
|
lines.push(sim.style);
|
|
88
100
|
}
|
|
101
|
+
if (sim.skillMd) {
|
|
102
|
+
lines.push(``);
|
|
103
|
+
lines.push(`## Simocracy navigation cheat-sheet`);
|
|
104
|
+
lines.push(
|
|
105
|
+
`Your specific simocracy.org page: https://www.simocracy.org/sims/${sim.did}/${sim.rkey}` +
|
|
106
|
+
(sim.handle ? `\nThe owner's profile: https://www.simocracy.org/profile/${sim.handle}` : ""),
|
|
107
|
+
);
|
|
108
|
+
lines.push(``);
|
|
109
|
+
lines.push(
|
|
110
|
+
`The block below is fetched from ${getSimocracySkillUrl()} on every sim-load and is the canonical reference for URL patterns, indexer endpoints, and common navigation workflows. Treat it as authoritative; when the user asks about you, your gatherings, your proposals, or what other sims have been saying, follow this guide.`,
|
|
111
|
+
);
|
|
112
|
+
lines.push(``);
|
|
113
|
+
lines.push(sim.skillMd);
|
|
114
|
+
}
|
|
89
115
|
lines.push(``);
|
|
90
116
|
lines.push(
|
|
91
117
|
`When the user asks you to use any of pi's tools (read, edit, bash, etc.), you should still use them — you're ${sim.name} *with access to a developer's terminal*. Just narrate tool use the way ${sim.name} would talk about it.`,
|
package/src/simocracy.ts
CHANGED
|
@@ -345,3 +345,48 @@ export async function resolveHandle(did: string): Promise<string | null> {
|
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
export const SIMOCRACY_INDEXER_URL = DEFAULT_INDEXER_URL;
|
|
348
|
+
|
|
349
|
+
const SIMOCRACY_SKILL_URL =
|
|
350
|
+
process.env.SIMOCRACY_SKILL_URL ?? "https://www.simocracy.org/skill.md";
|
|
351
|
+
|
|
352
|
+
const SKILL_MD_FETCH_TIMEOUT_MS = 4000;
|
|
353
|
+
const SKILL_MD_MAX_BYTES = 64 * 1024;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Fetch the navigation cheat-sheet served at `simocracy.org/skill.md`.
|
|
357
|
+
* Never throws — returns either `{ text }` on success or `{ error }` on
|
|
358
|
+
* failure / disablement so the caller can record the diagnostic and
|
|
359
|
+
* fall through to the pre-change behaviour (no navigation guidance).
|
|
360
|
+
*/
|
|
361
|
+
export async function fetchSkillMd(): Promise<{ text?: string; error?: string }> {
|
|
362
|
+
if (process.env.SIMOCRACY_SKILL_MD_DISABLED === "1") {
|
|
363
|
+
return { error: "disabled by SIMOCRACY_SKILL_MD_DISABLED=1" };
|
|
364
|
+
}
|
|
365
|
+
const controller = new AbortController();
|
|
366
|
+
const timer = setTimeout(() => controller.abort(), SKILL_MD_FETCH_TIMEOUT_MS);
|
|
367
|
+
try {
|
|
368
|
+
const res = await fetch(SIMOCRACY_SKILL_URL, {
|
|
369
|
+
signal: controller.signal,
|
|
370
|
+
headers: { Accept: "text/markdown, text/plain;q=0.5" },
|
|
371
|
+
});
|
|
372
|
+
if (!res.ok) {
|
|
373
|
+
return { error: `${res.status} ${res.statusText}` };
|
|
374
|
+
}
|
|
375
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
376
|
+
const trimmed =
|
|
377
|
+
buf.byteLength > SKILL_MD_MAX_BYTES
|
|
378
|
+
? buf.slice(0, SKILL_MD_MAX_BYTES).toString("utf8") +
|
|
379
|
+
`\n\n<!-- truncated at ${SKILL_MD_MAX_BYTES} bytes -->`
|
|
380
|
+
: buf.toString("utf8");
|
|
381
|
+
return { text: trimmed };
|
|
382
|
+
} catch (err) {
|
|
383
|
+
return { error: (err as Error).message };
|
|
384
|
+
} finally {
|
|
385
|
+
clearTimeout(timer);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** Exported for the persona prompt to mention the URL by name. */
|
|
390
|
+
export function getSimocracySkillUrl(): string {
|
|
391
|
+
return SIMOCRACY_SKILL_URL;
|
|
392
|
+
}
|