@yemi33/squad 0.1.2 → 0.1.4
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/dashboard.html +1 -1
- package/engine.js +26 -3
- package/package.json +1 -1
package/dashboard.html
CHANGED
|
@@ -387,7 +387,7 @@
|
|
|
387
387
|
<section class="cmd-center">
|
|
388
388
|
<h2>Command Center</h2>
|
|
389
389
|
<div class="cmd-input-wrap" id="cmd-input-wrap">
|
|
390
|
-
<textarea id="cmd-input" rows="1" placeholder='What do you need? e.g. "Fix the auth bug @dallas" or "/
|
|
390
|
+
<textarea id="cmd-input" rows="1" placeholder='What do you need? e.g. "Fix the auth bug @dallas" or "/note always use feature flags"'
|
|
391
391
|
oninput="cmdInputChanged()" onkeydown="cmdKeyDown(event)"></textarea>
|
|
392
392
|
<button class="cmd-send-btn" id="cmd-send-btn" onclick="cmdSubmit()">Send <kbd>Ctrl+Enter</kbd></button>
|
|
393
393
|
</div>
|
package/engine.js
CHANGED
|
@@ -128,10 +128,24 @@ function safeWrite(p, data) {
|
|
|
128
128
|
const tmp = p + '.tmp.' + process.pid;
|
|
129
129
|
try {
|
|
130
130
|
fs.writeFileSync(tmp, content);
|
|
131
|
-
|
|
131
|
+
// Atomic rename — retry on Windows EPERM (file locking)
|
|
132
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
133
|
+
try {
|
|
134
|
+
fs.renameSync(tmp, p);
|
|
135
|
+
return;
|
|
136
|
+
} catch (e) {
|
|
137
|
+
if (e.code === 'EPERM' && attempt < 2) {
|
|
138
|
+
// Brief sync sleep (10ms) then retry
|
|
139
|
+
const start = Date.now(); while (Date.now() - start < 10) {}
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
throw e;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
132
145
|
} catch (e) {
|
|
146
|
+
// Fallback: direct write if atomic rename fails entirely
|
|
133
147
|
try { fs.unlinkSync(tmp); } catch {}
|
|
134
|
-
|
|
148
|
+
try { fs.writeFileSync(p, content); } catch {}
|
|
135
149
|
}
|
|
136
150
|
}
|
|
137
151
|
|
|
@@ -1146,13 +1160,22 @@ function getAdoToken() {
|
|
|
1146
1160
|
return null;
|
|
1147
1161
|
}
|
|
1148
1162
|
|
|
1149
|
-
async function adoFetch(url, token) {
|
|
1163
|
+
async function adoFetch(url, token, _retried = false) {
|
|
1150
1164
|
const res = await fetch(url, {
|
|
1151
1165
|
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
|
|
1152
1166
|
});
|
|
1153
1167
|
if (!res.ok) throw new Error(`ADO API ${res.status}: ${res.statusText}`);
|
|
1154
1168
|
const text = await res.text();
|
|
1155
1169
|
if (!text || text.trimStart().startsWith('<')) {
|
|
1170
|
+
// Auth redirect — token likely expired. Invalidate cache and retry once.
|
|
1171
|
+
if (!_retried) {
|
|
1172
|
+
_adoTokenCache = { token: null, expiresAt: 0 };
|
|
1173
|
+
const freshToken = getAdoToken();
|
|
1174
|
+
if (freshToken) {
|
|
1175
|
+
log('info', 'ADO token expired mid-session — refreshed and retrying');
|
|
1176
|
+
return adoFetch(url, freshToken, true);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1156
1179
|
throw new Error(`ADO returned HTML instead of JSON (likely auth redirect) for ${url.split('?')[0]}`);
|
|
1157
1180
|
}
|
|
1158
1181
|
return JSON.parse(text);
|
package/package.json
CHANGED