pi-simocracy 0.8.0 → 0.8.1
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/docs/SIM_AUTHORED_COMMENTS.md +8 -7
- package/docs/SIM_AUTHORED_PROPOSALS.md +10 -7
- package/package.json +1 -1
- package/src/auth/commands.ts +13 -1
- package/src/index.ts +1 -1
- package/src/lookup.ts +20 -8
- package/src/simocracy.ts +6 -1
- package/src/writes.ts +23 -3
|
@@ -31,13 +31,14 @@ changes. Zero new Simocracy lexicons.**
|
|
|
31
31
|
|
|
32
32
|
## Why no lexicon change
|
|
33
33
|
|
|
34
|
-
The impactindexer namespace is owned by the
|
|
35
|
-
project, not Simocracy — adding a `sim` StrongRef
|
|
36
|
-
`org.impactindexer.review.comment` would couple two
|
|
37
|
-
cycles together for one cross-app feature. And
|
|
38
|
-
extensible by structure (unknown fields are
|
|
39
|
-
ignored by old readers), but baking a
|
|
40
|
-
an impact-review lexicon mixes
|
|
34
|
+
The `org.impactindexer.*` namespace is owned by the upstream
|
|
35
|
+
hypercerts-org project, not Simocracy — adding a `sim` StrongRef
|
|
36
|
+
field to `org.impactindexer.review.comment` would couple two
|
|
37
|
+
independent release cycles together for one cross-app feature. And
|
|
38
|
+
ATProto records *are* extensible by structure (unknown fields are
|
|
39
|
+
preserved by the indexer, ignored by old readers), but baking a
|
|
40
|
+
Simocracy-specific concept into an impact-review lexicon mixes
|
|
41
|
+
concerns badly.
|
|
41
42
|
|
|
42
43
|
The `org.simocracy.history` lexicon already has every field we need:
|
|
43
44
|
|
|
@@ -18,8 +18,10 @@ When pi submits a proposal on behalf of a loaded sim, it writes
|
|
|
18
18
|
exact shape simocracy.org's `ProposalFormDialog` already writes
|
|
19
19
|
today (`title`, `shortDescription`, optional `description` /
|
|
20
20
|
`workScope` / `contributors` / `image`, `createdAt`). Old readers
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
that don't know about the history sidecar (Bluesky AppView,
|
|
22
|
+
third-party hypercerts consumers, the upstream Hyperindex network at
|
|
23
|
+
`api.hi.gainforest.app`) see this as a regular user-authored
|
|
24
|
+
proposal — graceful degradation.
|
|
23
25
|
2. **`org.simocracy.proposalContext`** — sidecar binding the proposal
|
|
24
26
|
to its parent gathering (StrongRef to `org.simocracy.gathering`)
|
|
25
27
|
or to a Frontier Tower SF floor (`floorNumber` integer). Required
|
|
@@ -46,11 +48,12 @@ its parent.
|
|
|
46
48
|
|
|
47
49
|
## Why no lexicon change
|
|
48
50
|
|
|
49
|
-
The `org.hypercerts.*` namespace is owned by the
|
|
50
|
-
|
|
51
|
-
`org.hypercerts.claim.activity`
|
|
52
|
-
|
|
53
|
-
feature, and the same argument
|
|
51
|
+
The `org.hypercerts.*` namespace is owned by the
|
|
52
|
+
[`hypercerts-org/hypercerts-lexicon`](https://github.com/hypercerts-org/hypercerts-lexicon)
|
|
53
|
+
project, not Simocracy — extending `org.hypercerts.claim.activity`
|
|
54
|
+
with a `sim` StrongRef field would couple two independent release
|
|
55
|
+
cycles together for one cross-app feature, and the same argument
|
|
56
|
+
made for comments
|
|
54
57
|
([`SIM_AUTHORED_COMMENTS.md`](./SIM_AUTHORED_COMMENTS.md)) applies
|
|
55
58
|
verbatim here.
|
|
56
59
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-simocracy",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
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/auth/commands.ts
CHANGED
|
@@ -52,11 +52,23 @@ export async function runLogin(
|
|
|
52
52
|
handle = prompt.trim().replace(/^@/, "");
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
const { CALLBACK_PORT } = await import("./callback-server.ts");
|
|
56
|
+
const isRemote = Boolean(
|
|
57
|
+
process.env.SSH_CONNECTION || process.env.SSH_CLIENT || process.env.SSH_TTY,
|
|
58
|
+
);
|
|
59
|
+
|
|
55
60
|
ctx.ui.notify(
|
|
56
|
-
`Signing in with ATProto / Bluesky as @${handle}. Starting loopback OAuth flow on 127.0.0.1
|
|
61
|
+
`Signing in with ATProto / Bluesky as @${handle}. Starting loopback OAuth flow on 127.0.0.1:${CALLBACK_PORT}… (this is NOT Anthropic auth — pi's built-in /login does that.)`,
|
|
57
62
|
"info",
|
|
58
63
|
);
|
|
59
64
|
|
|
65
|
+
if (isRemote) {
|
|
66
|
+
ctx.ui.notify(
|
|
67
|
+
`Detected SSH session. The OAuth redirect goes to 127.0.0.1:${CALLBACK_PORT}, which is THIS remote host's loopback — your local browser can't reach it directly. Open another terminal on your laptop and run: ssh -L ${CALLBACK_PORT}:127.0.0.1:${CALLBACK_PORT} <user>@<this-host> then keep that session open and continue the sign-in in your browser. (VS Code / Cursor Remote: add port ${CALLBACK_PORT} in the Ports panel.) If port ${CALLBACK_PORT} is taken locally, set PI_SIMOCRACY_OAUTH_PORT to a free port on both sides.`,
|
|
68
|
+
"info",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
60
72
|
let callback: Awaited<ReturnType<typeof startCallbackServer>>;
|
|
61
73
|
try {
|
|
62
74
|
callback = await startCallbackServer();
|
package/src/index.ts
CHANGED
|
@@ -536,7 +536,7 @@ async function loadSimByName(query: string): Promise<{
|
|
|
536
536
|
// it into something actionable.
|
|
537
537
|
const friendly =
|
|
538
538
|
msg === "fetch failed" || msg.includes("fetch failed")
|
|
539
|
-
? "could not reach the Simocracy indexer at simocracy-indexer
|
|
539
|
+
? "could not reach the Simocracy indexer at simocracy-indexer.gainforest.id — check your internet connection"
|
|
540
540
|
: msg;
|
|
541
541
|
return { matches: [], error: `Indexer search failed: ${friendly}` };
|
|
542
542
|
}
|
package/src/lookup.ts
CHANGED
|
@@ -5,11 +5,18 @@
|
|
|
5
5
|
* (`lookupRecord`) handles every kind the LLM might want to inspect:
|
|
6
6
|
* sims, proposals, gatherings, decisions, and individual comments.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* Reads go through one indexer (`simocracy-indexer`) plus the
|
|
9
9
|
* owner's PDS for direct AT-URI lookups. Sim-attribution for
|
|
10
10
|
* comments is joined client-side from `org.simocracy.history`
|
|
11
11
|
* records — same pattern simocracy-v2's notifications system uses.
|
|
12
12
|
* See `docs/SIM_AUTHORED_COMMENTS.md` for the full design.
|
|
13
|
+
*
|
|
14
|
+
* Pre-2026-05-10 this fanned out across two indexers (Simocracy +
|
|
15
|
+
* Hyperindex). The single-indexer migration
|
|
16
|
+
* (https://pi-eval.vercel.app/reports/simocracy-indexer/drop-hyperindex-migration/)
|
|
17
|
+
* folded `org.hypercerts.*` / `org.impactindexer.*` / `app.certified.*`
|
|
18
|
+
* into the same Simocracy indexer, so this file collapsed to a single
|
|
19
|
+
* URL.
|
|
13
20
|
*/
|
|
14
21
|
|
|
15
22
|
import {
|
|
@@ -21,9 +28,6 @@ import {
|
|
|
21
28
|
SIMOCRACY_INDEXER_URL,
|
|
22
29
|
} from "./simocracy.ts";
|
|
23
30
|
|
|
24
|
-
/** Hyperindexer base URL — handles `org.hypercerts.*` and `org.impactindexer.*`. */
|
|
25
|
-
const HYPERINDEXER_URL = "https://api.hi.gainforest.app";
|
|
26
|
-
|
|
27
31
|
const COLLECTION_SIM = "org.simocracy.sim";
|
|
28
32
|
const COLLECTION_PROPOSAL = "org.hypercerts.claim.activity";
|
|
29
33
|
const COLLECTION_GATHERING = "org.simocracy.gathering";
|
|
@@ -55,10 +59,16 @@ const KIND_BY_COLLECTION: Record<string, Exclude<LookupKind, "auto">> = {
|
|
|
55
59
|
[COLLECTION_COMMENT]: "comment",
|
|
56
60
|
};
|
|
57
61
|
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Which indexer hosts which collection.
|
|
64
|
+
*
|
|
65
|
+
* Trivial post-migration — every collection we read lives on the
|
|
66
|
+
* same indexer. Kept as a helper so the call sites read clearly
|
|
67
|
+
* and so a future split (if one ever shows up) only needs to edit
|
|
68
|
+
* one place.
|
|
69
|
+
*/
|
|
70
|
+
function indexerForCollection(_collection: string): string {
|
|
71
|
+
return SIMOCRACY_INDEXER_URL;
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
interface GraphQLNode {
|
|
@@ -82,6 +92,8 @@ async function fetchRecordsFromIndexer(
|
|
|
82
92
|
collection: string,
|
|
83
93
|
first: number,
|
|
84
94
|
): Promise<GraphQLNode[]> {
|
|
95
|
+
// indexerForCollection returns the same URL for everything
|
|
96
|
+
// post-2026-05-10; the helper survives in case a future split needs it.
|
|
85
97
|
const url = `${indexerForCollection(collection).replace(/\/+$/, "")}/graphql`;
|
|
86
98
|
const res = await fetch(url, {
|
|
87
99
|
method: "POST",
|
package/src/simocracy.ts
CHANGED
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
* - Resolves blob URLs through the owning DID's PDS.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// Canonical Simocracy indexer endpoint. The legacy Railway deployment at
|
|
10
|
+
// `simocracy-indexer-production.up.railway.app` is being phased out — the
|
|
11
|
+
// active instance is bumi-0 (Mac Mini, Zurich) behind a Cloudflare Tunnel.
|
|
12
|
+
// Override via SIMOCRACY_INDEXER_URL if needed. Mirrors simocracy-v2's
|
|
13
|
+
// lib/indexer-utils.ts DEFAULT_INDEXER_URL.
|
|
14
|
+
const DEFAULT_INDEXER_URL = "https://simocracy-indexer.gainforest.id";
|
|
10
15
|
const COLLECTION_SIM = "org.simocracy.sim";
|
|
11
16
|
const COLLECTION_AGENTS = "org.simocracy.agents";
|
|
12
17
|
const COLLECTION_STYLE = "org.simocracy.style";
|
package/src/writes.ts
CHANGED
|
@@ -433,16 +433,36 @@ export async function createProposal(opts: {
|
|
|
433
433
|
shortDescription: shortDescription.slice(0, 300),
|
|
434
434
|
createdAt: new Date().toISOString(),
|
|
435
435
|
};
|
|
436
|
+
// `description` and `workScope` are UNION types in the
|
|
437
|
+
// org.hypercerts.claim.activity lexicon — they MUST be wrapped objects
|
|
438
|
+
// with a `$type` discriminator. Plain strings are rejected by lex-gql
|
|
439
|
+
// (silently drops the record from the indexer). Same applies to each
|
|
440
|
+
// `contributorIdentity` (also a union).
|
|
436
441
|
if (opts.description !== undefined) {
|
|
437
442
|
const body = opts.description.trim();
|
|
438
|
-
if (body)
|
|
443
|
+
if (body) {
|
|
444
|
+
record.description = {
|
|
445
|
+
$type: "org.hypercerts.defs#descriptionString",
|
|
446
|
+
value: body,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
439
449
|
}
|
|
440
450
|
if (opts.workScope !== undefined) {
|
|
441
451
|
const ws = opts.workScope.trim();
|
|
442
|
-
if (ws)
|
|
452
|
+
if (ws) {
|
|
453
|
+
record.workScope = {
|
|
454
|
+
$type: "org.hypercerts.claim.activity#workScopeString",
|
|
455
|
+
scope: ws,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
443
458
|
}
|
|
444
459
|
if (opts.contributors && opts.contributors.length > 0) {
|
|
445
|
-
record.contributors = opts.contributors
|
|
460
|
+
record.contributors = opts.contributors.map((c) => ({
|
|
461
|
+
contributorIdentity: {
|
|
462
|
+
$type: "org.hypercerts.claim.activity#contributorIdentity",
|
|
463
|
+
identity: c.contributorIdentity,
|
|
464
|
+
},
|
|
465
|
+
}));
|
|
446
466
|
}
|
|
447
467
|
if (opts.image) record.image = opts.image;
|
|
448
468
|
const res = await opts.agent.com.atproto.repo.createRecord({
|