@pleri/olam-cli 0.1.145 → 0.1.147
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/dist/commands/skills-doctor.d.ts +14 -0
- package/dist/commands/skills-doctor.d.ts.map +1 -0
- package/dist/commands/skills-doctor.js +126 -0
- package/dist/commands/skills-doctor.js.map +1 -0
- package/dist/commands/skills-hook.d.ts +19 -0
- package/dist/commands/skills-hook.d.ts.map +1 -0
- package/dist/commands/skills-hook.js +99 -0
- package/dist/commands/skills-hook.js.map +1 -0
- package/dist/commands/skills-migrate-back.d.ts +21 -0
- package/dist/commands/skills-migrate-back.d.ts.map +1 -0
- package/dist/commands/skills-migrate-back.js +222 -0
- package/dist/commands/skills-migrate-back.js.map +1 -0
- package/dist/commands/skills-migrate-hooks-back.d.ts +19 -0
- package/dist/commands/skills-migrate-hooks-back.d.ts.map +1 -0
- package/dist/commands/skills-migrate-hooks-back.js +83 -0
- package/dist/commands/skills-migrate-hooks-back.js.map +1 -0
- package/dist/commands/skills-migrate-hooks.d.ts +40 -0
- package/dist/commands/skills-migrate-hooks.d.ts.map +1 -0
- package/dist/commands/skills-migrate-hooks.js +178 -0
- package/dist/commands/skills-migrate-hooks.js.map +1 -0
- package/dist/commands/skills-migrate.d.ts +33 -0
- package/dist/commands/skills-migrate.d.ts.map +1 -0
- package/dist/commands/skills-migrate.js +216 -0
- package/dist/commands/skills-migrate.js.map +1 -0
- package/dist/commands/skills-onboard.d.ts +26 -0
- package/dist/commands/skills-onboard.d.ts.map +1 -0
- package/dist/commands/skills-onboard.js +227 -0
- package/dist/commands/skills-onboard.js.map +1 -0
- package/dist/commands/skills-shadow-backups.d.ts +15 -0
- package/dist/commands/skills-shadow-backups.d.ts.map +1 -0
- package/dist/commands/skills-shadow-backups.js +132 -0
- package/dist/commands/skills-shadow-backups.js.map +1 -0
- package/dist/commands/skills-source.d.ts +25 -0
- package/dist/commands/skills-source.d.ts.map +1 -1
- package/dist/commands/skills-source.js +305 -7
- package/dist/commands/skills-source.js.map +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +62 -7
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/substrate-audit-log.d.ts +49 -0
- package/dist/commands/substrate-audit-log.d.ts.map +1 -0
- package/dist/commands/substrate-audit-log.js +148 -0
- package/dist/commands/substrate-audit-log.js.map +1 -0
- package/dist/commands/substrate.d.ts +60 -0
- package/dist/commands/substrate.d.ts.map +1 -0
- package/dist/commands/substrate.js +175 -0
- package/dist/commands/substrate.js.map +1 -0
- package/dist/commands/upgrade.d.ts +10 -0
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +30 -0
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/image-digests.json +7 -7
- package/dist/index.js +8687 -4713
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +69 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +146 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/instrumentation.d.ts +85 -0
- package/dist/lib/instrumentation.d.ts.map +1 -0
- package/dist/lib/instrumentation.js +104 -0
- package/dist/lib/instrumentation.js.map +1 -0
- package/dist/lib/kubectl-wrap.d.ts +59 -0
- package/dist/lib/kubectl-wrap.d.ts.map +1 -0
- package/dist/lib/kubectl-wrap.js +130 -0
- package/dist/lib/kubectl-wrap.js.map +1 -0
- package/dist/lib/manifest-refresh.d.ts +95 -0
- package/dist/lib/manifest-refresh.d.ts.map +1 -0
- package/dist/lib/manifest-refresh.js +222 -0
- package/dist/lib/manifest-refresh.js.map +1 -0
- package/dist/lib/port-forward.d.ts +101 -0
- package/dist/lib/port-forward.d.ts.map +1 -0
- package/dist/lib/port-forward.js +240 -0
- package/dist/lib/port-forward.js.map +1 -0
- package/dist/lib/upgrade-kubernetes.d.ts +77 -0
- package/dist/lib/upgrade-kubernetes.d.ts.map +1 -0
- package/dist/lib/upgrade-kubernetes.js +277 -0
- package/dist/lib/upgrade-kubernetes.js.map +1 -0
- package/dist/mcp-server.js +20262 -18211
- package/host-cp/k8s/manifests/00-namespace.yaml +7 -0
- package/host-cp/k8s/manifests/10-serviceaccount.yaml +8 -0
- package/host-cp/k8s/manifests/20-rbac.yaml +34 -0
- package/host-cp/k8s/manifests/30-configmap.yaml +30 -0
- package/host-cp/k8s/manifests/45-pvc.yaml +27 -0
- package/host-cp/k8s/manifests/50-deployment.yaml +148 -0
- package/host-cp/k8s/manifests/60-service.yaml +22 -0
- package/host-cp/k8s/templates/40-secret-template.yaml +32 -0
- package/host-cp/src/plan-chat-service.mjs +31 -7
- package/host-cp/src/server.mjs +32 -7
- package/package.json +3 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Phase 1b Decision 19: Role scoped to resourceNames: ["olam-host-cp"] on
|
|
2
|
+
# apps/v1 deployments. Without this scope, the in-cluster ServiceAccount
|
|
3
|
+
# could patch ANY Deployment in the namespace. This is the load-bearing
|
|
4
|
+
# security guardrail — preserve verbatim.
|
|
5
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
6
|
+
kind: Role
|
|
7
|
+
metadata:
|
|
8
|
+
name: olam-host-cp
|
|
9
|
+
namespace: olam
|
|
10
|
+
labels:
|
|
11
|
+
app: olam-host-cp
|
|
12
|
+
olam.io/component: host-stack
|
|
13
|
+
rules:
|
|
14
|
+
- apiGroups: ["apps"]
|
|
15
|
+
resources: ["deployments"]
|
|
16
|
+
resourceNames: ["olam-host-cp"]
|
|
17
|
+
verbs: ["get", "patch", "watch"]
|
|
18
|
+
---
|
|
19
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
20
|
+
kind: RoleBinding
|
|
21
|
+
metadata:
|
|
22
|
+
name: olam-host-cp
|
|
23
|
+
namespace: olam
|
|
24
|
+
labels:
|
|
25
|
+
app: olam-host-cp
|
|
26
|
+
olam.io/component: host-stack
|
|
27
|
+
subjects:
|
|
28
|
+
- kind: ServiceAccount
|
|
29
|
+
name: olam-host-cp
|
|
30
|
+
namespace: olam
|
|
31
|
+
roleRef:
|
|
32
|
+
kind: Role
|
|
33
|
+
name: olam-host-cp
|
|
34
|
+
apiGroup: rbac.authorization.k8s.io
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# ConfigMap for olam-host-cp environment. Sensitive values (OLAM_AUTH_SECRET,
|
|
2
|
+
# GH_TOKEN) are NOT here — they live in the Secret (see templates/40-secret-template.yaml).
|
|
3
|
+
# Operators apply the Secret separately before applying the manifests.
|
|
4
|
+
apiVersion: v1
|
|
5
|
+
kind: ConfigMap
|
|
6
|
+
metadata:
|
|
7
|
+
name: olam-host-cp-env
|
|
8
|
+
namespace: olam
|
|
9
|
+
labels:
|
|
10
|
+
app: olam-host-cp
|
|
11
|
+
olam.io/component: host-stack
|
|
12
|
+
data:
|
|
13
|
+
# Auth service URL. Default targets host.docker.internal for Colima/Docker
|
|
14
|
+
# Desktop k3d setups. Override when auth-service runs elsewhere (e.g. via
|
|
15
|
+
# an ExternalName Service pointing at the host gateway).
|
|
16
|
+
OLAM_AUTH_SERVICE_URL: "http://host.docker.internal:8000"
|
|
17
|
+
# Docker socket proxy — ClusterIP Service DNS inside the namespace.
|
|
18
|
+
DOCKER_HOST: "tcp://docker-socket-proxy:2375"
|
|
19
|
+
# Host-cp server port — must match the Service targetPort in 60-service.yaml.
|
|
20
|
+
OLAM_HOST_CP_PORT: "19000"
|
|
21
|
+
# Operator state paths (resolved inside the K3s node via hostPath volumes).
|
|
22
|
+
OLAM_HOST_CP_TOKEN_PATH: "/data/host-cp.token"
|
|
23
|
+
OLAM_WORKSPACES_DIR: "/data/workspaces"
|
|
24
|
+
OLAM_WORLDS_DB: "/data/worlds.db"
|
|
25
|
+
OLAM_PLAN_DB_PATH: "/data/plan.db"
|
|
26
|
+
OLAM_PLAN_DIR: "/data/plan"
|
|
27
|
+
# Tunable defaults.
|
|
28
|
+
OLAM_SECRET_CACHE_TTL_SEC: "300"
|
|
29
|
+
OLAM_PR_POLL_INTERVAL_MS: "300000"
|
|
30
|
+
OLAM_MERGE_GRACE_MS: "600000"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# PersistentVolumeClaim for olam-host-cp /data volume.
|
|
2
|
+
#
|
|
3
|
+
# Why PVC instead of hostPath:
|
|
4
|
+
# hostPath volumes on k3d nodes resolve to paths INSIDE the k3d node
|
|
5
|
+
# container — not the operator's host filesystem. A bare k3d cluster has
|
|
6
|
+
# an empty node filesystem, so a hostPath at /host/.olam is always empty.
|
|
7
|
+
# Additionally, fsGroup does NOT relabel hostPath volumes (only PVCs /
|
|
8
|
+
# emptyDir / projected volumes), so UID-1000 pods cannot write to
|
|
9
|
+
# root-owned hostPath mounts even when fsGroup: 1000 is set.
|
|
10
|
+
#
|
|
11
|
+
# local-path StorageClass ships with k3d by default (rancher/local-path-provisioner).
|
|
12
|
+
# On non-k3d clusters, substitute with the appropriate StorageClass name.
|
|
13
|
+
apiVersion: v1
|
|
14
|
+
kind: PersistentVolumeClaim
|
|
15
|
+
metadata:
|
|
16
|
+
name: olam-host-cp-data
|
|
17
|
+
namespace: olam
|
|
18
|
+
labels:
|
|
19
|
+
app: olam-host-cp
|
|
20
|
+
olam.io/component: host-stack
|
|
21
|
+
spec:
|
|
22
|
+
accessModes:
|
|
23
|
+
- ReadWriteOnce
|
|
24
|
+
storageClassName: local-path
|
|
25
|
+
resources:
|
|
26
|
+
requests:
|
|
27
|
+
storage: 5Gi
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Deployment for olam-host-cp.
|
|
2
|
+
#
|
|
3
|
+
# Image: pinned to sha256 digest (not :latest or named tag) per T4 threat model.
|
|
4
|
+
# Digest resolves to ghcr.io/pleri/olam-host-cp:0.1.143 (multi-arch index).
|
|
5
|
+
# To update: resolve the new tag's digest via:
|
|
6
|
+
# TOKEN=$(curl -s "https://ghcr.io/token?scope=repository:pleri/olam-host-cp:pull&service=ghcr.io" | jq -r .token)
|
|
7
|
+
# curl -sI -H "Authorization: Bearer $TOKEN" \
|
|
8
|
+
# -H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json" \
|
|
9
|
+
# https://ghcr.io/v2/pleri/olam-host-cp/manifests/<tag> | grep docker-content-digest
|
|
10
|
+
#
|
|
11
|
+
# securityContext: conservative defaults per T6/T7 threat model.
|
|
12
|
+
# Operators who need to relax these (e.g. for debugging) must pass
|
|
13
|
+
# --accept-security-regression (Phase C, Decision D14) — out of scope here.
|
|
14
|
+
#
|
|
15
|
+
# Volume requirements for k3d:
|
|
16
|
+
# olam-home (/data): backed by a PersistentVolumeClaim (45-pvc.yaml).
|
|
17
|
+
# An init container (chown-data) runs `chown -R 1000:1000 /data` as root
|
|
18
|
+
# before the main container starts, granting UID-1000 write access on the
|
|
19
|
+
# freshly-provisioned PV. fsGroup alone is insufficient for hostPath volumes.
|
|
20
|
+
#
|
|
21
|
+
# gh-config (/gh-config) and operator-repo (/operator-repo) remain hostPath
|
|
22
|
+
# volumes that resolve to paths inside the k3d node container.
|
|
23
|
+
# OPERATORS MUST pass these volume mounts when creating the k3d cluster:
|
|
24
|
+
#
|
|
25
|
+
# k3d cluster create olam-host \
|
|
26
|
+
# --volume ~/.config/gh:/host/.config/gh \
|
|
27
|
+
# --volume <olam-repo-root>:/host/olam \
|
|
28
|
+
# --wait --timeout 90s
|
|
29
|
+
#
|
|
30
|
+
# Without these flags the gh-config and operator-repo mounts will be empty
|
|
31
|
+
# (the paths exist inside the node container but contain no data from the
|
|
32
|
+
# operator's host). The pod will still start — features that depend on GitHub
|
|
33
|
+
# auth or the operator repo will fail gracefully. The Phase D install guide
|
|
34
|
+
# surfaces this requirement prominently.
|
|
35
|
+
apiVersion: apps/v1
|
|
36
|
+
kind: Deployment
|
|
37
|
+
metadata:
|
|
38
|
+
name: olam-host-cp
|
|
39
|
+
namespace: olam
|
|
40
|
+
labels:
|
|
41
|
+
app: olam-host-cp
|
|
42
|
+
olam.io/component: host-stack
|
|
43
|
+
spec:
|
|
44
|
+
replicas: 1
|
|
45
|
+
strategy:
|
|
46
|
+
type: RollingUpdate
|
|
47
|
+
rollingUpdate:
|
|
48
|
+
maxSurge: 1
|
|
49
|
+
maxUnavailable: 0
|
|
50
|
+
selector:
|
|
51
|
+
matchLabels:
|
|
52
|
+
app: olam-host-cp
|
|
53
|
+
template:
|
|
54
|
+
metadata:
|
|
55
|
+
labels:
|
|
56
|
+
app: olam-host-cp
|
|
57
|
+
spec:
|
|
58
|
+
serviceAccountName: olam-host-cp
|
|
59
|
+
securityContext:
|
|
60
|
+
runAsNonRoot: true
|
|
61
|
+
runAsUser: 1000
|
|
62
|
+
runAsGroup: 1000
|
|
63
|
+
fsGroup: 1000
|
|
64
|
+
initContainers:
|
|
65
|
+
- name: chown-data
|
|
66
|
+
# busybox:1.36 — sha256-pinned per T4 threat model.
|
|
67
|
+
# To update: docker pull busybox:1.36 && docker inspect busybox:1.36 --format '{{index .RepoDigests 0}}'
|
|
68
|
+
image: busybox@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662
|
|
69
|
+
imagePullPolicy: IfNotPresent
|
|
70
|
+
# Run as root to chown the freshly-provisioned PV to UID 1000.
|
|
71
|
+
# The pod-level runAsNonRoot: true is overridden here deliberately.
|
|
72
|
+
# The main container still runs as UID 1000 with all security defaults intact.
|
|
73
|
+
securityContext:
|
|
74
|
+
runAsUser: 0
|
|
75
|
+
runAsNonRoot: false
|
|
76
|
+
allowPrivilegeEscalation: false
|
|
77
|
+
command: ["chown", "-R", "1000:1000", "/data"]
|
|
78
|
+
volumeMounts:
|
|
79
|
+
- name: olam-home
|
|
80
|
+
mountPath: /data
|
|
81
|
+
containers:
|
|
82
|
+
- name: olam-host-cp
|
|
83
|
+
image: ghcr.io/pleri/olam-host-cp@sha256:513b16e1c36c96f4a03b431445da45cabf83c85b5761d1c93fab684a13c7354b
|
|
84
|
+
imagePullPolicy: IfNotPresent
|
|
85
|
+
securityContext:
|
|
86
|
+
runAsNonRoot: true
|
|
87
|
+
runAsUser: 1000
|
|
88
|
+
readOnlyRootFilesystem: true
|
|
89
|
+
allowPrivilegeEscalation: false
|
|
90
|
+
capabilities:
|
|
91
|
+
drop: ["ALL"]
|
|
92
|
+
ports:
|
|
93
|
+
- name: http
|
|
94
|
+
containerPort: 19000
|
|
95
|
+
protocol: TCP
|
|
96
|
+
envFrom:
|
|
97
|
+
- configMapRef:
|
|
98
|
+
name: olam-host-cp-env
|
|
99
|
+
- secretRef:
|
|
100
|
+
name: olam-host-cp-secret
|
|
101
|
+
volumeMounts:
|
|
102
|
+
- name: olam-home
|
|
103
|
+
mountPath: /data
|
|
104
|
+
- name: gh-config
|
|
105
|
+
mountPath: /gh-config
|
|
106
|
+
readOnly: true
|
|
107
|
+
- name: operator-repo
|
|
108
|
+
mountPath: /operator-repo
|
|
109
|
+
readOnly: true
|
|
110
|
+
- name: tmp
|
|
111
|
+
mountPath: /tmp
|
|
112
|
+
readinessProbe:
|
|
113
|
+
httpGet:
|
|
114
|
+
path: /api/version/status
|
|
115
|
+
port: 19000
|
|
116
|
+
initialDelaySeconds: 5
|
|
117
|
+
periodSeconds: 5
|
|
118
|
+
timeoutSeconds: 3
|
|
119
|
+
failureThreshold: 6
|
|
120
|
+
livenessProbe:
|
|
121
|
+
httpGet:
|
|
122
|
+
path: /api/version/status
|
|
123
|
+
port: 19000
|
|
124
|
+
initialDelaySeconds: 30
|
|
125
|
+
periodSeconds: 20
|
|
126
|
+
timeoutSeconds: 5
|
|
127
|
+
failureThreshold: 3
|
|
128
|
+
resources:
|
|
129
|
+
requests:
|
|
130
|
+
cpu: "50m"
|
|
131
|
+
memory: "256Mi"
|
|
132
|
+
limits:
|
|
133
|
+
cpu: "1000m"
|
|
134
|
+
memory: "1Gi"
|
|
135
|
+
volumes:
|
|
136
|
+
- name: olam-home
|
|
137
|
+
persistentVolumeClaim:
|
|
138
|
+
claimName: olam-host-cp-data
|
|
139
|
+
- name: gh-config
|
|
140
|
+
hostPath:
|
|
141
|
+
path: /host/.config/gh
|
|
142
|
+
type: DirectoryOrCreate
|
|
143
|
+
- name: operator-repo
|
|
144
|
+
hostPath:
|
|
145
|
+
path: /host/olam
|
|
146
|
+
type: DirectoryOrCreate
|
|
147
|
+
- name: tmp
|
|
148
|
+
emptyDir: {}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# ClusterIP Service for olam-host-cp.
|
|
2
|
+
# Operator surfaces the SPA externally via:
|
|
3
|
+
# kubectl port-forward -n olam svc/olam-host-cp 19000:19000
|
|
4
|
+
# This keeps the "127.0.0.1-only" single-user-per-host invariant
|
|
5
|
+
# (NodePort would bind on all interfaces; port-forward keeps it local).
|
|
6
|
+
apiVersion: v1
|
|
7
|
+
kind: Service
|
|
8
|
+
metadata:
|
|
9
|
+
name: olam-host-cp
|
|
10
|
+
namespace: olam
|
|
11
|
+
labels:
|
|
12
|
+
app: olam-host-cp
|
|
13
|
+
olam.io/component: host-stack
|
|
14
|
+
spec:
|
|
15
|
+
type: ClusterIP
|
|
16
|
+
selector:
|
|
17
|
+
app: olam-host-cp
|
|
18
|
+
ports:
|
|
19
|
+
- name: http
|
|
20
|
+
port: 19000
|
|
21
|
+
targetPort: 19000
|
|
22
|
+
protocol: TCP
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Secret TEMPLATE for olam-host-cp.
|
|
2
|
+
#
|
|
3
|
+
# This file is a TEMPLATE — it MUST NOT be applied directly without substituting
|
|
4
|
+
# the placeholder values. The placeholders are intentionally invalid; a raw
|
|
5
|
+
# `kubectl apply` will result in auth-service 401s rather than silently shipping
|
|
6
|
+
# fake credentials.
|
|
7
|
+
#
|
|
8
|
+
# Preferred substitution (keeps secrets out of git):
|
|
9
|
+
# kubectl create secret generic olam-host-cp-secret -n olam \
|
|
10
|
+
# --from-literal=OLAM_AUTH_SECRET=$(cat ~/.olam/auth-secret) \
|
|
11
|
+
# --from-literal=GH_TOKEN=$(gh auth token) \
|
|
12
|
+
# --dry-run=client -o yaml | kubectl apply -f -
|
|
13
|
+
#
|
|
14
|
+
# This template lives in packages/host-cp/k8s/templates/ (NOT manifests/)
|
|
15
|
+
# so that `kubectl apply -f manifests/` does NOT apply it — operators must
|
|
16
|
+
# explicitly handle Secret provisioning before applying the manifests.
|
|
17
|
+
apiVersion: v1
|
|
18
|
+
kind: Secret
|
|
19
|
+
metadata:
|
|
20
|
+
name: olam-host-cp-secret
|
|
21
|
+
namespace: olam
|
|
22
|
+
labels:
|
|
23
|
+
app: olam-host-cp
|
|
24
|
+
olam.io/component: host-stack
|
|
25
|
+
type: Opaque
|
|
26
|
+
stringData:
|
|
27
|
+
# Shared bearer secret between host-cp and the long-lived olam-auth process.
|
|
28
|
+
# Source: cat ~/.olam/auth-secret
|
|
29
|
+
OLAM_AUTH_SECRET: "REPLACE_ME_FROM_HOME_DOTOLAM_AUTH_SECRET"
|
|
30
|
+
# GitHub token for GHCR image pulls and the /api/prs endpoint.
|
|
31
|
+
# Source: gh auth token
|
|
32
|
+
GH_TOKEN: "REPLACE_ME_FROM_GH_AUTH_TOKEN"
|
|
@@ -80,19 +80,38 @@ const SCOPED_QUERY_PARAMS = new Set(['world_id', 'session_id', 'where']);
|
|
|
80
80
|
* resolution is wired here. Remove the throw when this function actually
|
|
81
81
|
* inspects the bearer.
|
|
82
82
|
*/
|
|
83
|
-
|
|
83
|
+
/**
|
|
84
|
+
* G1-demo lift (2026-05-16): trust the client-supplied `actor_type` when
|
|
85
|
+
* present. The original A2 design hardcoded ('agent','agent') with T3
|
|
86
|
+
* mitigation explicitly requiring server-injection — but that collapsed
|
|
87
|
+
* the operator + driver + codex roles into one and broke the driver
|
|
88
|
+
* pickup loop (which filters on `actor_type === 'operator'`).
|
|
89
|
+
*
|
|
90
|
+
* Single-operator local mode has no multi-tenant threat, so trusting the
|
|
91
|
+
* client field is safe pragmatic. The original silent-collapse safeguard
|
|
92
|
+
* for OLAM_PLAN_CHAT_SECRETS stays — multi-secret mode still needs A4
|
|
93
|
+
* proper bearer→principal resolution before it can land.
|
|
94
|
+
*/
|
|
95
|
+
function principalFromBearer(bearer, body) {
|
|
84
96
|
if (process.env.OLAM_PLAN_CHAT_SECRETS) {
|
|
85
|
-
// Multi-secret mode reserved for A4 / A6.1; minimal A2 doesn't support it.
|
|
86
97
|
throw new Error(
|
|
87
98
|
'plan-chat-service: OLAM_PLAN_CHAT_SECRETS is set but principalFromBearer ' +
|
|
88
|
-
"still
|
|
89
|
-
'enabling multi-secret mode (see plan A4 / A6.1).',
|
|
99
|
+
"still trusts client-supplied actor_type. Wire bearer→principal " +
|
|
100
|
+
'resolution before enabling multi-secret mode (see plan A4 / A6.1).',
|
|
90
101
|
);
|
|
91
102
|
}
|
|
92
103
|
if (typeof bearer !== 'string' || bearer.length === 0) {
|
|
93
104
|
throw new Error('plan-chat-service: principalFromBearer called with empty bearer');
|
|
94
105
|
}
|
|
95
|
-
|
|
106
|
+
// Trust client field if supplied (SPA sends 'operator'; SDK adapter omits
|
|
107
|
+
// and inherits 'agent' default; codex-runner sends 'codex').
|
|
108
|
+
const claimed = body && typeof body.actor_type === 'string' && body.actor_type.length > 0
|
|
109
|
+
? body.actor_type
|
|
110
|
+
: 'agent';
|
|
111
|
+
const claimedId = body && typeof body.actor_id === 'string' && body.actor_id.length > 0
|
|
112
|
+
? body.actor_id
|
|
113
|
+
: claimed;
|
|
114
|
+
return { actorId: claimedId, actorType: claimed };
|
|
96
115
|
}
|
|
97
116
|
|
|
98
117
|
function readJson(req, limit = 65536) {
|
|
@@ -182,7 +201,7 @@ export function createHandler({ pool, bearer, electricUrl }) {
|
|
|
182
201
|
}
|
|
183
202
|
const invalid = validateChunkInput(body);
|
|
184
203
|
if (invalid) return badRequest(res, invalid);
|
|
185
|
-
const principal = principalFromBearer(bearer);
|
|
204
|
+
const principal = principalFromBearer(bearer, body);
|
|
186
205
|
try {
|
|
187
206
|
await pool.query(
|
|
188
207
|
`INSERT INTO chunks
|
|
@@ -251,9 +270,14 @@ export function createHandler({ pool, bearer, electricUrl }) {
|
|
|
251
270
|
}
|
|
252
271
|
// PB2 — server-derived `where` clause; safe because SCOPE_ID_RE has
|
|
253
272
|
// already rejected single-quotes, semicolons, and SQL meta-characters.
|
|
273
|
+
//
|
|
274
|
+
// Predicate order: session_id FIRST. Electric SQL caches shapes by the
|
|
275
|
+
// exact predicate string; flipping the original (world_id, session_id)
|
|
276
|
+
// order forces a fresh shape and avoids a known cache-staleness bug
|
|
277
|
+
// where an empty-result shape persists after new rows land.
|
|
254
278
|
upstream.searchParams.set(
|
|
255
279
|
'where',
|
|
256
|
-
`
|
|
280
|
+
`session_id='${sessionId}' AND world_id='${worldId}'`,
|
|
257
281
|
);
|
|
258
282
|
|
|
259
283
|
let upstreamRes;
|
package/host-cp/src/server.mjs
CHANGED
|
@@ -2534,8 +2534,8 @@ const DIST_DIR = (() => {
|
|
|
2534
2534
|
return '/app/dist'; // fallback; readFile will surface ENOENT
|
|
2535
2535
|
})();
|
|
2536
2536
|
|
|
2537
|
-
const SPA_ROUTES = new Set(['/', '/worlds', '/workspaces', '/inbox', '/repos', '/runbooks']);
|
|
2538
|
-
const SPA_PREFIX = ['/world/', '/worlds/', '/workspaces/', '/inbox/', '/repos/', '/runbooks/', '/session/'];
|
|
2537
|
+
const SPA_ROUTES = new Set(['/', '/worlds', '/workspaces', '/inbox', '/repos', '/runbooks', '/plan']);
|
|
2538
|
+
const SPA_PREFIX = ['/world/', '/worlds/', '/workspaces/', '/inbox/', '/repos/', '/runbooks/', '/session/', '/plan/'];
|
|
2539
2539
|
|
|
2540
2540
|
// Top-level path segments that are NOT world IDs. Mirrors RESERVED_SEGMENTS
|
|
2541
2541
|
// in lib/worldId.ts — keep in sync.
|
|
@@ -2675,7 +2675,7 @@ async function tryServeStatic(req, res, pathname) {
|
|
|
2675
2675
|
// memory thereafter. Cache invalidates on dist/ mtime change so a
|
|
2676
2676
|
// rebuilt bundle is picked up without restart.
|
|
2677
2677
|
let _spaCache = null;
|
|
2678
|
-
let
|
|
2678
|
+
let _spaCacheKey = '';
|
|
2679
2679
|
|
|
2680
2680
|
/**
|
|
2681
2681
|
* Bootstrap script injected into the SPA shell. Two responsibilities:
|
|
@@ -2755,9 +2755,32 @@ let _spaCacheMtime = 0;
|
|
|
2755
2755
|
// failures — don't cause a refresh loop).
|
|
2756
2756
|
const BOOTSTRAP_SCRIPT = `<script>(function(){function ck(){var m=document.cookie.match(/olam_host_cp_token=([^;]+)/);return m?m[1]:'';}function sw(t){document.cookie='olam_host_cp_token='+t+'; path=/; samesite=strict';}try{var x=new XMLHttpRequest();x.open('GET','/api/bootstrap',false);x.send();if(x.status===200){var d=JSON.parse(x.responseText);sw(d.token);}}catch(e){console.error('[host-cp bootstrap]',e);}var reloading=false;function recover(){if(reloading)return;try{var x=new XMLHttpRequest();x.open('GET','/api/bootstrap',false);x.send();if(x.status===200){var d=JSON.parse(x.responseText);if(d.token&&ck()!==d.token){reloading=true;sw(d.token);console.warn('[host-cp auth recover] token rotated; reloading');location.reload();}}}catch(e){console.error('[host-cp auth recover]',e);}}var HN=['/api/bootstrap','/api/worlds','/api/projects','/api/workspaces','/api/workspaces/match','/api/repos','/api/runbooks','/api/auth','/api/host-stream','/api/plan-chat','/api/plan/agent-runtime','/health'];var WP=['/api/','/session/','/hooks/','/dispatch','/lanes','/codex/','/review/'];function sr(p){if(typeof p!=='string')return false;if(p.startsWith('/api/world/'))return false;for(var i=0;i<HN.length;i++){var n=HN[i];if(p===n||p.startsWith(n+'?')||p.startsWith(n+'/'))return false;}for(var j=0;j<WP.length;j++){var w=WP[j];if(p===w||p===w.replace(/\\/$/,'')||p.startsWith(w)||p.startsWith(w.replace(/\\/$/,'')+'?')||p.startsWith(w.replace(/\\/$/,'')+'/'))return true;}return false;}function wid(){var p=location.pathname;var m=p.match(/^\\/(world|inbox|session)\\/([^/?#]+)/);if(m)return m[2];if(/^\\/(?:worlds?|workspaces?|world|sandbox|session|inbox|plan|design|repos|runbooks|assets|api|health|favicon)($|\\/|\\?)/.test(p))return null;var r=p.match(/^\\/([a-z][a-z0-9-]+)(?:\\/|$|\\?)/);return r?r[1]:null;}function rw(p){var w=wid();return w?'/api/world/'+w+p:p;}var of=window.fetch.bind(window);window.fetch=function(input,init){var pr;if(typeof input==='string'&&sr(input))pr=of(rw(input),init);else if(input&&typeof input.url==='string'&&sr(input.url))pr=of(new Request(rw(input.url),input),init);else pr=of(input,init);return pr.then(function(res){if(res&&res.status===401)recover();return res;});};var OE=window.EventSource;if(OE){window.EventSource=function(u,i){var s=u;if(typeof s==='string'&&sr(s))s=rw(s);var es=new OE(s,i);es.addEventListener('error',function(){if(es.readyState===OE.CLOSED)recover();});return es;};window.EventSource.prototype=OE.prototype;window.EventSource.CONNECTING=OE.CONNECTING;window.EventSource.OPEN=OE.OPEN;window.EventSource.CLOSED=OE.CLOSED;}})();</script>`;
|
|
2757
2757
|
|
|
2758
|
+
/**
|
|
2759
|
+
* Build the plan-chat bearer injection script. Reads
|
|
2760
|
+
* ~/.olam/plan-chat-secret on every shell render (cheap; tiny file)
|
|
2761
|
+
* so secret rotation propagates without restart. Missing secret →
|
|
2762
|
+
* no injection; PlanChatRoute falls back to the URL-hash channel.
|
|
2763
|
+
*
|
|
2764
|
+
* Operator-local single-user mode only. Multi-tenant deploys will
|
|
2765
|
+
* replace this with one of the 3 candidates in chunks-collection.ts.
|
|
2766
|
+
*/
|
|
2767
|
+
function buildPlanChatBearerInjection() {
|
|
2768
|
+
try {
|
|
2769
|
+
const bearer = readPlanChatSecret();
|
|
2770
|
+
if (!bearer) return '';
|
|
2771
|
+
// JSON.stringify escapes for safe embedding inside the <script>.
|
|
2772
|
+
return `<script>window.__OLAM_PLAN_CHAT_BEARER__=${JSON.stringify(bearer)};</script>`;
|
|
2773
|
+
} catch {
|
|
2774
|
+
return '';
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2758
2778
|
async function renderSpaShell(filePath) {
|
|
2759
2779
|
const stat = fs.statSync(filePath);
|
|
2760
|
-
|
|
2780
|
+
const bearerInjection = buildPlanChatBearerInjection();
|
|
2781
|
+
// Cache key must include the bearer so rotation invalidates correctly.
|
|
2782
|
+
const cacheKey = stat.mtimeMs + ':' + bearerInjection.length;
|
|
2783
|
+
if (_spaCache !== null && _spaCacheKey === cacheKey) {
|
|
2761
2784
|
return _spaCache;
|
|
2762
2785
|
}
|
|
2763
2786
|
let html = fs.readFileSync(filePath, 'utf-8');
|
|
@@ -2768,10 +2791,12 @@ async function renderSpaShell(filePath) {
|
|
|
2768
2791
|
// (/, /worlds, /workspaces, /world/<id>) reference the same bundle.
|
|
2769
2792
|
html = html.replace(/(href|src)="\.\/assets\//g, '$1="/assets/');
|
|
2770
2793
|
// Inject right after <head> so the bootstrap runs before any other
|
|
2771
|
-
// script tag on the page.
|
|
2772
|
-
|
|
2794
|
+
// script tag on the page. Bearer injection runs after the host-cp
|
|
2795
|
+
// bootstrap so window.__OLAM_PLAN_CHAT_BEARER__ is set before the
|
|
2796
|
+
// SPA bundle reads it.
|
|
2797
|
+
html = html.replace(/<head>/i, `<head>\n ${BOOTSTRAP_SCRIPT}\n ${bearerInjection}`);
|
|
2773
2798
|
_spaCache = html;
|
|
2774
|
-
|
|
2799
|
+
_spaCacheKey = cacheKey;
|
|
2775
2800
|
return html;
|
|
2776
2801
|
}
|
|
2777
2802
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pleri/olam-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.147",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"olam": "./bin/olam.cjs"
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"test": "vitest run --passWithNoTests",
|
|
32
32
|
"test:ci": "vitest run --reporter=basic --passWithNoTests",
|
|
33
33
|
"test:docker": "vitest run --config vitest.config.docker.ts",
|
|
34
|
-
"audit:publish-deps": "node scripts/audit-publish-deps.mjs"
|
|
34
|
+
"audit:publish-deps": "node scripts/audit-publish-deps.mjs",
|
|
35
|
+
"audit:cli-bundle-k8s": "node scripts/audit-cli-bundle-k8s.mjs"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"better-sqlite3": "^12.0.0",
|