clay-server 3.4.0-beta.4 → 3.4.0-beta.6
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/lib/project-connection.js +20 -5
- package/lib/project-debate.js +4 -0
- package/lib/project-mate-interaction.js +21 -1
- package/lib/project-memory.js +3 -0
- package/lib/project.js +4 -2
- package/lib/public/app.js +17 -0
- package/lib/public/css/messages.css +9 -0
- package/lib/public/css/pwa-mobile.css +17 -0
- package/lib/public/css/title-bar.css +19 -0
- package/lib/public/index.html +2 -2
- package/lib/public/modules/app-header.js +4 -0
- package/lib/public/modules/notifications.js +7 -1
- package/lib/public/style.css +3 -2
- package/lib/sdk-bridge.js +33 -17
- package/lib/yoke/adapters/codex.js +98 -3
- package/lib/yoke/codex-app-server.js +25 -8
- package/lib/yoke/index.js +7 -2
- package/package.json +1 -1
|
@@ -22,6 +22,17 @@ function dispatchMessageSafely(handleMessage, sendTo, slug, ws, msg) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function buildInitialModelInfo(sm, initialVendor, initialModel, initialModels) {
|
|
26
|
+
return {
|
|
27
|
+
type: "model_info",
|
|
28
|
+
model: initialModel || "",
|
|
29
|
+
models: initialModels || [],
|
|
30
|
+
vendor: initialVendor,
|
|
31
|
+
availableVendors: sm.availableVendors || [],
|
|
32
|
+
installedVendors: sm.installedVendors || [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
25
36
|
/**
|
|
26
37
|
* Attach connection/disconnection handlers to a project context.
|
|
27
38
|
*
|
|
@@ -116,7 +127,7 @@ function attachConnection(ctx) {
|
|
|
116
127
|
if (!_warmedUp) {
|
|
117
128
|
_warmedUp = true;
|
|
118
129
|
if (typeof warmup === "function") {
|
|
119
|
-
try { warmup(); }
|
|
130
|
+
try { warmup(wsUser && wsUser.linuxUser ? wsUser.linuxUser : null); }
|
|
120
131
|
catch (e) { console.error("[project-connection] warmup failed for " + slug + ":", e && e.message ? e.message : e); }
|
|
121
132
|
}
|
|
122
133
|
}
|
|
@@ -167,9 +178,9 @@ function attachConnection(ctx) {
|
|
|
167
178
|
sendTo(ws, { type: "slash_commands", commands: sm.slashCommands });
|
|
168
179
|
}
|
|
169
180
|
var initialModel = (restoredActive && restoredActive.model) || sm.currentModel || "";
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
181
|
+
// Vendor installation state is needed even before a new project has a
|
|
182
|
+
// model. Always send it so reconnects cannot fall back to "Not installed."
|
|
183
|
+
sendTo(ws, buildInitialModelInfo(sm, initialVendor, initialModel, initialModels));
|
|
173
184
|
sendTo(ws, { type: "config_state", model: initialModel, mode: sm.currentPermissionMode || "default", effort: initialEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
174
185
|
sendTo(ws, { type: "last_vendor", vendor: sm.lastVendor || "" });
|
|
175
186
|
sendTo(ws, Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
|
|
@@ -333,4 +344,8 @@ function attachConnection(ctx) {
|
|
|
333
344
|
};
|
|
334
345
|
}
|
|
335
346
|
|
|
336
|
-
module.exports = {
|
|
347
|
+
module.exports = {
|
|
348
|
+
attachConnection: attachConnection,
|
|
349
|
+
buildInitialModelInfo: buildInitialModelInfo,
|
|
350
|
+
dispatchMessageSafely: dispatchMessageSafely,
|
|
351
|
+
};
|
package/lib/project-debate.js
CHANGED
|
@@ -604,6 +604,7 @@ function attachDebate(ctx) {
|
|
|
604
604
|
var _modMate = matesModule.getMate(mateCtx, debate.moderatorId);
|
|
605
605
|
ctx.sdk.createMentionSession({
|
|
606
606
|
vendor: _modMate ? _modMate.vendor : null,
|
|
607
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
607
608
|
claudeMd: claudeMd,
|
|
608
609
|
initialContext: digests,
|
|
609
610
|
initialMessage: quickBriefPrompt,
|
|
@@ -799,6 +800,7 @@ function attachDebate(ctx) {
|
|
|
799
800
|
var _modMate2 = matesModule.getMate(mateCtx, debate.moderatorId);
|
|
800
801
|
ctx.sdk.createMentionSession({
|
|
801
802
|
vendor: _modMate2 ? _modMate2.vendor : null,
|
|
803
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
802
804
|
claudeMd: claudeMd,
|
|
803
805
|
initialContext: moderatorContext,
|
|
804
806
|
initialMessage: "Begin the debate on: " + debate.topic,
|
|
@@ -994,6 +996,7 @@ function attachDebate(ctx) {
|
|
|
994
996
|
var _panMate = matesModule.getMate(debate.mateCtx, mateId);
|
|
995
997
|
ctx.sdk.createMentionSession({
|
|
996
998
|
vendor: _panMate ? _panMate.vendor : null,
|
|
999
|
+
linuxUser: debate.mateCtx && debate.mateCtx.linuxUser ? debate.mateCtx.linuxUser : undefined,
|
|
997
1000
|
claudeMd: claudeMd,
|
|
998
1001
|
initialContext: panelistContext + historyContext,
|
|
999
1002
|
initialMessage: "The moderator addresses you:\n\n" + moderatorText,
|
|
@@ -1530,6 +1533,7 @@ function attachDebate(ctx) {
|
|
|
1530
1533
|
var _modMate3 = matesModule.getMate(mateCtx, debate.moderatorId);
|
|
1531
1534
|
ctx.sdk.createMentionSession({
|
|
1532
1535
|
vendor: _modMate3 ? _modMate3.vendor : null,
|
|
1536
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
1533
1537
|
claudeMd: claudeMd,
|
|
1534
1538
|
initialContext: moderatorContext,
|
|
1535
1539
|
initialMessage: resumePrompt,
|
|
@@ -179,6 +179,7 @@ function attachMateInteraction(ctx) {
|
|
|
179
179
|
var _digestQueue = [];
|
|
180
180
|
var _digestBusy = false;
|
|
181
181
|
var _digestWorkerTurns = 0;
|
|
182
|
+
var _digestWorkerLinuxUser = null;
|
|
182
183
|
var DIGEST_WORKER_MAX_TURNS = 20;
|
|
183
184
|
|
|
184
185
|
function enqueueDigest(job) {
|
|
@@ -191,6 +192,13 @@ function attachMateInteraction(ctx) {
|
|
|
191
192
|
_digestBusy = true;
|
|
192
193
|
var job = _digestQueue.shift();
|
|
193
194
|
|
|
195
|
+
if (_digestWorker && _digestWorkerLinuxUser !== (job.linuxUser || null)) {
|
|
196
|
+
try { _digestWorker.close(); } catch (e) {}
|
|
197
|
+
_digestWorker = null;
|
|
198
|
+
_digestWorkerTurns = 0;
|
|
199
|
+
_digestWorkerLinuxUser = null;
|
|
200
|
+
}
|
|
201
|
+
|
|
194
202
|
var mateDir = matesModule.getMateDir(job.mateCtx, job.mateId);
|
|
195
203
|
var knowledgeDir = path.join(mateDir, "knowledge");
|
|
196
204
|
|
|
@@ -315,6 +323,7 @@ function attachMateInteraction(ctx) {
|
|
|
315
323
|
try { _digestWorker.close(); } catch (e) {}
|
|
316
324
|
_digestWorker = null;
|
|
317
325
|
_digestWorkerTurns = 0;
|
|
326
|
+
_digestWorkerLinuxUser = null;
|
|
318
327
|
}
|
|
319
328
|
|
|
320
329
|
var responseText = "";
|
|
@@ -328,12 +337,14 @@ function attachMateInteraction(ctx) {
|
|
|
328
337
|
console.error("[digest-worker] Error:", err);
|
|
329
338
|
_digestWorker = null;
|
|
330
339
|
_digestWorkerTurns = 0;
|
|
340
|
+
_digestWorkerLinuxUser = null;
|
|
331
341
|
if (job.onError) job.onError(err);
|
|
332
342
|
processDigestQueue();
|
|
333
343
|
},
|
|
334
344
|
});
|
|
335
345
|
} else {
|
|
336
346
|
sdk.createMentionSession({
|
|
347
|
+
linuxUser: job.linuxUser || undefined,
|
|
337
348
|
claudeMd: "",
|
|
338
349
|
model: "haiku",
|
|
339
350
|
initialContext: "[Digest Worker] You generate memory digests. Respond with ONLY JSON.",
|
|
@@ -344,10 +355,15 @@ function attachMateInteraction(ctx) {
|
|
|
344
355
|
onError: function (err) {
|
|
345
356
|
console.error("[digest-worker] Create error:", err);
|
|
346
357
|
_digestWorker = null;
|
|
358
|
+
_digestWorkerLinuxUser = null;
|
|
347
359
|
if (job.onError) job.onError(err);
|
|
348
360
|
processDigestQueue();
|
|
349
361
|
},
|
|
350
|
-
}).then(function (ws) {
|
|
362
|
+
}).then(function (ws) {
|
|
363
|
+
_digestWorker = ws;
|
|
364
|
+
_digestWorkerTurns = 1;
|
|
365
|
+
_digestWorkerLinuxUser = job.linuxUser || null;
|
|
366
|
+
}).catch(function (err) {
|
|
351
367
|
if (job.onError) job.onError(err || new Error("digest worker creation failed"));
|
|
352
368
|
processDigestQueue();
|
|
353
369
|
});
|
|
@@ -378,6 +394,7 @@ function attachMateInteraction(ctx) {
|
|
|
378
394
|
|
|
379
395
|
enqueueDigest({
|
|
380
396
|
mateCtx: mateCtx,
|
|
397
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
381
398
|
mateId: mateId,
|
|
382
399
|
type: "mention",
|
|
383
400
|
conversationContent: conversationContent,
|
|
@@ -473,6 +490,7 @@ function attachMateInteraction(ctx) {
|
|
|
473
490
|
|
|
474
491
|
enqueueDigest({
|
|
475
492
|
mateCtx: mateCtx,
|
|
493
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
476
494
|
mateId: mateId,
|
|
477
495
|
type: "dm",
|
|
478
496
|
priorSummary: priorSummary || "",
|
|
@@ -716,6 +734,7 @@ function attachMateInteraction(ctx) {
|
|
|
716
734
|
// Create new persistent mention session
|
|
717
735
|
sdk.createMentionSession({
|
|
718
736
|
vendor: mate.vendor || null,
|
|
737
|
+
linuxUser: getLinuxUserForSession(session) || undefined,
|
|
719
738
|
claudeMd: claudeMd,
|
|
720
739
|
initialContext: mentionContext,
|
|
721
740
|
initialMessage: mentionFullInput,
|
|
@@ -887,6 +906,7 @@ function attachMateInteraction(ctx) {
|
|
|
887
906
|
].join("\n");
|
|
888
907
|
|
|
889
908
|
sdk.createMentionSession({
|
|
909
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
890
910
|
claudeMd: "",
|
|
891
911
|
model: "haiku",
|
|
892
912
|
initialContext: synthesisContext,
|
package/lib/project-memory.js
CHANGED
|
@@ -339,6 +339,7 @@ function attachMemory(ctx) {
|
|
|
339
339
|
var gateText = "";
|
|
340
340
|
var _gateSession = null;
|
|
341
341
|
sdk.createMentionSession({
|
|
342
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
342
343
|
claudeMd: "",
|
|
343
344
|
model: "haiku",
|
|
344
345
|
initialContext: gateContext,
|
|
@@ -448,6 +449,7 @@ function attachMemory(ctx) {
|
|
|
448
449
|
var updateText = "";
|
|
449
450
|
var _updateSession = null;
|
|
450
451
|
sdk.createMentionSession({
|
|
452
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
451
453
|
claudeMd: "",
|
|
452
454
|
model: "haiku",
|
|
453
455
|
initialContext: updateContext,
|
|
@@ -553,6 +555,7 @@ function attachMemory(ctx) {
|
|
|
553
555
|
var initText = "";
|
|
554
556
|
var _initSession = null;
|
|
555
557
|
sdk.createMentionSession({
|
|
558
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
556
559
|
claudeMd: "",
|
|
557
560
|
model: "haiku",
|
|
558
561
|
initialContext: initContext,
|
package/lib/project.js
CHANGED
|
@@ -835,6 +835,7 @@ function createProjectContext(opts) {
|
|
|
835
835
|
getNotificationsModule: function () { return _notifications; },
|
|
836
836
|
mateDisplayName: opts.mateDisplayName || "",
|
|
837
837
|
isMate: isMate,
|
|
838
|
+
osUsers: osUsers,
|
|
838
839
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
839
840
|
mcpServers: getLocalMcpServers,
|
|
840
841
|
getRemoteMcpServers: function () { return _mcp.getMcpServers(); },
|
|
@@ -1078,6 +1079,7 @@ function createProjectContext(opts) {
|
|
|
1078
1079
|
try {
|
|
1079
1080
|
var readyResult = await vendorAdapter.init({
|
|
1080
1081
|
cwd: cwd,
|
|
1082
|
+
linuxUser: modelLinuxUser || undefined,
|
|
1081
1083
|
clayPort: serverPort,
|
|
1082
1084
|
clayTls: serverTls,
|
|
1083
1085
|
clayAuthToken: serverAuthToken,
|
|
@@ -1686,8 +1688,8 @@ function createProjectContext(opts) {
|
|
|
1686
1688
|
getProject: function () { return project; },
|
|
1687
1689
|
// Exposed so the first websocket connection can lazily warm up the
|
|
1688
1690
|
// adapters for this project (see project-connection handleConnection).
|
|
1689
|
-
warmup: function () {
|
|
1690
|
-
sdk.warmup();
|
|
1691
|
+
warmup: function (linuxUser) {
|
|
1692
|
+
sdk.warmup(osUsers ? linuxUser : null);
|
|
1691
1693
|
sdk.startIdleReaper();
|
|
1692
1694
|
if (!osUsers && !sessionTitleMigrationScheduled) {
|
|
1693
1695
|
sessionTitleMigrationScheduled = true;
|
package/lib/public/app.js
CHANGED
|
@@ -556,6 +556,22 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
556
556
|
var newMsgBtnDefault = "\u2193 Latest";
|
|
557
557
|
var newMsgBtnActivity = "\u2193 New activity";
|
|
558
558
|
|
|
559
|
+
// The composer grows for mobile tabs, safe areas, multiline input, and the
|
|
560
|
+
// keyboard. Keep the jump button immediately above its real height instead
|
|
561
|
+
// of relying on one fixed bottom offset.
|
|
562
|
+
function positionNewMsgButton() {
|
|
563
|
+
var inputArea = $("input-area");
|
|
564
|
+
if (!inputArea) return;
|
|
565
|
+
newMsgBtn.style.bottom = (inputArea.offsetHeight + 12) + "px";
|
|
566
|
+
}
|
|
567
|
+
var inputAreaForNewMsgBtn = $("input-area");
|
|
568
|
+
if (inputAreaForNewMsgBtn && window.ResizeObserver) {
|
|
569
|
+
new ResizeObserver(positionNewMsgButton).observe(inputAreaForNewMsgBtn);
|
|
570
|
+
} else {
|
|
571
|
+
window.addEventListener("resize", positionNewMsgButton);
|
|
572
|
+
}
|
|
573
|
+
positionNewMsgButton();
|
|
574
|
+
|
|
559
575
|
messagesEl.addEventListener("scroll", function () {
|
|
560
576
|
// While sticky-bottom is armed (e.g. just after history_done or a "New
|
|
561
577
|
// activity" click), suppress "user scrolled up" detection. Growth-induced
|
|
@@ -932,6 +948,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
932
948
|
messagesEl: messagesEl,
|
|
933
949
|
sessionListEl: sessionListEl,
|
|
934
950
|
scrollToBottom: scrollToBottom,
|
|
951
|
+
forceScrollToBottom: forceScrollToBottom,
|
|
935
952
|
basePath: basePath,
|
|
936
953
|
toggleUsagePanel: toggleUsagePanel,
|
|
937
954
|
toggleStatusPanel: toggleStatusPanel,
|
|
@@ -5,12 +5,21 @@
|
|
|
5
5
|
#messages {
|
|
6
6
|
flex: 1;
|
|
7
7
|
overflow-y: auto;
|
|
8
|
+
overflow-x: hidden;
|
|
8
9
|
-webkit-overflow-scrolling: touch;
|
|
9
10
|
overscroll-behavior-y: contain;
|
|
10
11
|
padding: 20px 0 12px;
|
|
11
12
|
position: relative;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
/* Rich message content can contain wide tables, diagrams, or generated HTML.
|
|
16
|
+
Keep that content inside the chat scroller; nested code blocks still own
|
|
17
|
+
their intentional horizontal scrolling. */
|
|
18
|
+
#messages > * {
|
|
19
|
+
min-width: 0;
|
|
20
|
+
max-width: 100%;
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
/* --- Remote cursors (Figma-style) --- */
|
|
15
24
|
.remote-cursor {
|
|
16
25
|
pointer-events: none;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* In an iOS Home Screen app, a 100%-tall root can be measured against the
|
|
2
|
+
shortened safe-area viewport. Use the physical viewport for the root while
|
|
3
|
+
retaining safe-area padding on controls inside the app. */
|
|
4
|
+
@media (max-width: 768px) {
|
|
5
|
+
html.pwa-standalone,
|
|
6
|
+
html.pwa-standalone body {
|
|
7
|
+
height: 100vh;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@media (max-width: 768px) and (display-mode: standalone),
|
|
12
|
+
(max-width: 768px) and (display-mode: fullscreen) {
|
|
13
|
+
html,
|
|
14
|
+
body {
|
|
15
|
+
height: 100vh;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -324,6 +324,25 @@
|
|
|
324
324
|
display: none !important;
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
.title-bar-content {
|
|
328
|
+
height: calc(48px + var(--safe-top));
|
|
329
|
+
padding: var(--safe-top) 16px 0;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* The inline title editor must own the header while the keyboard is open.
|
|
333
|
+
Leaving the other controls visible made its usable width nearly zero. */
|
|
334
|
+
.title-bar-content.session-renaming .status,
|
|
335
|
+
.title-bar-content.session-renaming #header-left > :not(.header-rename-input) {
|
|
336
|
+
display: none !important;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.title-bar-content.session-renaming .header-rename-input {
|
|
340
|
+
flex: 1 1 100%;
|
|
341
|
+
width: 100%;
|
|
342
|
+
min-height: 36px;
|
|
343
|
+
font-size: 16px;
|
|
344
|
+
}
|
|
345
|
+
|
|
327
346
|
#sidebar-column {
|
|
328
347
|
width: 0 !important;
|
|
329
348
|
min-width: 0 !important;
|
package/lib/public/index.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
(function(){try{var k="clay-theme-vars",v=localStorage.getItem(k),r=document.documentElement;if(v){var o=JSON.parse(v),p;for(p in o)r.style.setProperty(p,o[p]);var vt=localStorage.getItem(k.replace("-vars","-variant"));if(vt==="light"){r.classList.add("light-theme");r.classList.remove("dark-theme")}else{r.classList.add("dark-theme");r.classList.remove("light-theme")}var m=document.querySelector('meta[name="theme-color"]');if(m&&o["--bg"])m.setAttribute("content",o["--bg"])}else{var sl=window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches;if(sl){r.classList.add("light-theme");r.classList.remove("dark-theme")}}}catch(e){}})();
|
|
31
31
|
</script>
|
|
32
32
|
<script>if(window.navigator.standalone||window.matchMedia("(display-mode:standalone)").matches){document.documentElement.classList.add("pwa-standalone")}</script>
|
|
33
|
-
<link rel="stylesheet" href="style.css?v=
|
|
33
|
+
<link rel="stylesheet" href="style.css?v=20260824f">
|
|
34
34
|
<style>
|
|
35
35
|
@media(max-width:768px){
|
|
36
36
|
/* User messages: vertical stack, avatar on top, right-aligned */
|
|
@@ -2338,7 +2338,7 @@
|
|
|
2338
2338
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"></script>
|
|
2339
2339
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0/lib/addon-web-links.min.js"></script>
|
|
2340
2340
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0/lib/addon-webgl.min.js"></script>
|
|
2341
|
-
<script type="module" src="app.js"></script>
|
|
2341
|
+
<script type="module" src="app.js?v=20260824g"></script>
|
|
2342
2342
|
<div id="pwa-install-modal" class="pwa-modal hidden">
|
|
2343
2343
|
<div class="pwa-modal-backdrop"></div>
|
|
2344
2344
|
<div class="pwa-modal-card">
|
|
@@ -93,6 +93,8 @@ export function initHeader() {
|
|
|
93
93
|
input.type = "text";
|
|
94
94
|
input.className = "header-rename-input";
|
|
95
95
|
input.value = currentText;
|
|
96
|
+
var titleBar = headerTitleEl.closest(".title-bar-content");
|
|
97
|
+
if (titleBar) titleBar.classList.add("session-renaming");
|
|
96
98
|
headerTitleEl.style.display = "none";
|
|
97
99
|
headerRenameBtn.style.display = "none";
|
|
98
100
|
headerTitleEl.parentNode.insertBefore(input, headerTitleEl.nextSibling);
|
|
@@ -117,6 +119,7 @@ export function initHeader() {
|
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
input.remove();
|
|
122
|
+
if (titleBar) titleBar.classList.remove("session-renaming");
|
|
120
123
|
headerTitleEl.style.display = "";
|
|
121
124
|
headerRenameBtn.style.display = "";
|
|
122
125
|
}
|
|
@@ -126,6 +129,7 @@ export function initHeader() {
|
|
|
126
129
|
if (e.key === "Escape") {
|
|
127
130
|
e.preventDefault();
|
|
128
131
|
input.remove();
|
|
132
|
+
if (titleBar) titleBar.classList.remove("session-renaming");
|
|
129
133
|
headerTitleEl.style.display = "";
|
|
130
134
|
headerRenameBtn.style.display = "";
|
|
131
135
|
}
|
|
@@ -72,6 +72,7 @@ export function initNotifications(_ctx) {
|
|
|
72
72
|
if (window.visualViewport) {
|
|
73
73
|
var layout = $("layout");
|
|
74
74
|
var mobileTabBar = document.getElementById("mobile-tab-bar");
|
|
75
|
+
var keyboardWasOpen = false;
|
|
75
76
|
// Capture initial viewport height before any keyboard interaction.
|
|
76
77
|
// In iOS PWA standalone mode, window.innerHeight shrinks when the
|
|
77
78
|
// keyboard opens, so we need a stable baseline for comparison.
|
|
@@ -86,7 +87,12 @@ export function initNotifications(_ctx) {
|
|
|
86
87
|
// Toggle class so CSS can remove the tab-bar bottom padding while keyboard is up
|
|
87
88
|
var keyboardOpen = vv.height < stableViewportHeight - 100;
|
|
88
89
|
document.body.classList.toggle("keyboard-open", keyboardOpen);
|
|
89
|
-
if (
|
|
90
|
+
if (keyboardOpen && !keyboardWasOpen && ctx.forceScrollToBottom) {
|
|
91
|
+
requestAnimationFrame(function() { ctx.forceScrollToBottom(); });
|
|
92
|
+
} else if (!keyboardOpen) {
|
|
93
|
+
ctx.scrollToBottom();
|
|
94
|
+
}
|
|
95
|
+
keyboardWasOpen = keyboardOpen;
|
|
90
96
|
// Hide tab bar when software keyboard is open
|
|
91
97
|
if (mobileTabBar) {
|
|
92
98
|
if (keyboardOpen) {
|
package/lib/public/style.css
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
@import url("css/base.css");
|
|
2
2
|
@import url("css/icon-strip.css");
|
|
3
|
-
@import url("css/title-bar.css");
|
|
3
|
+
@import url("css/title-bar.css?v=20260824");
|
|
4
4
|
@import url("css/pane.css");
|
|
5
5
|
@import url("css/worker-proposal.css");
|
|
6
6
|
@import url("css/sidebar.css");
|
|
7
7
|
@import url("css/overlays.css");
|
|
8
8
|
@import url("css/menus.css");
|
|
9
|
-
@import url("css/messages.css?v=
|
|
9
|
+
@import url("css/messages.css?v=20260824");
|
|
10
10
|
@import url("css/rewind.css");
|
|
11
11
|
@import url("css/input.css");
|
|
12
12
|
@import url("css/filebrowser.css");
|
|
@@ -35,3 +35,4 @@
|
|
|
35
35
|
@import url("css/debate.css");
|
|
36
36
|
@import url("css/notifications-center.css");
|
|
37
37
|
@import url("css/tui-attention.css");
|
|
38
|
+
@import url("css/pwa-mobile.css?v=20260824f");
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -110,6 +110,7 @@ function createSDKBridge(opts) {
|
|
|
110
110
|
var adapters = opts.adapters || {};
|
|
111
111
|
var mateDisplayName = opts.mateDisplayName || "";
|
|
112
112
|
var isMate = opts.isMate || (slug.indexOf("mate-") === 0);
|
|
113
|
+
var osUsers = !!opts.osUsers;
|
|
113
114
|
var dangerouslySkipPermissions = opts.dangerouslySkipPermissions || false;
|
|
114
115
|
// mcpServers may be either a static object or a getter function. The
|
|
115
116
|
// getter form lets callers gate individual servers at call time (e.g.
|
|
@@ -323,6 +324,14 @@ function createSDKBridge(opts) {
|
|
|
323
324
|
var mergeSkills = skills.mergeSkills;
|
|
324
325
|
|
|
325
326
|
// --- Message processing (extracted to sdk-message-processor.js) ---
|
|
327
|
+
function getSessionLinuxUser(session) {
|
|
328
|
+
if (!osUsers || !session) return null;
|
|
329
|
+
if (session.lastLinuxUser) return session.lastLinuxUser;
|
|
330
|
+
if (!session.ownerId) return null;
|
|
331
|
+
var owner = usersModule.findUserById(session.ownerId);
|
|
332
|
+
return owner && owner.linuxUser ? owner.linuxUser : null;
|
|
333
|
+
}
|
|
334
|
+
|
|
326
335
|
// Auto-generate a session title via YOKE adapter.generateTitle().
|
|
327
336
|
// Triggered by sdk-message-processor after AUTO_TITLE_TURN_THRESHOLD turns.
|
|
328
337
|
function autoGenerateTitle(session) {
|
|
@@ -345,7 +354,10 @@ function createSDKBridge(opts) {
|
|
|
345
354
|
}
|
|
346
355
|
console.log("[auto-title] Calling adapter.generateTitle with " + userMessages.length + " messages for session " + session.localId);
|
|
347
356
|
|
|
348
|
-
sessionAdapter.generateTitle(userMessages, {
|
|
357
|
+
sessionAdapter.generateTitle(userMessages, {
|
|
358
|
+
cwd: cwd,
|
|
359
|
+
linuxUser: getSessionLinuxUser(session) || undefined,
|
|
360
|
+
}).then(function(title) {
|
|
349
361
|
if (!title || title.length < 2) return;
|
|
350
362
|
title = title.substring(0, 100);
|
|
351
363
|
if (!session.titleManuallySet) {
|
|
@@ -1131,7 +1143,9 @@ function createSDKBridge(opts) {
|
|
|
1131
1143
|
async function rollbackConversation(session, numTurns) {
|
|
1132
1144
|
var sessionAdapter = getAdapterForSession(session);
|
|
1133
1145
|
if (sessionAdapter && typeof sessionAdapter.rollbackThread === "function") {
|
|
1134
|
-
await sessionAdapter.rollbackThread(session.cliSessionId, numTurns
|
|
1146
|
+
await sessionAdapter.rollbackThread(session.cliSessionId, numTurns, {
|
|
1147
|
+
linuxUser: getSessionLinuxUser(session) || undefined,
|
|
1148
|
+
});
|
|
1135
1149
|
}
|
|
1136
1150
|
// Claude: conversation rollback is handled by rewindFiles + local history trim
|
|
1137
1151
|
}
|
|
@@ -1143,7 +1157,11 @@ function createSDKBridge(opts) {
|
|
|
1143
1157
|
|
|
1144
1158
|
async function forkSessionUnified(session, uuid) {
|
|
1145
1159
|
var sessionAdapter = getAdapterForSession(session);
|
|
1146
|
-
var result = await sessionAdapter.forkSession(session.cliSessionId, {
|
|
1160
|
+
var result = await sessionAdapter.forkSession(session.cliSessionId, {
|
|
1161
|
+
upToMessageId: uuid,
|
|
1162
|
+
dir: cwd,
|
|
1163
|
+
linuxUser: getSessionLinuxUser(session) || undefined,
|
|
1164
|
+
});
|
|
1147
1165
|
if (!result || !result.sessionId) throw new Error("Fork returned no session id");
|
|
1148
1166
|
|
|
1149
1167
|
// Adapters with rollbackThread (e.g. Codex) use local history copy
|
|
@@ -1471,6 +1489,7 @@ function createSDKBridge(opts) {
|
|
|
1471
1489
|
|
|
1472
1490
|
var queryOpts = {
|
|
1473
1491
|
cwd: cwd,
|
|
1492
|
+
linuxUser: linuxUser || undefined,
|
|
1474
1493
|
// appendSystemPrompt, NOT systemPrompt: a plain-string systemPrompt
|
|
1475
1494
|
// REPLACES the entire Claude Code system prompt (sdk.d.ts:2096), which
|
|
1476
1495
|
// would strip the driver session down to the two-sentence pair preset.
|
|
@@ -1840,6 +1859,13 @@ function createSDKBridge(opts) {
|
|
|
1840
1859
|
var defaultVendor = adapter ? adapter.vendor : "claude";
|
|
1841
1860
|
sm.defaultVendor = defaultVendor;
|
|
1842
1861
|
|
|
1862
|
+
// Installation detection is independent of adapter initialization. Publish
|
|
1863
|
+
// it before warmup so a new project does not render the initial empty state
|
|
1864
|
+
// as if every CLI were missing while the default adapter starts up.
|
|
1865
|
+
sm.installedVendors = detectInstalledVendors(linuxUser);
|
|
1866
|
+
sm.availableVendors = getAvailableVendors(linuxUser);
|
|
1867
|
+
sendModelInfoForVendor(defaultVendor, sm.currentModel || "");
|
|
1868
|
+
|
|
1843
1869
|
// Initialize default adapter first (provides skills, slash commands, etc.)
|
|
1844
1870
|
if (adapter) {
|
|
1845
1871
|
try {
|
|
@@ -1891,19 +1917,8 @@ function createSDKBridge(opts) {
|
|
|
1891
1917
|
// actually issues a query with it.
|
|
1892
1918
|
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
1893
1919
|
|
|
1894
|
-
//
|
|
1895
|
-
sm.
|
|
1896
|
-
sm.availableVendors = getAvailableVendors(linuxUser);
|
|
1897
|
-
|
|
1898
|
-
// Send initial state to client
|
|
1899
|
-
send({
|
|
1900
|
-
type: "model_info",
|
|
1901
|
-
model: sm.currentModel || "",
|
|
1902
|
-
models: getModelsForVendor(defaultVendor),
|
|
1903
|
-
vendor: defaultVendor,
|
|
1904
|
-
availableVendors: sm.availableVendors,
|
|
1905
|
-
installedVendors: sm.installedVendors,
|
|
1906
|
-
});
|
|
1920
|
+
// Send the fully initialized state to the client.
|
|
1921
|
+
sendModelInfoForVendor(defaultVendor, sm.currentModel || "");
|
|
1907
1922
|
}
|
|
1908
1923
|
|
|
1909
1924
|
async function setModel(session, model) {
|
|
@@ -2017,7 +2032,7 @@ function createSDKBridge(opts) {
|
|
|
2017
2032
|
// Creates a mention session that can be reused across multiple mentions
|
|
2018
2033
|
// within a conversation flow (session continuity).
|
|
2019
2034
|
async function createMentionSession(opts) {
|
|
2020
|
-
// opts: { vendor, claudeMd, initialContext, initialMessage, onDelta, onDone, onError, onActivity }
|
|
2035
|
+
// opts: { vendor, linuxUser, claudeMd, initialContext, initialMessage, onDelta, onDone, onError, onActivity }
|
|
2021
2036
|
var abortController = new AbortController();
|
|
2022
2037
|
|
|
2023
2038
|
// Current response callbacks (swapped on each pushMessage)
|
|
@@ -2037,6 +2052,7 @@ function createSDKBridge(opts) {
|
|
|
2037
2052
|
try {
|
|
2038
2053
|
handle = await mentionAdapter.createQuery({
|
|
2039
2054
|
cwd: cwd,
|
|
2055
|
+
linuxUser: opts.linuxUser || undefined,
|
|
2040
2056
|
systemPrompt: opts.claudeMd,
|
|
2041
2057
|
model: opts.model || undefined,
|
|
2042
2058
|
toolServers: opts.includeMcpServers ? (mergeMcpServers(getMcpServers(), getRemoteMcpServers) || undefined) : undefined,
|
|
@@ -7,6 +7,7 @@ var path = require("path");
|
|
|
7
7
|
var fs = require("fs");
|
|
8
8
|
var { CodexAppServer } = require("../codex-app-server");
|
|
9
9
|
var skillDiscovery = require("../skill-discovery");
|
|
10
|
+
var { resolveOsUserInfo } = require("../../os-users");
|
|
10
11
|
|
|
11
12
|
// --- Event flattening ---
|
|
12
13
|
// Converts app-server JSON-RPC notifications into flat objects with a yokeType field.
|
|
@@ -1220,6 +1221,13 @@ function createCodexAdapter(opts) {
|
|
|
1220
1221
|
var _cwd = (opts && opts.cwd) || process.cwd();
|
|
1221
1222
|
var _slug = (opts && opts.slug) || "";
|
|
1222
1223
|
var _defaultInitOpts = Object.assign({}, opts || {});
|
|
1224
|
+
var _runtimeLinuxUser = (opts && opts.runtimeLinuxUser) || null;
|
|
1225
|
+
var _requiresLinuxUser = !!(opts && opts.osUsers) && !_runtimeLinuxUser;
|
|
1226
|
+
var _userRuntimes = Object.create(null);
|
|
1227
|
+
var _resolveOsUserInfo = (opts && opts.resolveOsUserInfo) || resolveOsUserInfo;
|
|
1228
|
+
var _createAppServer = (opts && opts.createAppServer) || function(serverOpts) {
|
|
1229
|
+
return new CodexAppServer(null, serverOpts);
|
|
1230
|
+
};
|
|
1223
1231
|
// Codex models are a fixed list (the app-server doesn't enumerate them), so
|
|
1224
1232
|
// model listing must not depend on a successful app-server init — otherwise a
|
|
1225
1233
|
// slow/failed `initialize` leaves the picker empty and the chip shows the
|
|
@@ -1244,6 +1252,45 @@ function createCodexAdapter(opts) {
|
|
|
1244
1252
|
var _shuttingDown = false;
|
|
1245
1253
|
var _activeQueries = [];
|
|
1246
1254
|
|
|
1255
|
+
function withoutLinuxUser(callOpts) {
|
|
1256
|
+
var cleaned = Object.assign({}, callOpts || {});
|
|
1257
|
+
delete cleaned.linuxUser;
|
|
1258
|
+
return cleaned;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
function getUserRuntime(linuxUser) {
|
|
1262
|
+
if (!linuxUser) return null;
|
|
1263
|
+
if (_runtimeLinuxUser) {
|
|
1264
|
+
if (_runtimeLinuxUser !== linuxUser) {
|
|
1265
|
+
throw new Error("Codex runtime user mismatch: expected " + _runtimeLinuxUser + ", received " + linuxUser);
|
|
1266
|
+
}
|
|
1267
|
+
return adapter;
|
|
1268
|
+
}
|
|
1269
|
+
if (!_userRuntimes[linuxUser]) {
|
|
1270
|
+
var runtimeOpts = Object.assign({}, _defaultInitOpts, {
|
|
1271
|
+
runtimeLinuxUser: linuxUser,
|
|
1272
|
+
});
|
|
1273
|
+
delete runtimeOpts.linuxUser;
|
|
1274
|
+
_userRuntimes[linuxUser] = createCodexAdapter(runtimeOpts);
|
|
1275
|
+
}
|
|
1276
|
+
return _userRuntimes[linuxUser];
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
function shutdownUserRuntimes(method, idleMs) {
|
|
1280
|
+
var users = Object.keys(_userRuntimes);
|
|
1281
|
+
if (!users.length) return Promise.resolve([]);
|
|
1282
|
+
return Promise.all(users.map(function(linuxUser) {
|
|
1283
|
+
var runtime = _userRuntimes[linuxUser];
|
|
1284
|
+
var result = method === "shutdownIfIdle"
|
|
1285
|
+
? runtime.shutdownIfIdle(idleMs)
|
|
1286
|
+
: runtime.shutdown();
|
|
1287
|
+
return Promise.resolve(result).then(function(stopped) {
|
|
1288
|
+
if (method === "shutdown" || stopped) delete _userRuntimes[linuxUser];
|
|
1289
|
+
return stopped;
|
|
1290
|
+
});
|
|
1291
|
+
}));
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1247
1294
|
function updateLastActiveAt() {
|
|
1248
1295
|
_lastActiveAt = Date.now();
|
|
1249
1296
|
}
|
|
@@ -1396,6 +1443,16 @@ function createCodexAdapter(opts) {
|
|
|
1396
1443
|
vendor: "codex",
|
|
1397
1444
|
|
|
1398
1445
|
init: function(initOpts) {
|
|
1446
|
+
var requestedLinuxUser = initOpts && initOpts.linuxUser;
|
|
1447
|
+
if (!_runtimeLinuxUser && requestedLinuxUser) {
|
|
1448
|
+
return getUserRuntime(requestedLinuxUser).init(withoutLinuxUser(initOpts));
|
|
1449
|
+
}
|
|
1450
|
+
if (_requiresLinuxUser) {
|
|
1451
|
+
return Promise.reject(new Error("Codex requires a mapped Linux user while OS-user isolation is enabled"));
|
|
1452
|
+
}
|
|
1453
|
+
if (_runtimeLinuxUser && requestedLinuxUser && requestedLinuxUser !== _runtimeLinuxUser) {
|
|
1454
|
+
return Promise.reject(new Error("Codex runtime user mismatch"));
|
|
1455
|
+
}
|
|
1399
1456
|
if (_shuttingDown) {
|
|
1400
1457
|
return Promise.reject(createShutdownError());
|
|
1401
1458
|
}
|
|
@@ -1412,6 +1469,9 @@ function createCodexAdapter(opts) {
|
|
|
1412
1469
|
|
|
1413
1470
|
_initPromise = (async function() {
|
|
1414
1471
|
var serverOpts = { cwd: _cwd };
|
|
1472
|
+
if (_runtimeLinuxUser) {
|
|
1473
|
+
serverOpts.osUserInfo = _resolveOsUserInfo(_runtimeLinuxUser);
|
|
1474
|
+
}
|
|
1415
1475
|
|
|
1416
1476
|
// Extract adapter options
|
|
1417
1477
|
if (effectiveInitOpts && effectiveInitOpts.adapterOptions && effectiveInitOpts.adapterOptions.CODEX) {
|
|
@@ -1478,7 +1538,7 @@ function createCodexAdapter(opts) {
|
|
|
1478
1538
|
}
|
|
1479
1539
|
|
|
1480
1540
|
// Spawn and initialize app-server
|
|
1481
|
-
_appServer =
|
|
1541
|
+
_appServer = _createAppServer(serverOpts);
|
|
1482
1542
|
await _appServer.start();
|
|
1483
1543
|
|
|
1484
1544
|
await _appServer.send("initialize", {
|
|
@@ -1565,6 +1625,16 @@ function createCodexAdapter(opts) {
|
|
|
1565
1625
|
},
|
|
1566
1626
|
|
|
1567
1627
|
createQuery: async function(queryOpts) {
|
|
1628
|
+
var requestedLinuxUser = queryOpts && queryOpts.linuxUser;
|
|
1629
|
+
if (!_runtimeLinuxUser && requestedLinuxUser) {
|
|
1630
|
+
return getUserRuntime(requestedLinuxUser).createQuery(withoutLinuxUser(queryOpts));
|
|
1631
|
+
}
|
|
1632
|
+
if (_requiresLinuxUser) {
|
|
1633
|
+
throw new Error("Codex requires a mapped Linux user while OS-user isolation is enabled");
|
|
1634
|
+
}
|
|
1635
|
+
if (_runtimeLinuxUser && requestedLinuxUser && requestedLinuxUser !== _runtimeLinuxUser) {
|
|
1636
|
+
throw new Error("Codex runtime user mismatch");
|
|
1637
|
+
}
|
|
1568
1638
|
if (_shuttingDown) {
|
|
1569
1639
|
throw createShutdownError();
|
|
1570
1640
|
}
|
|
@@ -1661,6 +1731,13 @@ function createCodexAdapter(opts) {
|
|
|
1661
1731
|
|
|
1662
1732
|
// --- Title generation ---
|
|
1663
1733
|
generateTitle: async function(messages, opts) {
|
|
1734
|
+
var requestedLinuxUser = opts && opts.linuxUser;
|
|
1735
|
+
if (!_runtimeLinuxUser && requestedLinuxUser) {
|
|
1736
|
+
return getUserRuntime(requestedLinuxUser).generateTitle(messages, withoutLinuxUser(opts));
|
|
1737
|
+
}
|
|
1738
|
+
if (_requiresLinuxUser) {
|
|
1739
|
+
throw new Error("Codex requires a mapped Linux user while OS-user isolation is enabled");
|
|
1740
|
+
}
|
|
1664
1741
|
var systemPrompt = "You are a title generator. Output only a short title (3-8 words). No quotes, no punctuation at the end, no explanation.";
|
|
1665
1742
|
var prompt = "Below is a conversation between a user and an AI assistant. Generate a short, descriptive title (3-8 words) that captures the main topic. Reply with ONLY the title, nothing else.\n\n";
|
|
1666
1743
|
for (var i = 0; i < messages.length; i++) {
|
|
@@ -1708,6 +1785,11 @@ function createCodexAdapter(opts) {
|
|
|
1708
1785
|
listSessions: function() { return Promise.resolve([]); },
|
|
1709
1786
|
renameSession: function() { return Promise.resolve(); },
|
|
1710
1787
|
forkSession: function(threadId, opts) {
|
|
1788
|
+
var requestedLinuxUser = opts && opts.linuxUser;
|
|
1789
|
+
if (!_runtimeLinuxUser && requestedLinuxUser) {
|
|
1790
|
+
return getUserRuntime(requestedLinuxUser).forkSession(threadId, withoutLinuxUser(opts));
|
|
1791
|
+
}
|
|
1792
|
+
if (_requiresLinuxUser) return Promise.reject(new Error("Codex requires a mapped Linux user while OS-user isolation is enabled"));
|
|
1711
1793
|
if (!_appServer || !_appServer.started) return Promise.resolve(null);
|
|
1712
1794
|
return _appServer.send("thread/fork", { threadId: threadId }, 30000).then(function(result) {
|
|
1713
1795
|
var newThreadId = (result && result.thread) ? result.thread.id : null;
|
|
@@ -1715,17 +1797,30 @@ function createCodexAdapter(opts) {
|
|
|
1715
1797
|
return { sessionId: newThreadId };
|
|
1716
1798
|
});
|
|
1717
1799
|
},
|
|
1718
|
-
rollbackThread: function(threadId, numTurns) {
|
|
1800
|
+
rollbackThread: function(threadId, numTurns, opts) {
|
|
1801
|
+
var requestedLinuxUser = opts && opts.linuxUser;
|
|
1802
|
+
if (!_runtimeLinuxUser && requestedLinuxUser) {
|
|
1803
|
+
return getUserRuntime(requestedLinuxUser).rollbackThread(threadId, numTurns, withoutLinuxUser(opts));
|
|
1804
|
+
}
|
|
1805
|
+
if (_requiresLinuxUser) return Promise.reject(new Error("Codex requires a mapped Linux user while OS-user isolation is enabled"));
|
|
1719
1806
|
if (!_appServer || !_appServer.started) return Promise.resolve(null);
|
|
1720
1807
|
return _appServer.send("thread/rollback", { threadId: threadId, numTurns: numTurns }, 30000);
|
|
1721
1808
|
},
|
|
1722
1809
|
|
|
1723
1810
|
// Shutdown the app-server process
|
|
1724
1811
|
shutdown: function() {
|
|
1725
|
-
return
|
|
1812
|
+
return Promise.all([
|
|
1813
|
+
beginShutdown(true),
|
|
1814
|
+
shutdownUserRuntimes("shutdown"),
|
|
1815
|
+
]).then(function() { return true; });
|
|
1726
1816
|
},
|
|
1727
1817
|
|
|
1728
1818
|
shutdownIfIdle: function(idleMs) {
|
|
1819
|
+
if (!_runtimeLinuxUser && Object.keys(_userRuntimes).length > 0) {
|
|
1820
|
+
return shutdownUserRuntimes("shutdownIfIdle", idleMs).then(function(results) {
|
|
1821
|
+
return results.some(function(stopped) { return !!stopped; });
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1729
1824
|
if (_shuttingDown || _shutdownPromise) return Promise.resolve(false);
|
|
1730
1825
|
if (_initPromise) return Promise.resolve(false);
|
|
1731
1826
|
if (!_appServer) return Promise.resolve(false);
|
|
@@ -9,6 +9,8 @@ var readline = require("readline");
|
|
|
9
9
|
var path = require("path");
|
|
10
10
|
var fs = require("fs");
|
|
11
11
|
var { createRequire } = require("module");
|
|
12
|
+
var { buildUserEnv } = require("../build-user-env");
|
|
13
|
+
var { wrapSpawnAsUser } = require("../os-users");
|
|
12
14
|
|
|
13
15
|
// --- Find the codex binary path ---
|
|
14
16
|
// Mirrors the logic from @openai/codex-sdk findCodexPath()
|
|
@@ -120,14 +122,30 @@ function CodexAppServer(executablePath, opts) {
|
|
|
120
122
|
this._stderrBuf = "";
|
|
121
123
|
}
|
|
122
124
|
|
|
125
|
+
function buildSpawnSpec(executablePath, args, opts, wrapFn) {
|
|
126
|
+
opts = opts || {};
|
|
127
|
+
var osUserInfo = opts.osUserInfo || null;
|
|
128
|
+
var env = osUserInfo
|
|
129
|
+
? Object.assign(buildUserEnv(osUserInfo), opts.env || {})
|
|
130
|
+
: Object.assign({}, process.env, opts.env || {});
|
|
131
|
+
var spawnOptions = {
|
|
132
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
133
|
+
env: env,
|
|
134
|
+
cwd: opts.cwd || process.cwd(),
|
|
135
|
+
};
|
|
136
|
+
if (osUserInfo) {
|
|
137
|
+
spawnOptions.uid = osUserInfo.uid;
|
|
138
|
+
spawnOptions.gid = osUserInfo.gid;
|
|
139
|
+
}
|
|
140
|
+
return (wrapFn || wrapSpawnAsUser)(executablePath, args, spawnOptions);
|
|
141
|
+
}
|
|
142
|
+
|
|
123
143
|
CodexAppServer.prototype.start = function() {
|
|
124
144
|
var self = this;
|
|
125
145
|
|
|
126
146
|
return new Promise(function(resolve, reject) {
|
|
127
147
|
try {
|
|
128
148
|
var args = ["app-server"];
|
|
129
|
-
var env = Object.assign({}, process.env, self.opts.env || {});
|
|
130
|
-
|
|
131
149
|
// Pass config overrides via --config key=value flags
|
|
132
150
|
if (self.opts.config) {
|
|
133
151
|
var configArgs = serializeConfig(self.opts.config, "");
|
|
@@ -136,13 +154,11 @@ CodexAppServer.prototype.start = function() {
|
|
|
136
154
|
}
|
|
137
155
|
}
|
|
138
156
|
|
|
139
|
-
|
|
157
|
+
var spawnSpec = buildSpawnSpec(self.executablePath, args, self.opts);
|
|
158
|
+
var userSuffix = self.opts.osUserInfo ? " as " + self.opts.osUserInfo.user : "";
|
|
159
|
+
console.log("[codex-app-server] Spawning" + userSuffix + ":", spawnSpec.command, spawnSpec.args.join(" "));
|
|
140
160
|
|
|
141
|
-
self.proc = spawn(
|
|
142
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
143
|
-
env: env,
|
|
144
|
-
cwd: self.opts.cwd || process.cwd(),
|
|
145
|
-
});
|
|
161
|
+
self.proc = spawn(spawnSpec.command, spawnSpec.args, spawnSpec.options);
|
|
146
162
|
|
|
147
163
|
self.proc.on("error", function(err) {
|
|
148
164
|
console.error("[codex-app-server] Process error:", err.message);
|
|
@@ -383,4 +399,5 @@ CodexAppServer.prototype.stop = function() {
|
|
|
383
399
|
module.exports = {
|
|
384
400
|
CodexAppServer: CodexAppServer,
|
|
385
401
|
findCodexPath: findCodexPath,
|
|
402
|
+
buildSpawnSpec: buildSpawnSpec,
|
|
386
403
|
};
|
package/lib/yoke/index.js
CHANGED
|
@@ -350,7 +350,7 @@ function createAdapters(opts) {
|
|
|
350
350
|
|
|
351
351
|
if (installed.codex && supportsConfiguredIsolation("codex")) {
|
|
352
352
|
try {
|
|
353
|
-
adapters.codex = createAdapter({ vendor: "codex", cwd: opts.cwd, slug: opts.slug });
|
|
353
|
+
adapters.codex = createAdapter({ vendor: "codex", cwd: opts.cwd, slug: opts.slug, osUsers: !!opts.osUsers });
|
|
354
354
|
auth.codex = true;
|
|
355
355
|
console.log("[yoke] Adapter created: codex");
|
|
356
356
|
} catch (e) {
|
|
@@ -411,7 +411,12 @@ async function lazyCreateAdapter(adapters, vendor, opts) {
|
|
|
411
411
|
if (!installed[vendor]) return null;
|
|
412
412
|
|
|
413
413
|
try {
|
|
414
|
-
var ad = createAdapter({
|
|
414
|
+
var ad = createAdapter({
|
|
415
|
+
vendor: vendor,
|
|
416
|
+
cwd: opts.cwd,
|
|
417
|
+
slug: opts.slug,
|
|
418
|
+
osUsers: !!(opts.osUsers || opts.linuxUser),
|
|
419
|
+
});
|
|
415
420
|
if (typeof ad.init === "function") {
|
|
416
421
|
await ad.init(opts || {});
|
|
417
422
|
}
|
package/package.json
CHANGED