@pleri/olam-cli 0.1.144 → 0.1.146
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/doctor.d.ts +53 -23
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +117 -46
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/logs.d.ts +17 -3
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +38 -35
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/memory/bridge.d.ts +57 -0
- package/dist/commands/memory/bridge.d.ts.map +1 -0
- package/dist/commands/memory/bridge.js +156 -0
- package/dist/commands/memory/bridge.js.map +1 -0
- package/dist/commands/memory/index.d.ts +3 -0
- package/dist/commands/memory/index.d.ts.map +1 -1
- package/dist/commands/memory/index.js +10 -1
- package/dist/commands/memory/index.js.map +1 -1
- package/dist/commands/memory/reclassify.d.ts +56 -0
- package/dist/commands/memory/reclassify.d.ts.map +1 -0
- package/dist/commands/memory/reclassify.js +177 -0
- package/dist/commands/memory/reclassify.js.map +1 -0
- package/dist/commands/memory/stats.d.ts +69 -0
- package/dist/commands/memory/stats.d.ts.map +1 -0
- package/dist/commands/memory/stats.js +164 -0
- package/dist/commands/memory/stats.js.map +1 -0
- 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.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 +37 -0
- package/dist/commands/skills-source.d.ts.map +1 -0
- package/dist/commands/skills-source.js +431 -0
- package/dist/commands/skills-source.js.map +1 -0
- package/dist/commands/skills.d.ts +11 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +170 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/status.d.ts +27 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +102 -1
- package/dist/commands/status.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 +8394 -3876
- 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/health-probes.d.ts +72 -0
- package/dist/lib/health-probes.d.ts.map +1 -1
- package/dist/lib/health-probes.js +218 -0
- package/dist/lib/health-probes.js.map +1 -1
- 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 +3328 -1166
- 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/agent-runtime-trigger.mjs +74 -4
- package/host-cp/src/engine-identity.mjs +32 -0
- package/host-cp/src/plan-chat-service.mjs +31 -7
- package/host-cp/src/server.mjs +219 -9
- 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"
|
|
@@ -165,11 +165,81 @@ export async function triggerAgentRuntime(args) {
|
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
// Container mode (docker-socket-proxy
|
|
169
|
-
//
|
|
170
|
-
//
|
|
168
|
+
// Container mode (docker-socket-proxy on tcp://<host>:<port>).
|
|
169
|
+
// Two-step Docker API exec: POST /containers/<name>/exec creates an
|
|
170
|
+
// exec instance, then POST /exec/<id>/start with Detach=true runs it
|
|
171
|
+
// in the background. Matches the pattern in container-secret-fetcher.mjs.
|
|
172
|
+
if (dockerHost.startsWith('tcp://')) {
|
|
173
|
+
const apiBase = dockerHost.replace(/^tcp:\/\//, 'http://');
|
|
174
|
+
|
|
175
|
+
// Step 0: verify the container is running.
|
|
176
|
+
const inspectRes = await fetch(
|
|
177
|
+
`${apiBase}/containers/${encodeURIComponent(containerName)}/json`,
|
|
178
|
+
);
|
|
179
|
+
if (!inspectRes.ok) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`socket-proxy GET /containers/${containerName}/json: ${inspectRes.status} ${inspectRes.statusText}`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const inspect = await inspectRes.json();
|
|
185
|
+
if (!inspect?.State?.Running) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`container ${containerName} is not running (state: ${JSON.stringify(inspect?.State)})`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Step 1: create exec instance with env injection.
|
|
192
|
+
const createRes = await fetch(
|
|
193
|
+
`${apiBase}/containers/${encodeURIComponent(containerName)}/exec`,
|
|
194
|
+
{
|
|
195
|
+
method: 'POST',
|
|
196
|
+
headers: { 'Content-Type': 'application/json' },
|
|
197
|
+
body: JSON.stringify({
|
|
198
|
+
Cmd: ['node', supervisorPath],
|
|
199
|
+
Env: [
|
|
200
|
+
`HOST_CP_URL=${hostCpUrl}`,
|
|
201
|
+
`HOST_CP_BEARER=${bearer}`,
|
|
202
|
+
`WORLD_ID=${worldId}`,
|
|
203
|
+
`SESSION_ID=${sessionId}`,
|
|
204
|
+
],
|
|
205
|
+
AttachStdout: false,
|
|
206
|
+
AttachStderr: false,
|
|
207
|
+
Tty: false,
|
|
208
|
+
}),
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
if (!createRes.ok) {
|
|
212
|
+
const errBody = await createRes.text().catch(() => '<no body>');
|
|
213
|
+
throw new Error(
|
|
214
|
+
`socket-proxy POST /containers/${containerName}/exec: ${createRes.status} — ${errBody}`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
const { Id: execId } = await createRes.json();
|
|
218
|
+
|
|
219
|
+
// Step 2: start exec in detached mode.
|
|
220
|
+
const startRes = await fetch(`${apiBase}/exec/${execId}/start`, {
|
|
221
|
+
method: 'POST',
|
|
222
|
+
headers: { 'Content-Type': 'application/json' },
|
|
223
|
+
body: JSON.stringify({ Detach: true, Tty: false }),
|
|
224
|
+
});
|
|
225
|
+
if (!startRes.ok && startRes.status !== 200) {
|
|
226
|
+
const errBody = await startRes.text().catch(() => '<no body>');
|
|
227
|
+
throw new Error(
|
|
228
|
+
`socket-proxy POST /exec/${execId}/start: ${startRes.status} — ${errBody}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
liveSpawns.set(k, { spawnedAt: Date.now(), execId });
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
status: 'spawned',
|
|
236
|
+
container: containerName,
|
|
237
|
+
execId,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
171
241
|
throw new Error(
|
|
172
|
-
`triggerAgentRuntime: dockerHost mode '${dockerHost}'
|
|
242
|
+
`triggerAgentRuntime: unsupported dockerHost mode '${dockerHost}'`,
|
|
173
243
|
);
|
|
174
244
|
}
|
|
175
245
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Container-engine identity for host-cp.
|
|
2
|
+
//
|
|
3
|
+
// Phase 1a / A1: defaults to "docker"; switches to "kubernetes" when running
|
|
4
|
+
// inside a K8s pod (autodetected via KUBERNETES_SERVICE_HOST). Operators can
|
|
5
|
+
// override either way via OLAM_HOST_CP_ENGINE.
|
|
6
|
+
//
|
|
7
|
+
// This module exists separately from server.mjs to keep the engine-resolution
|
|
8
|
+
// logic pure (no I/O, no mkdir, no global side-effects) so unit tests can
|
|
9
|
+
// import it without triggering server startup. server.mjs imports
|
|
10
|
+
// resolveHostCpEngine from here and computes its module-level HOST_CP_ENGINE
|
|
11
|
+
// constant.
|
|
12
|
+
//
|
|
13
|
+
// KubernetesEngine adapter (Phase B / PR3) consumes the same env variables
|
|
14
|
+
// when constructing the engine; the context-allowlist guard (T6 / Decision 10)
|
|
15
|
+
// lives inside that adapter, not here. This module is "what name to surface
|
|
16
|
+
// in the X-Olam-Engine response header" — nothing more.
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the active container-engine identity for host-cp.
|
|
20
|
+
*
|
|
21
|
+
* Precedence (matches HOST_CP_MODE convention at server.mjs:85-87):
|
|
22
|
+
* 1. Explicit env override: OLAM_HOST_CP_ENGINE=docker|kubernetes
|
|
23
|
+
* 2. Autodetect: KUBERNETES_SERVICE_HOST set → "kubernetes"
|
|
24
|
+
* 3. Default: "docker"
|
|
25
|
+
*
|
|
26
|
+
* @param {NodeJS.ProcessEnv} [env=process.env] - environment to inspect.
|
|
27
|
+
* @returns {string} - engine identity surfaced via X-Olam-Engine header.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveHostCpEngine(env = process.env) {
|
|
30
|
+
return env.OLAM_HOST_CP_ENGINE
|
|
31
|
+
?? (env.KUBERNETES_SERVICE_HOST ? 'kubernetes' : 'docker');
|
|
32
|
+
}
|
|
@@ -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;
|